Playwright Test

Playwright 除了作为浏览器自动化工具,还自带了一个 完整的测试框架 —— Playwright Test

Playwright Test 可以帮助我们组织测试用例、生成报告、并行执行、失败重试,功能完备,开箱即用。

  1. Playwright Test 是一个 集成的测试框架,无需额外依赖。
  2. 配置文件可设置 超时、重试、报告、浏览器项目等。
  3. 测试用例支持 分组、钩子、数据驱动
  4. 提供 并行执行、HTML 报告、失败重试 等高级能力。

测试框架概述

  • 集成测试框架:自带测试运行器,无需额外安装 Jest、Mocha。
  • 内置断言库:支持 expect 断言。
  • 测试隔离:每个 test 默认在新的浏览器上下文中执行,互不干扰。
  • 丰富功能:并行执行、重试、截图、录制、HTML 报告等。

安装方式:

npm init playwright@latest

自动生成项目结构,包含配置文件 playwright.config.js

1. 配置文件详解

Playwright Test 使用 playwright.config.js(或 .ts)作为配置入口。

// playwright.config.js
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',         // 测试目录
  timeout: 30 * 1000,         // 单个测试超时时间
  retries: 2,                 // 失败重试次数
  reporter: [['html'], ['list']], // 测试报告类型
  use: {
    headless: true,           // 是否无头模式
    screenshot: 'only-on-failure', // 失败时截图
    video: 'retain-on-failure',    // 失败时保留视频
    baseURL: 'https://example.com', // 基础 URL
  },
  projects: [
    { name: 'Chromium', use: { browserName: 'chromium' } },
    { name: 'Firefox',  use: { browserName: 'firefox' } },
    { name: 'WebKit',   use: { browserName: 'webkit' } },
  ],
});

2. 测试脚本结构

Playwright Test 脚本通常包含:

1、导入测试库

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

2、编写测试用例

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

3、分组与钩子

  • 使用 describe 组织测试
  • 使用 beforeEach / afterEach 做前后置处理

编写测试用例

1. test() 函数使用

test('页面标题验证', async ({ page }) => {
  await page.goto('https://playwright.dev');
  await expect(page).toHaveTitle(/Playwright/);
});

2. describe() 分组

test.describe('用户登录模块', () => {
  test('输入正确账号密码', async ({ page }) => {
    // 登录逻辑...
  });

  test('输入错误密码', async ({ page }) => {
    // 验证错误提示...
  });
});

3. 测试钩子

test.describe('购物车模块', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('https://example.com/cart');
  });

  test.afterEach(async ({ page }) => {
    await page.screenshot({ path: 'cart.png' });
  });

  test('添加商品', async ({ page }) => {
    await page.click('#add-item');
    await expect(page.locator('#cart-count')).toHaveText('1');
  });
});

4. 测试数据准备

支持参数化测试:

const users = [
  { name: 'admin', password: '123456' },
  { name: 'guest', password: 'guest' },
];

for (const user of users) {
  test(`用户 ${user.name} 登录`, async ({ page }) => {
    await page.goto('https://example.com/login');
    await page.fill('#username', user.name);
    await page.fill('#password', user.password);
    await page.click('#submit');
    await expect(page.locator('.welcome')).toBeVisible();
  });
}

运行和报告

1. 测试执行命令

npx playwright test          # 运行全部测试
npx playwright test login.spec.js   # 运行单个文件
npx playwright test -g "登录"       # 运行指定用例

2. 并行测试

Playwright 默认 按文件并行执行,也可在配置文件中指定 workers 数量:

npx playwright test --workers=4

3. 测试报告生成

运行后生成 HTML 报告

npx playwright show-report

4. 失败重试机制

在配置文件 playwright.config.js 中设置:

retries: 2

测试失败时会自动重试,适合处理偶发性错误。

  1. Playwright Test 是一个 集成的测试框架,无需额外依赖。
  2. 配置文件可设置 超时、重试、报告、浏览器项目等。
  3. 测试用例支持 分组、钩子、数据驱动
  4. 提供 并行执行、HTML 报告、失败重试 等高级能力。

完整测试套件示例

1、项目结构

playwright-demo/
├─ playwright.config.ts           # 全局配置(多项目、多浏览器、报告、重试…)
├─ package.json
├─ tests/
│  ├─ auth.setup.ts               # 一次性登录,生成 storageState
│  ├─ smoke/
│  │  └─ home.spec.ts             # 冒烟用例:标题/URL/截图对比
│  └─ e2e/
│     └─ cart.spec.ts             # 端到端:添加购物车、断言、附件、步骤
├─ fixtures/
│  ├─ pages.ts                    # 页面对象(PO)
│  └─ test.extend.ts              # 自定义 fixtures
└─ snapshots/                     # 视觉基线(自动生成)

2、配置文件(playwright.config.ts)

实例

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

export default defineConfig({
  testDir: './tests',
  timeout: 30_000,
  retries: 2,
  workers: 4,
  reporter: [['html', { outputFolder: 'report' }], ['list']],
  forbidOnly: !!process.env.CI,

  // 统一默认使用(可在项目或测试级覆盖)
  use: {
    baseURL: 'https://demo.playwright.dev',
    headless: true,
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
    viewport: { width: 1366, height: 900 },
    locale: 'zh-CN',
  },

  // 多项目:不同浏览器/设备/登录态
  projects: [
    { name: 'Chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'Firefox',  use: { ...devices['Desktop Firefox'] } },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 7'], headless: true },
    },
    // 带登录态项目(复用 storageState)
    {
      name: 'Chromium Logged-in',
      use: { storageState: 'storageState.json' },
      dependencies: ['setup'], // 依赖前置项目生成登录态
    },
    // 前置"setup"项目:只跑一次生成 storageState
    {
      name: 'setup',
      testMatch: /auth\.setup\.ts/,
    },
  ],
});

要点:projects 让同一套用例在不同浏览器/配置下运行;use 可在全局/项目/测试三级覆盖;报告与重试在配置中统一管理。

3、夹具与页面对象

fixtures/test.extend.ts(自定义 fixture + 公共步骤封装)

实例

// fixtures/test.extend.ts
import { test as base } from '@playwright/test';
import { HomePage, CartPage } from './pages';

type MyFixtures = {
  home: HomePage;
  cart: CartPage;
};

export const test = base.extend<MyFixtures>({
  home: async ({ page }, use) => {
    const home = new HomePage(page);
    await use(home);
  },
  cart: async ({ page }, use) => {
    const cart = new CartPage(page);
    await use(cart);
  },
});

export { expect } from '@playwright/test';

基于 test.extend() 增加自定义 fixture,便于在任意用例中直接注入 home、cart 实例。

fixtures/pages.ts(页面对象)

实例

// fixtures/pages.ts
import { Page, expect } from '@playwright/test';

export class HomePage {
  constructor(private page: Page) {}
  async goto() { await this.page.goto('/'); }
  get tryTodoLink() { return this.page.getByRole('link', { name: /ToDo MVC/i }); }
}

export class CartPage {
  constructor(private page: Page) {}
  async goto() { await this.page.goto('/e2e'); } // 假设存在 e2e 示例页
  get addFirstItem() { return this.page.getByRole('button', { name: /Add .* #1/i }); }
  get cartCount() { return this.page.locator('[data-test=cart-count]'); }
  async addOne() { await this.addFirstItem.click(); }
  async expectCount(n: number) { await expect(this.cartCount).toHaveText(String(n)); }
}

4、前置登录(一次性生成 storageState)

tests/auth.setup.ts(仅在 "setup" 项目里执行)

实例

// tests/auth.setup.ts
import { test, expect } from '@playwright/test';

test('create storageState', async ({ page }) => {
  await page.goto('https://demo.playwright.dev/todomvc');
  // ……此处替换为你的登录流程……
  // 假设已登录成功
  await expect(page).toHaveURL(/todomvc/);
  await page.context().storageState({ path: 'storageState.json' });
});

5、冒烟测试(标题/URL/视觉对比)

tests/smoke/home.spec.ts

实例

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

test.describe('Smoke - Home', () => {
  test('title & url & visual snapshot', async ({ page }) => {
    await page.goto('https://playwright.dev');
    await expect(page).toHaveTitle(/Playwright/);
    await expect(page).toHaveURL(/playwright\.dev/);

    // 视觉回归(自动生成/比对基线)
    await expect(page).toHaveScreenshot(); // 首次运行会生成基线
  });
});

视觉对比建议使用 toHaveScreenshot(优先于用 page.screenshot() + toMatchSnapshot 的手写方式)。

6、端到端测试(步骤、附件、断言)

tests/e2e/cart.spec.ts

实例

import { test, expect } from '../../fixtures/test.extend';

test.describe.configure({ mode: 'parallel' });

test.describe('E2E - Cart', () => {
  test.beforeEach(async ({ home }) => {
    await test.step('进入首页', async () => {
      await home.goto();
    });
  });

  test('添加商品到购物车(含附件与断言)', async ({ page, cart }) => {
    await test.step('进入购物页并添加商品', async () => {
      await cart.goto();
      await cart.addOne();
      await cart.expectCount(1);
    });

    await test.step('附加调试信息到报告', async () => {
      await test.info().attach('state.json', {
        contentType: 'application/json',
        body: Buffer.from(JSON.stringify({ ts: Date.now(), note: 'after add' })),
      });
    });

    // 元素级截图
    await test.step('元素截图', async () => {
      const badge = page.locator('[data-test=cart-count]');
      await expect(badge).toBeVisible();
      await expect(badge).toHaveScreenshot(); // 元素快照
    });
  });
});

用 test.step 分层记录步骤、用 test.info().attach()把调试数据/截图附加到报告,便于排查;并行模式可用 describe.configure({ mode: 'parallel' })。

7、运行与报告

常用命令:

npx playwright test                # 运行全部
npx playwright test tests/e2e      # 运行目录
npx playwright test -g "Cart"      # 按标题过滤
npx playwright test --project="Chromium"   # 指定项目
npx playwright show-report         # 打开HTML报告
npx playwright test --update-snapshots     # 更新视觉基线
npx playwright test --debug        # 调试模式(Inspector)

CLI 过滤、项目选择、报告查看与快照更新等见官方 CLI/报告/快照文档。


常用相关 API 速查表(Playwright Test)

1、测试与分组

API 作用
test(name, fn) 定义测试
test.describe(title, fn) 分组
test.describe.configure({ mode }) parallel / serial
test.beforeAll/afterAll 组级前后置
test.beforeEach/afterEach 用例前后置
test.skip/only/fixme/slow/fail 标记/控制用例

2、夹具与配置

API 作用
test.extend<Fixtures>(defs) 扩展自定义 fixtures
test.use(options) 测试级覆盖 use 选项(如 storageStateviewportlocale 等)
defineConfig({...}) 配置文件入口(testDirretriesworkersreporter…)
projects 多项目/多浏览器/不同配置运行
use: { baseURL, trace, video, screenshot, viewport, locale, timezoneId, storageState } 运行时环境与采集策略

fixtures / use / 配置项与 projects 详见官方 "Fixtures / Use options / Configuration / Projects" 文档。(Playwright)

3、断言(expect)

API 作用
expect(value).toBe / toEqual / toContain ... 通用断言
expect(locator).toHaveText/Value/Attribute Web 特有断言(带智能等待)
expect(locator).toBeVisible/Hidden/Enabled/Disabled/Checked 状态断言
expect(page).toHaveTitle/URL 页面断言
`expect(page/locator).toHaveScreenshot([name options])` 视觉快照对比(推荐)

断言与视觉快照的推荐做法详见 Assertions 与 Snapshots 文档。(Playwright)

4、步骤与附件

API 作用
test.step(name, fn) 将测试分解为步骤(报告可视化)
`test.info().attach(name, { path body, contentType })` 给测试或步骤附加文件/数据(报告中展示)

附件也可作用于步骤级(参见 TestStep / TestStepInfo)。(Playwright)

5、报告与 CLI

命令/配置 作用
reporter: [['html', { outputFolder }], ['list']] 报告配置
npx playwright test --project="Chromium" 选择项目运行
-g "keyword" / --grep / --grep-invert 过滤用例
--update-snapshots 更新视觉基线
npx playwright show-report 打开报告

完整 CLI 与 Reporter 选项见官方文档。(Playwright)