/e2e-playwright
Playwright E2E 测试完整方法论,涵盖项目初始化、Page Object Model、认证复用、API Mock、视觉回归、多浏览器测试、CI 集成和调试技巧
$ npx -y skills add echoVic/boss-skill --skill e2e-playwright --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/e2e-playwright
Context preview
The summary Claude sees to decide when to auto-load this skill.
Playwright E2E 测试完整方法论,涵盖项目初始化、Page Object Model、认证复用、API Mock、视觉回归、多浏览器测试、CI 集成和调试技巧
SKILL.md
e2e-playwright.SKILL.mdname: qa/e2e-playwright
description: Playwright E2E 测试完整方法论,涵盖项目初始化、Page Object Model、认证复用、API Mock、视觉回归、多浏览器测试、CI 集成和调试技巧
version: 1.0.0
agent: qa
type: methodology
user-invocable: false
agent-invocable: true
dependencies:
- shared/tech-stack-detection
triggers:
- 需要编写或执行 E2E 测试时
- 需要配置 Playwright 测试环境时
- 门禁要求 E2E 测试通过时
- 需要视觉回归测试时
Playwright E2E 测试方法论
适用场景
- Web 项目需要编写端到端测试
- 门禁(Gate 1)要求 E2E 测试通过
- 需要覆盖关键用户流程的自动化验证
- 需要多浏览器/多视口兼容性验证
- 需要视觉回归测试
---
1. 项目初始化
1.1 安装
# 新项目初始化(推荐)
npm init playwright@latest
# 已有项目添加
npm install -D @playwright/test
npx playwright install
1.2 配置文件(`playwright.config.ts`)
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
// 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
// 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 方式
// 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;**配置引用**:
// playwright.config.ts
export default defineConfig({
globalSetup: './e2e/gRead more
name: qa/e2e-playwright description: Playwright E2E 测试完整方法论,涵盖项目初始化、Page Object Model、认证复用、API Mock、视觉回归、多浏览器测试、CI 集成和调试技巧 version: 1.0.0 agent: qa type: methodology user-invocable: false agent-invocable: true dependencies: - shared/tech-stack-detection triggers: - 需要编写或执行 E2E 测试时 - 需要配置 Playwright 测试环境时 - 门禁要求 E2E 测试通过时 - 需要视觉回归测试时
Playwright E2E 测试方法论
适用场景
- Web 项目需要编写端到端测试
- 门禁(Gate 1)要求 E2E 测试通过
- 需要覆盖关键用户流程的自动化验证
- 需要多浏览器/多视口兼容性验证
- 需要视觉回归测试
---
1. 项目初始化
1.1 安装
# 新项目初始化(推荐) npm init playwright@latest # 已有项目添加 npm install -D @playwright/test npx playwright install
1.2 配置文件(`playwright.config.ts`)
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
// 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
// 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 方式
// 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;**配置引用**:
// playwright.config.ts
export default defineConfig({
globalSetup: './e2e/gBoss is an auditable agent-team workflow for coding agents. It turns one coding agent into a structured engineering team: PM, Architect, UI Designer, Tech Lead, Scrum Master, Frontend, Backend, QA, and DevOps.
Repo: echoVic/boss-skill
Other skills on boss.
- /architecture-design
系统架构设计方法论,包含架构模式选择、系统分层、目录结构设计
Open skill - /data-api-design
数据模型和API设计方法论,包含ERD设计、数据字典、RESTful API规范
Open skill - /tech-research
技术调研方法论,通过系统性调研和对比分析,为技术选型提供数据支持
Open skill - /api-development
后端API开发方法论,包括RESTful/GraphQL设计、请求验证、错误处理和安全实现
Open skill - /testing-guide
后端测试编写指南,包括单元测试、集成测试和E2E测试的编写方法和最佳实践
Open skill - /brainstorming
需求澄清 Skill。当用户只给了模糊描述时自动触发,通过业务提问把一句话翻译成完整需求,交给 Boss 流水线执行。 Triggers: '我想做一个', '帮我做', '有个想法', 'brainstorm', '帮我规划一下', '做个XX', 'I want to build' Does NOT trigger: - 需求已经完整(包含做什么 + 给谁用 + 核心场景) - 纯技术问题或 bug 修复 Output: .boss/<feature>/design-brief.md 需求设计简报
Open skill

