architecture-design
系统架构设计方法论,包含架构模式选择、系统分层、目录结构设计
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.
/e2e-playwrightContext preview
The summary Claude sees to decide when to auto-load this skill.
Playwright E2E 测试完整方法论,涵盖项目初始化、Page Object Model、认证复用、API Mock、视觉回归、多浏览器测试、CI 集成和调试技巧
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 测试通过时 - 需要视觉回归测试时
---
# 新项目初始化(推荐) npm init playwright@latest # 已有项目添加 npm install -D @playwright/test npx playwright install
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` | 自动启动开发服务器 | 必须配置 |
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)
---
// 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();
}
}// 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();
}
}选择定位器时遵循以下优先级(可靠性从高到低):
| 优先级 | 方法 | 示例 | 说明 | |--------|------|------|------| | 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')` | **尽量避免** |
---
// 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/gLanguages / 语言 / 言語 / 언어 / Idiomas / Langues: English · 中文 · 日本語 · 한국어 · Español · Français · Português Boss is an auditable agent-team workflow for coding agents.
Repo: echoVic/boss-skill
系统架构设计方法论,包含架构模式选择、系统分层、目录结构设计
数据模型和API设计方法论,包含ERD设计、数据字典、RESTful API规范
后端API开发方法论,包括RESTful/GraphQL设计、请求验证、错误处理和安全实现
后端测试编写指南,包括单元测试、集成测试和E2E测试的编写方法和最佳实践
需求澄清 Skill。当用户只给了模糊描述时自动触发,通过业务提问把一句话翻译成完整需求,交给 Boss 流水线执行。 Triggers: '我想做一个', '帮我做', '有个想法', 'brainstorm', '帮我规划一下', '做个XX', 'I want to build' Does NOT…