Token导航 LogoToken导航TokenDH.com
待分类操作浏览器github未标认证来源可访问clear审计异常

e2e-test-writere2e 测试编写器

Agent Skill

用于辅助测试设计、自动化测试、用例整理和回归验证。它适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。使用时需要确认项目测试框架、运行命令和夹具数据,避免为了通过测试而改坏真实逻辑;涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

总安装

321

周安装

13

GitHub Stars

21

下载量

101
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:e2e-test-writer(e2e 测试编写器)
来源仓库:https://github.com/matteocervelli/llms
仓库路径:skills/e2e-test-writer
安装命令:
npx skills add https://github.com/matteocervelli/llms --skill e2e-test-writer
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/matteocervelli/llms --skill e2e-test-writer

简介

提供 Playwright 端到端测试编写的专业指导与架构建议。

  • 涵盖页面对象模型、测试隔离与响应式设计等多维度内容。
  • 适用于新功能测试、用户旅程验证与 CI/CD 自动化集成。
  • 需根据项目语言选择 JS 或 TS,并正确导入 test 与 expect。
  • e2e-test-writer 属于待分类类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

E2E Test Writer Skill

Purpose

This skill provides comprehensive guidance for writing end-to-end tests using Playwright, following best practices including page object model pattern, proper test isolation, and maintainable test architecture.

When to Use

  • Implementing E2E tests for new features
  • Testing user workflows and journeys
  • Validating browser interactions
  • Testing responsive design across devices
  • Regression testing after changes
  • CI/CD test automation

E2E Testing Workflow

1. Setup Playwright Project

Initialize Playwright:

# Install Playwright
npm init playwright@latest

# Or add to existing project
npm install -D @playwright/test
npx playwright install

Project Structure:

tests/
├── e2e/
│   ├── auth/
│   ├── features/
│   └── workflows/
├── pages/
│   ├── BasePage.ts
│   ├── LoginPage.ts
│   └── DashboardPage.ts
├── fixtures/
│   ├── test-data.ts
│   └── custom-fixtures.ts
├── utils/
│   ├── helpers.ts
│   └── constants.ts
└── playwright.config.ts

Playwright Configuration:

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html'],
    ['json', { outputFile: 'test-results/results.json' }],
    ['junit', { outputFile: 'test-results/junit.xml' }],
  ],
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 12'] },
    },
  ],
  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Deliverable: Playwright project configured and ready


2. Page Object Model Pattern

Base Page Class:

// pages/BasePage.ts
import { Page, Locator } from '@playwright/test';

export class BasePage {
  readonly page: Page;

  constructor(page: Page) {
    this.page = page;
  }

  async goto(path: string) {
    await this.page.goto(path);
  }

  async waitForPageLoad() {
    await this.page.waitForLoadState('networkidle');
  }

  async takeScreenshot(name: string) {
    await this.page.screenshot({ path: `screenshots/${name}.png` });
  }

  async getTitle(): Promise<string> {
    return await this.page.title();
  }

  async clickElement(locator: Locator) {
    await locator.waitFor({ state: 'visible' });
    await locator.click();
  }

  async fillInput(locator: Locator, value: string) {
    await locator.waitFor({ state: 'visible' });
    await locator.fill(value);
  }

  async getText(locator: Locator): Promise<string> {
    await locator.waitFor({ state: 'visible' });
    return await locator.textContent() || '';
  }
}

Example Page Object:

// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
import { BasePage } from './BasePage';

export class LoginPage extends BasePage {
  // Locators
  readonly usernameInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;
  readonly errorMessage: Locator;
  readonly forgotPasswordLink: Locator;
  readonly signUpLink: Locator;

  constructor(page: Page) {
    super(page);
    this.usernameInput = page.locator('#username');
    this.passwordInput = page.locator('#password');
    this.submitButton = page.locator('button[type="submit"]');
    this.errorMessage = page.locator('.error-message');
    this.forgotPasswordLink = page.locator('a[href="/forgot-password"]');
    this.signUpLink = page.locator('a[href="/signup"]');
  }

  // Actions
  async goto() {
    await super.goto('/login');
  }

  async login(username: string, password: string) {
    await this.fillInput(this.usernameInput, username);
    await this.fillInput(this.passwordInput, password);
    await this.clickElement(this.submitButton);
  }

  async clickForgotPassword() {
    await this.clickElement(this.forgotPasswordLink);
  }

  async clickSignUp() {
    await this.clickElement(this.signUpLink);
  }

  // Assertions helpers
  async getErrorMessage(): Promise<string> {
    return await this.getText(this.errorMessage);
  }

  async isLoginButtonEnabled(): Promise<boolean> {
    return await this.submitButton.isEnabled();
  }

  async waitForErrorMessage() {
    await this.errorMessage.waitFor({ state: 'visible' });
  }
}

Deliverable: Page objects for all application pages


3. Writing Test Cases

Basic Test Structure:

// tests/e2e/auth/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../../pages/LoginPage';
import { DashboardPage } from '../../pages/DashboardPage';

test.describe('User Login', () => {
  let loginPage: LoginPage;

  test.beforeEach(async ({ page }) => {
    loginPage = new LoginPage(page);
    await loginPage.goto();
  });

  test('successful login with valid credentials', async ({ page }) => {
    await loginPage.login('user@example.com', 'ValidPassword123!');

    const dashboardPage = new DashboardPage(page);
    await expect(page).toHaveURL('/dashboard');
    await expect(dashboardPage.welcomeMessage).toContainText('Welcome back');
  });

  test('failed login with invalid credentials shows error', async ({ page }) => {
    await loginPage.login('user@example.com', 'WrongPassword');

    await loginPage.waitForErrorMessage();
    const errorMsg = await loginPage.getErrorMessage();
    expect(errorMsg).toContain('Invalid username or password');
    await expect(page).toHaveURL('/login');
  });

  test('login button disabled with empty fields', async ({ page }) => {
    const isEnabled = await loginPage.isLoginButtonEnabled();
    expect(isEnabled).toBe(false);
  });

  test('forgot password link navigates correctly', async ({ page }) => {
    await loginPage.clickForgotPassword();
    await expect(page).toHaveURL('/forgot-password');
  });

  test('sign up link navigates correctly', async ({ page }) => {
    await loginPage.clickSignUp();
    await expect(page).toHaveURL('/signup');
  });
});

Testing User Workflows:

// tests/e2e/workflows/checkout.spec.ts
import { test, expect } from '@playwright/test';
import { ProductPage } from '../../pages/ProductPage';
import { CartPage } from '../../pages/CartPage';
import { CheckoutPage } from '../../pages/CheckoutPage';

test.describe('Checkout Workflow', () => {
  test('complete purchase from product to confirmation', async ({ page }) => {
    // 1. Browse product
    const productPage = new ProductPage(page);
    await productPage.goto('/products/item-123');
    await expect(productPage.productTitle).toBeVisible();

    // 2. Add to cart
    await productPage.addToCart();
    await expect(productPage.cartBadge).toHaveText('1');

    // 3. View cart
    await productPage.goToCart();
    const cartPage = new CartPage(page);
    await expect(cartPage.cartItems).toHaveCount(1);

    // 4. Proceed to checkout
    await cartPage.proceedToCheckout();
    const checkoutPage = new CheckoutPage(page);

    // 5. Fill shipping info
    await checkoutPage.fillShippingInfo({
      name: 'John Doe',
      address: '123 Main St',
      city: 'San Francisco',
      zip: '94102',
      country: 'US',
    });

    // 6. Fill payment info
    await checkoutPage.fillPaymentInfo({
      cardNumber: '4242424242424242',
      expiry: '12/25',
      cvv: '123',
    });

    // 7. Submit order
    await checkoutPage.submitOrder();

    // 8. Verify confirmation
    await expect(page).toHaveURL(/\/order-confirmation/);
    await expect(page.locator('.success-message')).toContainText('Order placed successfully');
  });
});

Deliverable: Comprehensive test suite covering user journeys


4. Test Data Management

Test Fixtures:

// fixtures/test-data.ts
export const testUsers = {
  validUser: {
    email: 'user@example.com',
    password: 'ValidPassword123!',
  },
  adminUser: {
    email: 'admin@example.com',
    password: 'AdminPassword123!',
  },
  newUser: {
    email: 'newuser@example.com',
    password: 'NewPassword123!',
    firstName: 'John',
    lastName: 'Doe',
  },
};

export const testProducts = {
  product1: {
    id: 'item-123',
    name: 'Test Product',
    price: 29.99,
  },
};

Custom Fixtures:

// fixtures/custom-fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { DashboardPage } from '../pages/DashboardPage';

type MyFixtures = {
  loginPage: LoginPage;
  dashboardPage: DashboardPage;
  authenticatedPage: Page;
};

export const test = base.extend<MyFixtures>({
  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await use(loginPage);
  },

  dashboardPage: async ({ page }, use) => {
    const dashboardPage = new DashboardPage(page);
    await use(dashboardPage);
  },

  authenticatedPage: async ({ page }, use) => {
    // Auto-login before test
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await loginPage.login('user@example.com', 'ValidPassword123!');
    await page.waitForURL('/dashboard');
    await use(page);
  },
});

export { expect } from '@playwright/test';

Using Custom Fixtures:

// tests/e2e/features/profile.spec.ts
import { test, expect } from '../../fixtures/custom-fixtures';

test.describe('User Profile', () => {
  test('user can update profile information', async ({ authenticatedPage, dashboardPage }) => {
    // Already logged in via authenticatedPage fixture
    await dashboardPage.goToProfile();

    // Test continues with authenticated context
    await dashboardPage.updateProfile({
      firstName: 'Jane',
      lastName: 'Smith',
    });

    await expect(dashboardPage.profileName).toHaveText('Jane Smith');
  });
});

Deliverable: Reusable test data and fixtures


5. Responsive Design Testing

Test Multiple Viewports:

// tests/e2e/responsive/layout.spec.ts
import { test, expect, devices } from '@playwright/test';

const viewports = [
  { name: 'Desktop', device: devices['Desktop Chrome'] },
  { name: 'Tablet', device: devices['iPad Pro'] },
  { name: 'Mobile', device: devices['iPhone 12'] },
];

viewports.forEach(({ name, device }) => {
  test.describe(`${name} Layout`, () => {
    test.use(device);

    test('navigation menu displays correctly', async ({ page }) => {
      await page.goto('/');

      if (name === 'Mobile') {
        // Mobile should show hamburger menu
        await expect(page.locator('.hamburger-menu')).toBeVisible();
        await expect(page.locator('.desktop-nav')).not.toBeVisible();
      } else {
        // Desktop/Tablet should show full navigation
        await expect(page.locator('.desktop-nav')).toBeVisible();
        await expect(page.locator('.hamburger-menu')).not.toBeVisible();
      }
    });

    test('images are responsive', async ({ page }) => {
      await page.goto('/products');

      const images = page.locator('img');
      const count = await images.count();

      for (let i = 0; i < count; i++) {
        const img = images.nth(i);
        const bbox = await img.boundingBox();
        if (bbox) {
          // Images should not overflow viewport
          expect(bbox.width).toBeLessThanOrEqual(device.viewport.width);
        }
      }
    });
  });
});

Deliverable: Tests covering responsive design


6. Visual Regression Testing

Screenshot Comparison:

// tests/e2e/visual/snapshot.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Visual Regression', () => {
  test('homepage matches baseline', async ({ page }) => {
    await page.goto('/');

    // Take full page screenshot
    await expect(page).toHaveScreenshot('homepage.png', {
      fullPage: true,
      maxDiffPixels: 100,
    });
  });

  test('modal dialog matches baseline', async ({ page }) => {
    await page.goto('/');
    await page.click('[data-testid="open-modal"]');

    // Screenshot of specific element
    const modal = page.locator('.modal');
    await expect(modal).toHaveScreenshot('modal.png');
  });

  test('dark mode matches baseline', async ({ page }) => {
    await page.goto('/');

    // Enable dark mode
    await page.evaluate(() => {
      document.documentElement.setAttribute('data-theme', 'dark');
    });

    await expect(page).toHaveScreenshot('homepage-dark.png', {
      fullPage: true,
    });
  });
});

Deliverable: Visual regression test suite


7. API Mocking and Network Testing

Mock API Responses:

// tests/e2e/network/api-mocking.spec.ts
import { test, expect } from '@playwright/test';

test.describe('API Mocking', () => {
  test('handles slow API response gracefully', async ({ page }) => {
    // Mock slow API
    await page.route('**/api/products', async (route) => {
      await new Promise(resolve => setTimeout(resolve, 3000));
      await route.fulfill({
        status: 200,
        body: JSON.stringify({ products: [] }),
      });
    });

    await page.goto('/products');

    // Should show loading state
    await expect(page.locator('.loading-spinner')).toBeVisible();

    // Should eventually show products
    await expect(page.locator('.product-list')).toBeVisible({ timeout: 5000 });
  });

  test('handles API error gracefully', async ({ page }) => {
    // Mock API error
    await page.route('**/api/products', (route) => {
      route.fulfill({
        status: 500,
        body: JSON.stringify({ error: 'Internal server error' }),
      });
    });

    await page.goto('/products');

    // Should show error message
    await expect(page.locator('.error-message')).toBeVisible();
    await expect(page.locator('.error-message')).toContainText('Failed to load products');
  });

  test('handles network timeout', async ({ page }) => {
    await page.route('**/api/products', (route) => {
      // Never fulfill, causing timeout
    });

    await page.goto('/products');

    // Should show timeout message
    await expect(page.locator('.timeout-message')).toBeVisible({ timeout: 10000 });
  });
});

Deliverable: Tests for API interactions and error states


8. Authentication State Management

Reuse Authentication State:

// tests/auth.setup.ts
import { test as setup } from '@playwright/test';
import { LoginPage } from './pages/LoginPage';

const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('user@example.com', 'ValidPassword123!');

  await page.waitForURL('/dashboard');

  // Save authentication state
  await page.context().storageState({ path: authFile });
});

Use Saved Auth State:

// playwright.config.ts
export default defineConfig({
  // ... other config

  projects: [
    {
      name: 'setup',
      testMatch: /.*\.setup\.ts/,
    },
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
});

Deliverable: Efficient authentication handling


Best Practices

Test Organization:

  • Group related tests with test.describe()
  • Use meaningful test names (describe behavior, not implementation)
  • One assertion per test (or closely related assertions)
  • Isolate tests (no dependencies between tests)

Locator Strategy:

  • Prefer test IDs: page.locator('[data-testid="submit"]')
  • Use semantic selectors: page.locator('button:has-text("Submit")')
  • Avoid CSS selectors tied to styling
  • Never use XPath unless absolutely necessary

Waiting Strategy:

  • Use auto-waiting (built into Playwright)
  • Avoid fixed waits (page.waitForTimeout())
  • Use waitForLoadState() for page loads
  • Use waitFor() for specific elements

Error Handling:

  • Use try-catch for expected errors
  • Add screenshots on failure
  • Capture network logs
  • Record video on failure

Performance:

  • Run tests in parallel when possible
  • Use test.describe.configure({mode: 'parallel'})
  • Share browser contexts when safe
  • Reuse authentication state
  • Mock slow external APIs

Maintainability:

  • Page Object Model for all pages
  • Extract common logic to helpers
  • Use constants for repeated values
  • Keep tests DRY (Don't Repeat Yourself)
  • Regular refactoring

Integration with Playwright MCP

Using MCP Tools:

// Example: Using Playwright MCP for navigation
test('navigate using MCP', async ({ page }) => {
  // Use mcp__playwright-mcp__playwright_navigate
  await page.evaluate(async () => {
    // MCP navigation call would go here
  });

  await expect(page).toHaveURL('/expected-page');
});

// Example: Using MCP for screenshots
test('capture screenshot with MCP', async ({ page }) => {
  await page.goto('/');

  // Use mcp__playwright-mcp__playwright_screenshot
  await page.evaluate(async () => {
    // MCP screenshot call would go here
  });
});

Remember

  • Test behavior, not implementation: Tests should survive refactoring
  • User perspective: Test what users do, not how code works
  • Isolation: Each test should run independently
  • Fast feedback: Keep tests fast (< 5 min total suite)
  • Flake-free: No intermittent failures
  • Clear failures: Easy to debug when tests fail
  • Maintainable: Easy to update when app changes
  • Comprehensive: Cover happy paths and edge cases
  • CI/CD ready: Tests run reliably in CI environment

Your goal is to create robust, maintainable E2E tests that provide confidence in application functionality across browsers and devices.

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenCode

29.87%
按下载量换算30

Antigravity

21.69%
按下载量换算22

Claude Code

17.46%
按下载量换算18

Codex

13.64%
按下载量换算14

Gemini CLI

8.71%
按下载量换算9

github-copilot

3.46%
按下载量换算3

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

未通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。

来源信息

继续浏览同类 Skills