Skip to main content
ClaudeWave
Skill545 repo starsupdated 9d ago

qa/e2e-playwright

This Claude Code skill provides a comprehensive Playwright end-to-end testing methodology including project initialization with configuration templates, Page Object Model patterns, authentication fixture reuse, API mocking strategies, visual regression testing, multi-browser and viewport testing setup, CI pipeline integration, and debugging techniques. Use this when implementing automated E2E tests for web applications, meeting gating requirements, validating critical user workflows, ensuring cross-browser compatibility, or performing visual regression checks.

Install in Claude Code
Copy
git clone --depth 1 https://github.com/echoVic/boss-skill /tmp/qa-e2e-playwright && cp -r /tmp/qa-e2e-playwright/skill/skills/qa/e2e-playwright ~/.claude/skills/qa-e2e-playwright
Then start a new Claude Code session; the skill loads automatically.

SKILL.md

# Playwright E2E 测试方法论

## 适用场景

- Web 项目需要编写端到端测试
- 门禁(Gate 1)要求 E2E 测试通过
- 需要覆盖关键用户流程的自动化验证
- 需要多浏览器/多视口兼容性验证
- 需要视觉回归测试

---

## 1. 项目初始化

### 1.1 安装

```bash
# 新项目初始化(推荐)
npm init playwright@latest

# 已有项目添加
npm install -D @playwright/test
npx playwright install
```

### 1.2 配置文件(`playwright.config.ts`)

```typescript
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  // 测试产物目录
  outputDir: './e2e/test-results',

  // 全局超时
  timeout: 30_000,
  expect: { timeout: 5_000 },

  // 并行执行
  fullyParallel: true,
  workers: process.env.CI ? 1 : undefined,

  // 失败重试(CI 中重试一次减少 flaky)
  retries: process.env.CI ? 1 : 0,

  // 报告
  reporter: [
    ['html', { outputFolder: './e2e/playwright-report' }],
    ['json', { outputFile: './e2e/test-results/results.json' }],
    // CI 中额外输出到 stdout
    ...(process.env.CI ? [['github'] as const] : []),
  ],

  // 全局配置
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    // 失败时自动截图
    screenshot: 'only-on-failure',
    // 失败时录制 trace
    trace: 'on-first-retry',
    // 失败时录制视频
    video: 'on-first-retry',
  },

  // 多浏览器 + 移动端视口
  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 13'] } },
  ],

  // 开发服务器自动启动
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});
```

**关键配置说明**:

| 配置项 | 作用 | 建议值 |
|--------|------|--------|
| `fullyParallel` | 测试文件间并行执行 | `true` |
| `workers` | 并行 worker 数 | CI 为 1,本地默认 |
| `retries` | 失败重试次数 | CI 为 1,本地为 0 |
| `trace` | 失败时生成可视化时间线 | `on-first-retry` |
| `webServer` | 自动启动开发服务器 | 必须配置 |

### 1.3 目录结构

```
e2e/
├── playwright.config.ts        # 配置文件(或放在项目根目录)
├── fixtures/                   # 自定义 fixtures
│   ├── base.ts                 # 扩展 base test
│   └── auth.ts                 # 认证 fixture
├── pages/                      # Page Object Models
│   ├── login.page.ts
│   ├── dashboard.page.ts
│   └── components/             # 可复用组件 POM
│       ├── navbar.component.ts
│       └── modal.component.ts
├── specs/                      # 测试用例
│   ├── auth/
│   │   ├── login.spec.ts
│   │   └── register.spec.ts
│   ├── dashboard/
│   │   └── dashboard.spec.ts
│   └── crud/
│       └── user-management.spec.ts
├── helpers/                    # 测试工具
│   ├── seed.ts                 # 数据种子
│   └── cleanup.ts              # 数据清理
├── test-results/               # 测试产物(gitignore)
└── playwright-report/          # HTML 报告(gitignore)
```

---

## 2. Page Object Model(POM)

### 2.1 核心原则

- **每个页面一个 POM 类**:封装定位器和操作方法
- **不暴露 Locator**:外部只调用语义化方法
- **组件级复用**:导航栏、弹窗等提取为独立组件 POM

### 2.2 基础 POM

```typescript
// e2e/pages/login.page.ts
import { type Page, type Locator } from '@playwright/test';

export class LoginPage {
  private readonly emailInput: Locator;
  private readonly passwordInput: Locator;
  private readonly submitButton: Locator;
  private readonly errorMessage: Locator;

  constructor(private readonly page: Page) {
    this.emailInput = page.getByLabel('邮箱');
    this.passwordInput = page.getByLabel('密码');
    this.submitButton = page.getByRole('button', { name: '登录' });
    this.errorMessage = page.getByRole('alert');
  }

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  async getErrorMessage() {
    return this.errorMessage.textContent();
  }
}
```

### 2.3 组件 POM

```typescript
// e2e/pages/components/navbar.component.ts
import { type Page, type Locator } from '@playwright/test';

export class NavbarComponent {
  private readonly userMenu: Locator;
  private readonly logoutButton: Locator;

  constructor(private readonly page: Page) {
    this.userMenu = page.getByTestId('user-menu');
    this.logoutButton = page.getByRole('menuitem', { name: '退出登录' });
  }

  async logout() {
    await this.userMenu.click();
    await this.logoutButton.click();
  }

  async getUserDisplayName() {
    return this.userMenu.textContent();
  }
}
```

### 2.4 定位器优先级

选择定位器时遵循以下优先级(可靠性从高到低):

| 优先级 | 方法 | 示例 | 说明 |
|--------|------|------|------|
| 1 | `getByRole` | `getByRole('button', { name: '提交' })` | 无障碍语义,最稳定 |
| 2 | `getByLabel` | `getByLabel('邮箱')` | 表单元素首选 |
| 3 | `getByPlaceholder` | `getByPlaceholder('请输入邮箱')` | 备选 |
| 4 | `getByText` | `getByText('欢迎回来')` | 静态文本 |
| 5 | `getByTestId` | `getByTestId('submit-btn')` | 无语义标记时的兜底 |
| 6 | CSS/XPath | `page.locator('.btn-primary')` | **尽量避免** |

---

## 3. 认证状态复用

### 3.1 Global Setup 方式

```typescript
// e2e/global-setup.ts
import { chromium, type FullConfig } from '@playwright/test';

async function globalSetup(config: FullConfig) {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  // 执行登录
  await page.goto('http://localhost:3000/login');
  await page.getByLabel('邮箱').fill('admin@example.com');
  await page.getByLabel('密码').fill('password');
  await page.getByRole('button', { name: '登录' }).click();
  await page.waitForURL('/dashboard');

  // 保存认证状态
  await page.context().storageState({ path: './e2e/.auth/admin.json' });
  await browser.close();
}

export default globalSetup;
```

**配置引用**:

```typescript
// playwright.config.ts
export default defineConfig({
  globalSetup: './e2e/global-setup.ts',
  projects: [
    // 不带认证的测试
    { name: 'public', testMatch: /public\.spec\.ts/ },
    // 带认证的测试
    {
      name: 'authenticated',
      use: { storageState: './e2e/.auth/admin.json' },
      testIgnore: /public\.spec\.ts/,
    },
  ],
});
```

### 3.2 多角色认证

```typescript
// e2e/fixtures/auth.ts
import { test as base } from '@playw