playwright-e2e-tester
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlaywright E2E Tester
Playwright 端到端测试专家
Overview
概述
Expert in end-to-end testing with Playwright, the modern cross-browser testing framework. Specializes in test generation, page object patterns, visual regression testing, and CI/CD integration. Handles complex testing scenarios including authentication flows, API mocking, and mobile emulation.
精通基于Playwright(现代跨浏览器测试框架)的端到端测试。擅长测试生成、页面对象模式、视觉回归测试以及CI/CD集成。能够处理复杂测试场景,包括认证流程、API模拟和移动端仿真。
When to Use
使用场景
- Setting up Playwright in a new or existing project
- Writing E2E tests for critical user flows
- Debugging flaky tests or test failures
- Implementing visual regression testing
- Configuring Playwright for CI/CD pipelines
- Migrating from Cypress, Selenium, or Puppeteer
- Testing authenticated flows with session management
- Cross-browser testing (Chromium, Firefox, WebKit)
- 在新项目或现有项目中搭建Playwright环境
- 为关键用户流编写端到端测试
- 调试不稳定测试或测试失败问题
- 实现视觉回归测试
- 为CI/CD流水线配置Playwright
- 从Cypress、Selenium或Puppeteer迁移至Playwright
- 测试带会话管理的认证流程
- 跨浏览器测试(Chromium、Firefox、WebKit)
Capabilities
能力范围
Test Generation & Writing
测试生成与编写
- Generate Playwright tests from user stories or acceptance criteria
- Write tests using best practices (locators, assertions, waits)
- Implement Page Object Model (POM) patterns
- Create reusable test fixtures and utilities
- Handle dynamic content and race conditions
- 根据用户故事或验收标准生成Playwright测试
- 遵循最佳实践编写测试(定位器、断言、等待机制)
- 实现页面对象模型(POM)模式
- 创建可复用的测试夹具和工具函数
- 处理动态内容和竞态条件
Configuration & Setup
配置与搭建
- Configure for different environments
playwright.config.ts - Set up projects for multiple browsers and viewports
- Configure base URL, timeouts, and retries
- Implement global setup/teardown for auth
- Set up test reporters (HTML, JSON, JUnit)
- 针对不同环境配置
playwright.config.ts - 为多浏览器和视口设置测试项目
- 配置基础URL、超时时间和重试机制
- 实现认证相关的全局前置/后置操作
- 设置测试报告器(HTML、JSON、JUnit)
Advanced Patterns
高级模式
- API mocking with and
route()fulfill() - Network interception and request validation
- Visual regression with
toHaveScreenshot() - Accessibility testing with
@axe-core/playwright - Mobile emulation and device testing
- Geolocation and permissions mocking
- 使用和
route()进行API模拟fulfill() - 网络拦截与请求验证
- 使用进行视觉回归测试
toHaveScreenshot() - 结合进行无障碍测试
@axe-core/playwright - 移动端仿真与设备测试
- 地理位置与权限模拟
CI/CD Integration
CI/CD集成
- GitHub Actions workflow configuration
- Parallel test execution with sharding
- Artifact collection (traces, screenshots, videos)
- Flaky test detection and retry strategies
- Test result reporting and notifications
- GitHub Actions工作流配置
- 分片并行执行测试
- 收集测试产物(追踪信息、截图、录屏)
- 不稳定测试检测与重试策略
- 测试结果上报与通知
Debugging & Maintenance
调试与维护
- Use Playwright Inspector and Trace Viewer
- Debug with and headed mode
page.pause() - Analyze test traces for failures
- Reduce test flakiness with proper waits
- Maintain test stability over time
- 使用Playwright Inspector和Trace Viewer
- 通过和有头模式调试
page.pause() - 分析测试追踪信息定位失败原因
- 通过合理等待减少测试不稳定性
- 长期维护测试稳定性
Dependencies
依赖工具
Works well with:
- - Unit test patterns that complement E2E
vitest-testing-patterns - - CI/CD pipeline setup
github-actions-pipeline-builder - - Extended accessibility testing
accessibility-auditor - - API contract testing alongside E2E
api-architect
适配以下工具:
- - 与端到端测试互补的单元测试模式
vitest-testing-patterns - - CI/CD流水线搭建工具
github-actions-pipeline-builder - - 扩展无障碍测试工具
accessibility-auditor - - 与端到端测试配合的API契约测试工具
api-architect
Examples
示例
Basic Test Structure
基础测试结构
typescript
import { test, expect } from '@playwright/test';
test.describe('User Authentication', () => {
test('should allow user to sign in', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('securepassword');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page).toHaveURL('/dashboard');
});
});typescript
import { test, expect } from '@playwright/test';
test.describe('User Authentication', () => {
test('should allow user to sign in', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('securepassword');
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page).toHaveURL('/dashboard');
});
});Page Object Pattern
页面对象模式
typescript
// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly signInButton: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.signInButton = page.getByRole('button', { name: 'Sign In' });
}
async goto() {
await this.page.goto('/login');
}
async signIn(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.signInButton.click();
}
}typescript
// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly signInButton: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.signInButton = page.getByRole('button', { name: 'Sign In' });
}
async goto() {
await this.page.goto('/login');
}
async signIn(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.signInButton.click();
}
}Auth Setup Fixture
认证前置夹具
typescript
// fixtures/auth.ts
import { test as base } from '@playwright/test';
export const test = base.extend({
authenticatedPage: async ({ page }, use) => {
// Perform authentication
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.TEST_USER!);
await page.getByLabel('Password').fill(process.env.TEST_PASS!);
await page.getByRole('button', { name: 'Sign In' }).click();
// Wait for auth to complete
await page.waitForURL('/dashboard');
// Use the authenticated page in tests
await use(page);
},
});typescript
// fixtures/auth.ts
import { test as base } from '@playwright/test';
export const test = base.extend({
authenticatedPage: async ({ page }, use) => {
// 执行认证操作
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.TEST_USER!);
await page.getByLabel('Password').fill(process.env.TEST_PASS!);
await page.getByRole('button', { name: 'Sign In' }).click();
// 等待认证完成
await page.waitForURL('/dashboard');
// 在测试中使用已认证页面
await use(page);
},
});GitHub Actions CI
GitHub Actions CI配置
yaml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/yaml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run E2E tests
run: npx playwright test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/Visual Regression Test
视觉回归测试
typescript
test('homepage matches snapshot', async ({ page }) => {
await page.goto('/');
// Full page screenshot comparison
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
maxDiffPixelRatio: 0.01,
});
// Component-level screenshot
const hero = page.getByTestId('hero-section');
await expect(hero).toHaveScreenshot('hero-section.png');
});typescript
test('homepage matches snapshot', async ({ page }) => {
await page.goto('/');
// 全页面截图对比
await expect(page).toHaveScreenshot('homepage.png', {
fullPage: true,
maxDiffPixelRatio: 0.01,
});
// 组件级截图对比
const hero = page.getByTestId('hero-section');
await expect(hero).toHaveScreenshot('hero-section.png');
});API Mocking
API模拟
typescript
test('displays products from API', async ({ page }) => {
// Mock the API response
await page.route('**/api/products', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Product A', price: 29.99 },
{ id: 2, name: 'Product B', price: 49.99 },
]),
});
});
await page.goto('/products');
await expect(page.getByText('Product A')).toBeVisible();
await expect(page.getByText('$29.99')).toBeVisible();
});typescript
test('displays products from API', async ({ page }) => {
// 模拟API响应
await page.route('**/api/products', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Product A', price: 29.99 },
{ id: 2, name: 'Product B', price: 49.99 },
]),
});
});
await page.goto('/products');
await expect(page.getByText('Product A')).toBeVisible();
await expect(page.getByText('$29.99')).toBeVisible();
});Best Practices
最佳实践
- Use role-based locators - Prefer ,
getByRole(),getByLabel()over CSS selectorsgetByText() - Avoid hard waits - Use ,
waitForSelector(), or assertions instead ofwaitForURL()waitForTimeout() - Isolate tests - Each test should be independent and not rely on state from other tests
- Use fixtures - Share setup logic through fixtures rather than hooks
beforeEach - Keep tests focused - Test one user flow per test, avoid testing multiple unrelated things
- Handle flakiness proactively - Use proper waits, retries, and stable locators
- Organize with Page Objects - Encapsulate page interactions for maintainability
- Run in CI - Always run E2E tests in CI before merging
- 使用基于角色的定位器 - 优先使用、
getByRole()、getByLabel()而非CSS选择器getByText() - 避免硬等待 - 使用、
waitForSelector()或断言替代waitForURL()waitForTimeout() - 隔离测试 - 每个测试应独立,不依赖其他测试的状态
- 使用夹具 - 通过夹具共享前置逻辑,而非钩子
beforeEach - 保持测试聚焦 - 每个测试只验证一个用户流,避免测试无关内容
- 主动处理不稳定性 - 使用合理等待、重试机制和稳定定位器
- 用页面对象组织代码 - 封装页面交互逻辑以提升可维护性
- 在CI中运行 - 合并代码前务必在CI中执行端到端测试
Common Pitfalls
常见陷阱
- Flaky locators: Avoid fragile selectors like or auto-generated class names
nth-child(3) - Race conditions: Always wait for elements/navigation before interacting
- Shared state: Tests should not depend on execution order
- Slow tests: Use API calls to set up state instead of UI interactions when possible
- Missing cleanup: Clean up test data to avoid pollution between runs
- 不稳定定位器:避免使用或自动生成类名这类脆弱选择器
nth-child(3) - 竞态条件:交互前务必等待元素加载或导航完成
- 共享状态:测试不应依赖执行顺序
- 测试缓慢:尽可能用API调用设置状态,而非UI交互
- 缺少清理:清理测试数据避免跨测试污染