作为前端你还不会 Playwright 进行单元测试吗?

18 阅读3分钟

Playwright 测试配置与使用指南

Playwright 是一个现代化的 Web 应用端到端测试框架,支持 Chromium、WebKit 和 Firefox,可以在 Windows、Linux 和 macOS 上运行。

安装

初始化项目

npm init playwright@latest

安装过程中会询问:

  • TypeScript 或 JavaScript(默认:TypeScript)
  • 测试文件夹名称(默认:tests,如果 tests 已存在则用 e2e)
  • 是否添加 GitHub Actions 工作流(推荐)
  • 是否安装 Playwright 浏览器(默认:是)

系统要求

  • Node.js:最新 20.x、22.x 或 24.x
  • macOS 14 (Ventura) 或更高版本
  • Debian 12/13,Ubuntu 22.04/24.04

项目结构

安装后生成以下结构:

playwright.config.ts    # 测试配置
package.json
package-lock.json
tests/
  example.spec.ts      # 示例测试

编写测试

基础测试示例

import { test, expect } from '@playwright/test';

test('基础测试'async ({ page }) => {
  await page.goto('https://example.com');
  await expect(page).toHaveTitle(/Example Domain/);
});

使用 Locators

test('表单提交测试', async ({ page }) => {
  await page.goto('https://example.com/form');
  
  // 填写表单
  await page.fill('input[name="email"]', 'test@example.com');
  await page.fill('input[name="password"]', 'password123');
  
  // 点击提交按钮
  await page.click('button[type="submit"]');
  
  // 验证成功跳转
  await expect(page).toHaveURL(/.*dashboard/);
});

断言

Playwright 提供了丰富的断言方法:

// 页面状态断言
await expect(page).toHaveTitle(/Page Title/);
await expect(page).toHaveURL('https://example.com');

// 元素可见性断言
await expect(page.locator('.header')).toBeVisible();
await expect(page.locator('.loading')).toBeHidden();

// 文本内容断言
await expect(page.locator('h1')).toHaveText('Welcome');
await expect(page.locator('.count')).toContainText('5');

// 属性断言
await expect(page.locator('input')).toHaveAttribute('type', 'email');

运行测试

运行所有测试

npx playwright test

运行单个测试文件

npx playwright test tests/example.spec.ts

有头模式运行(显示浏览器窗口)

npx playwright test --headed

运行特定浏览器

npx playwright test --project=chromium
npx playwright test --project=webkit

UI 模式

UI 模式提供监视模式、实时步骤视图、时间旅行调试等:

npx playwright test --ui

HTML 测试报告

测试运行后,HTML Reporter 提供可过滤的仪表板,按浏览器、通过、失败、跳过、不稳定等分类:

npx playwright show-report

配置文件

playwright.config.ts 基础配置

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

export default defineConfig({
  // 测试文件夹
  testDir'./tests',
  
  // 完全并行运行测试
  fullyParalleltrue,
  
  // CI 中失败时重试
  forbidOnly: !!process.env.CI,
  
  // CI 中重试次数
  retries: process.env.CI ? 2 0,
  
  // 并行工作进程数
  workers: process.env.CI ? 1 : undefined,
  
  // 报告器
  reporter'html',
  
  use: {
    // 基础 URL
    baseURL'http://localhost:3000',
    
    // 追踪重试失败的测试
    trace'on-first-retry',
  },

  // 配置不同浏览器
  projects: [
    {
      name'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});

高级功能

测试夹具(Fixtures)

import { test as base } from '@playwright/test';

// 自定义 fixture
type MyFixtures = {
  authenticatedPage: Page;
};

const test = base.extend<MyFixtures>({
  authenticatedPage: async ({ page }, use) => {
    // 登录
    await page.goto('/login');
    await page.fill('input[name="email"]''test@example.com');
    await page.fill('input[name="password"]''password');
    await page.click('button[type="submit"]');
    
    // 使用已认证的页面
    await use(page);
  },
});

test('使用自定义 fixture'async ({ authenticatedPage }) => {
  await authenticatedPage.goto('/dashboard');
  await expect(authenticatedPage).toHaveURL(/.*dashboard/);
});

代码生成(Codegen)

Playwright Codegen 可以通过录制用户操作生成测试代码:

npx playwright codegen https://example.com

追踪查看器

查看测试失败的详细追踪:

npx playwright show-trace trace.zip

更新 Playwright

npm install -@playwright/test@latest
npx playwright install --with-deps

检查版本:

npx playwright --version

最佳实践

  1. 使用 Locators 而不是 Selectors

    // ✅ 推荐
    await page.locator('button').click();
    
    // ❌ 不推荐
    await page.$('button').click();
    
  2. 使用 Playwright 的等待机制

    // 自动等待元素可交互
    await page.click('button');
    
    // 显式等待
    await page.waitForSelector('.loaded');
    
  3. 使用页面对象模式(Page Object Model)

    class LoginPage {
      constructor(private page: Page) {}
      
      async login(email: string, password: string) {
        await this.page.fill('input[name="email"]', email);
        await this.page.fill('input[name="password"]', password);
        await this.page.click('button[type="submit"]');
      }
    }
    
  4. 隔离测试数据

    test.use({ storageState: 'auth.json' });
    
    test('需要登录的测试'async ({ page }) => {
      // 测试逻辑
    });
    

参考资料