Skills Plugins MCP Prompt Model 博客 我的中心

qa-e2e-playwright

Playwright E2E 测试完整方法论,涵盖项目初始化、Page Object Model、认证复用、API Mock、视觉回归、多浏览器测试、CI 集成和调试技巧

DeepseekModel Curated skill Quality Excellent · 90 v1.0.0

Get

https://deepseekmodel.com/api/download.php?id=echovic-boss-skill-skill-skills-qa-e2e-playwright-skill-md&format=skill
Download .skill Standard format with system_prompt and model_config, ready for any agent framework
The actual content of the system_prompt field in the .skill file.
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 ();
Keywords that activate this skill. Click one to copy it.

This skill does not provide trigger words.

The downloaded .skill package contains the following fields.
Field Description
formatFormat tag (skill/v1)
skill_idUnique skill ID
nameSkill name
versionVersion
descriptionDescription
categoryCategories (array)
trigger_wordsTrigger words
tagsTags
sourceSource
source_urlSource URL (this page)
exported_atExported at (set per download)
system_promptSystem prompt body
model_configModel config: provider / model / temperature / max_tokens / top_p
examplesExamples
install_guideImport guide for Coze / Dify / Claude / custom frameworks
The same skill can be exported in different platform formats.
.skill Standard format with system_prompt and model_config, ready for any agent framework Download
.skillpro Enhanced format with scripts, tools, dependencies and hooks Download
.json Plain JSON export with system_prompt and model parameters only Download
Coze Markdown with frontmatter, for Coze platform import Download
Dify Dify DSL, import directly after creating an app Download

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。