开发编程
#api
qa-e2e-playwright
Playwright E2E 测试完整方法论,涵盖项目初始化、Page Object Model、认证复用、API Mock、视觉回归、多浏览器测试、CI 集成和调试技巧
DeepseekModel
官方收录技能
质量 优秀 · 90
v1.0.0
获取
https://deepseekmodel.com/api/download.php?id=echovic-boss-skill-skill-skills-qa-e2e-playwright-skill-md&format=skill
下载 .skill
标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
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/global-setup.ts' , projects : [ // 不带认证的测试 { name : 'public' , testMatch : /public\.spec\.ts/ }, // 带认证的测试 { name : 'authenticated' , use : { storageState : './e2e/.auth/admin.json' }, testIgnore : /public\.spec\.ts/ , }, ], }); 3.2 多角色认证 // e2e/fixtures/auth.ts import { test as base } from '@playwright/test' ; type AuthFixtures = { adminPage : Page ; userPage : Page ; }; export const test = base. extend < AuthFixtures >({ adminPage : async ({ browser }, use) => { const context = await browser. newContext ({ storageState : './e2e/.auth/admin.json' , }); const page = await context. newPage (); await use (page); await context. close (); }, userPage : async ({ browser }, use) => { const context = await browser. newContext ({ storageState : './e2e/.auth/user.json' , }); const page = await context. newPage (); await use (page); await context. close (); }, }); 4. API Mocking 4.1 使用 page.route 拦截请求 test ( '显示用户列表(API Mock)' , async ({ page }) => { // 拦截 API 请求 await page. route ( '/api/users' , async (route) => { await route. fulfill ({ status : 200 , contentType : 'application/json' , body : JSON . stringify ([ { id : 1 , name : '张三' , email : 'zhang@example.com' }, { id : 2 , name : '李四' , email : 'li@example.com' }, ]), }); }); await page. goto ( '/users' ); await expect (page. getByText ( '张三' )). toBeVisible (); await expect (page. getByText ( '李四' )). toBeVisible (); }); 4.2 模拟错误响应 test ( 'API 失败时显示错误提示' , async ({ page }) => { await page. route ( '/api/users' , async (route) => { await route. fulfill ({ status : 500 , body : 'Internal Server Error' }); }); await page. goto ( '/users' ); await expect (page. getByText ( '加载失败' )). toBeVisible (); await expect (page. getByRole ( 'button' , { name : '重试' })). toBeVisible (); }); 4.3 Mock 与真实请求混合 test ( '部分 API Mock' , async ({ page }) => { // 只 Mock 第三方支付接口,其余走真实请求 await page. route ( '**/api/payment/**' , async (route) => { await route. fulfill ({ status : 200 , body : JSON . stringify ({ success : true , transactionId : 'mock-tx-001' }), }); }); await page. goto ( '/checkout' ); // ... 执行支付流程 }); 4.4 何时用 Mock vs 真实 API 场景 建议 核心用户路径 真实 API (关键路径证据规则) 第三方服务(支付、邮件) Mock 错误/边界状态 Mock 加载状态、空数据 Mock 数据量大的列表/分页 Mock + 至少一条真实路径 ⚠️ Boss 门禁规则 :核心用户路径只由 Mock 证明的,必须标记为 未验证 ,不能作为发布证据。 5. 关键用户流程测试(必须覆盖) 5.1 CRUD 完整流程 // e2e/specs/crud/user-management.spec.ts import { test, expect } from '@playwright/test' ; import { UserListPage } from '../../pages/user-list.page' ; import { UserFormPage } from '../../pages/user-form.page' ; test. describe ( '用户管理 CRUD' , () => { let userList : UserListPage ; let userForm : UserFormPage ; test. beforeEach ( async ({ page }) => { userList = new UserListPage (page); userForm = new UserFormPage (page); await userList. goto (); }); test ( '创建 → 编辑 → 删除完整流程' , async ({ page }) => { // 创建 await userList. clickAddUser (); await userForm. fillName ( '测试用户' ); await userForm. fillEmail ( 'test@example.com' ); await userForm. submit ();
Agent 识别该技能的关键词,点击任意一个即可复制。
该技能未提供触发词。
下载的 .skill 包内含以下字段。
| 字段 | 说明 |
|---|---|
| format | 格式标识(skill/v1) |
| skill_id | 技能唯一 ID |
| name | 技能名称 |
| version | 版本号 |
| description | 技能描述 |
| category | 所属分类(数组) |
| trigger_words | 触发词列表 |
| tags | 标签列表 |
| source | 来源标识 |
| source_url | 来源链接(本页地址) |
| exported_at | 导出时间(每次下载生成) |
| system_prompt | 系统提示词正文 |
| model_config | 模型参数:provider / model / temperature / max_tokens / top_p |
| examples | 示例 |
| install_guide | 各平台导入说明(Coze / Dify / Claude / 自定义框架) |