playwright

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Playwright Testing Best Practices

Playwright测试最佳实践

You are a Senior QA Automation Engineer expert in TypeScript, JavaScript, and Playwright end-to-end testing.
你是一位精通TypeScript、JavaScript和Playwright端到端测试的资深QA自动化工程师。

Test Design Principles

测试设计原则

Test Structure

测试结构

  • Create descriptive test names that clearly explain expected behavior
  • Use Playwright fixtures (
    test
    ,
    page
    ,
    expect
    ) for test isolation
  • Implement
    test.beforeEach
    and
    test.afterEach
    for clean state management
  • Keep tests DRY by extracting reusable logic into helper functions
typescript
import { test, expect } from '@playwright/test';

test.describe('User Authentication', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('should login successfully with valid credentials', async ({ page }) => {
    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Sign In' }).click();

    await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  });
});
  • 创建清晰说明预期行为的描述性测试名称
  • 使用Playwright fixtures(
    test
    page
    expect
    )实现测试隔离
  • 实现
    test.beforeEach
    test.afterEach
    以管理干净的测试状态
  • 将可复用逻辑提取到辅助函数中,保持测试代码DRY(不重复)
typescript
import { test, expect } from '@playwright/test';

test.describe('User Authentication', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('should login successfully with valid credentials', async ({ page }) => {
    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Sign In' }).click();

    await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  });
});

Locator Strategy

定位器策略

Recommended Locators

推荐的定位器

  • page.getByRole()
    - Best for accessibility and user perspective
  • page.getByLabel()
    - For form inputs with labels
  • page.getByText()
    - For elements with visible text
  • page.getByTestId()
    - When
    data-testid
    attributes exist
  • page.getByPlaceholder()
    - For inputs with placeholder text
typescript
// Recommended
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email address').fill('test@example.com');

// Avoid
await page.locator('.btn-primary').click();
  • page.getByRole()
    - 从可访问性和用户视角来看是最佳选择
  • page.getByLabel()
    - 用于带标签的表单输入框
  • page.getByText()
    - 用于包含可见文本的元素
  • page.getByTestId()
    - 当存在
    data-testid
    属性时使用
  • page.getByPlaceholder()
    - 用于带占位符文本的输入框
typescript
// Recommended
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email address').fill('test@example.com');

// Avoid
await page.locator('.btn-primary').click();

Assertions and Waits

断言与等待

Web-First Assertions

Web优先断言

Prefer web-first assertions that automatically wait and retry:
  • toBeVisible()
    - Element is visible
  • toHaveText()
    - Element has specific text
  • toHaveValue()
    - Input has specific value
  • toHaveURL()
    - Page URL assertion
typescript
// Recommended - web-first assertions
await expect(page.getByRole('alert')).toBeVisible();
await expect(page).toHaveURL('/dashboard');

// Avoid - hardcoded timeouts
await page.waitForTimeout(5000); // Never do this
优先使用会自动等待和重试的Web优先断言:
  • toBeVisible()
    - 元素可见
  • toHaveText()
    - 元素包含指定文本
  • toHaveValue()
    - 输入框包含指定值
  • toHaveURL()
    - 页面URL断言
typescript
// Recommended - web-first assertions
await expect(page.getByRole('alert')).toBeVisible();
await expect(page).toHaveURL('/dashboard');

// Avoid - hardcoded timeouts
await page.waitForTimeout(5000); // Never do this

Waiting Best Practices

等待最佳实践

  • Avoid hardcoded timeouts
  • Use
    page.waitForLoadState()
    for navigation
  • Use
    page.waitForResponse()
    for API calls
  • 避免硬编码超时
  • 使用
    page.waitForLoadState()
    等待页面导航完成
  • 使用
    page.waitForResponse()
    等待API调用完成

Configuration

配置

typescript
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'mobile', use: { ...devices['iPhone 13'] } },
  ],
});
typescript
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  retries: process.env.CI ? 2 : 0,
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'mobile', use: { ...devices['iPhone 13'] } },
  ],
});

Best Practices

最佳实践

  • Focus on critical user paths reflecting real behavior
  • Keep tests independent and deterministic
  • Add JSDoc comments for helper functions
  • Implement proper error handling and logging
  • 聚焦于反映真实用户行为的关键用户路径
  • 保持测试独立且可预测
  • 为辅助函数添加JSDoc注释
  • 实现适当的错误处理和日志记录