playwright-e2e-tester

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Playwright 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
    playwright.config.ts
    for different environments
  • 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
    route()
    and
    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()
    fulfill()
    进行API模拟
  • 网络拦截与请求验证
  • 使用
    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
    page.pause()
    and headed mode
  • 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:
  • vitest-testing-patterns
    - Unit test patterns that complement E2E
  • github-actions-pipeline-builder
    - CI/CD pipeline setup
  • accessibility-auditor
    - Extended accessibility testing
  • api-architect
    - API contract testing alongside E2E
适配以下工具:
  • vitest-testing-patterns
    - 与端到端测试互补的单元测试模式
  • github-actions-pipeline-builder
    - CI/CD流水线搭建工具
  • accessibility-auditor
    - 扩展无障碍测试工具
  • api-architect
    - 与端到端测试配合的API契约测试工具

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

最佳实践

  1. Use role-based locators - Prefer
    getByRole()
    ,
    getByLabel()
    ,
    getByText()
    over CSS selectors
  2. Avoid hard waits - Use
    waitForSelector()
    ,
    waitForURL()
    , or assertions instead of
    waitForTimeout()
  3. Isolate tests - Each test should be independent and not rely on state from other tests
  4. Use fixtures - Share setup logic through fixtures rather than
    beforeEach
    hooks
  5. Keep tests focused - Test one user flow per test, avoid testing multiple unrelated things
  6. Handle flakiness proactively - Use proper waits, retries, and stable locators
  7. Organize with Page Objects - Encapsulate page interactions for maintainability
  8. Run in CI - Always run E2E tests in CI before merging
  1. 使用基于角色的定位器 - 优先使用
    getByRole()
    getByLabel()
    getByText()
    而非CSS选择器
  2. 避免硬等待 - 使用
    waitForSelector()
    waitForURL()
    或断言替代
    waitForTimeout()
  3. 隔离测试 - 每个测试应独立,不依赖其他测试的状态
  4. 使用夹具 - 通过夹具共享前置逻辑,而非
    beforeEach
    钩子
  5. 保持测试聚焦 - 每个测试只验证一个用户流,避免测试无关内容
  6. 主动处理不稳定性 - 使用合理等待、重试机制和稳定定位器
  7. 用页面对象组织代码 - 封装页面交互逻辑以提升可维护性
  8. 在CI中运行 - 合并代码前务必在CI中执行端到端测试

Common Pitfalls

常见陷阱

  • Flaky locators: Avoid fragile selectors like
    nth-child(3)
    or auto-generated class names
  • 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交互
  • 缺少清理:清理测试数据避免跨测试污染