首页 / Playwright 入门教程 / 自定义夹具与组合复用

Playwright 入门教程

自定义夹具与组合复用

本教程共 59 篇 · 第 39 篇 · 更新于 2026-08-04 · 约 11 分钟阅读

Playwright自定义夹具test.extend组合覆盖跨文件复用

39. 自定义夹具与组合复用

本节目标:学完能用 test.extend 造自己的夹具,把它抽到独立文件跨测试复用,并学会覆盖内置夹具来满足特殊需求。

内置夹具够用,但写业务测试时,你会反复需要「一个已经登录的页面」「一个装好测试数据的环境」。这种重复准备,正是自定义夹具的用武之地。

用 test.extend 造夹具

核心方法是 test.extend(),它返回一个「升级版」的 test,带着你新增的夹具。

// my-test.ts
import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';

type MyFixtures = {
  todoPage: TodoPage;
};

export const test = base.extend<MyFixtures>({
  todoPage: async ({ page }, use) => {
    const todoPage = new TodoPage(page);
    await todoPage.goto();
    await todoPage.addToDo('item1');
    await use(todoPage);        // 交给测试用
    await todoPage.removeAll();  // 用完清理
  },
});

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

之后在测试里直接声明 todoPage 就能用:

import { test, expect } from './my-test';

test('能新增一项', async ({ todoPage }) => {
  await todoPage.addToDo('something nice');
});
Note

自定义夹具名只能以字母或下划线开头,且只能含字母、数字、下划线,别用连字符。

跨文件共享

把上面的 my-test.ts 放在公共位置,谁要用就 import 谁。这样所有测试共享同一套夹具定义,改一处全局生效。

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

如果想从多个模块合并夹具,用 mergeTests

import { mergeTests } from '@playwright/test';
import { test as dbTest } from 'database-test-utils';
import { test as a11yTest } from 'a11y-test-utils';

export const test = mergeTests(dbTest, a11yTest);

依赖组合:夹具套夹具

夹具可以依赖别的夹具,Playwright 会按依赖关系自动排好初始化顺序。下面两个夹具都依赖内置的 page

import { test as base } from '@playwright/test';
import { TodoPage } from './todo-page';
import { SettingsPage } from './settings-page';

type MyFixtures = {
  todoPage: TodoPage;
  settingsPage: SettingsPage;   // 新夹具要先写进类型,否则 TS 会报错
};

export const test = base.extend<MyFixtures>({
  todoPage: async ({ page }, use) => {
    const todoPage = new TodoPage(page);
    await todoPage.goto();
    await use(todoPage);
    await todoPage.removeAll();
  },
  settingsPage: async ({ page }, use) => {
    await use(new SettingsPage(page));
  },
});
Note

每加一个自定义夹具,都要在 MyFixtures 类型里补一条。漏了 TS 就报「类型不匹配」,这是新手最常撞的一个错。

覆盖内置夹具

自定义夹具还能覆盖已有的,比如让每个测试一打开就跳到 baseURL

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

export const test = base.extend({
  page: async ({ baseURL, page }, use) => {
    await page.goto(baseURL!);
    await use(page);
  },
});

第 33 章讲认证复用时提过:也可以覆盖 storageState 夹具,直接注入自己的登录态,不用每次走登录流程。

Tip

覆盖要克制。覆盖内置夹具影响面大,只在确有全局统一需求时才动,比如「每个测试都要先登录」。

worker 级夹具

有些资源建一次就够,比如一个测试账号。用 { scope: 'worker' } 让它在一个 worker(工作进程)里只建一次:

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

type Account = { username: string; password: string };

// 第一个泛型参数是测试级夹具,第二个才是 worker 级
export const test = base.extend<{}, { account: Account }>({
  account: [async ({ browser }, use, workerInfo) => {
    const username = `user-${workerInfo.workerIndex}`;  // 每个 worker 唯一
    // 创建账号、登录...
    await use({ username, password: 'verysecure' });
  }, { scope: 'worker' }],
});
Warning

worker 级夹具的清理要写在 use() 之后。它在整个 worker 结束时才拆,别指望它在每条测试后回收。

自动夹具

auto: true,夹具对每条测试自动生效,无需声明。比如给失败测试自动存日志:

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

export const test = base.extend<{ saveLogs: void }>({
  saveLogs: [async ({}, use, testInfo) => {
    const logs: string[] = [];
    // 收集日志...
    await use();
    if (testInfo.status !== testInfo.expectedStatus) {
      // 测试失败时落盘
      await testInfo.attach('logs', { body: logs.join('\n') });
    }
  }, { auto: true }],
});

选项型夹具做参数化

夹具还能当「配置项」用。声明成 option: true,就能在配置或测试里覆盖它的值,实现同一套测试跑在不同参数下。先在夹具文件里定义默认值:

// my-test.ts
import { test as base } from '@playwright/test';

export type MyOptions = { defaultItem: string };

export const test = base.extend<MyOptions>({
  defaultItem: ['Something nice', { option: true }],  // 带默认值的可选项
});

再在配置文件里按项目给不同的值:

// playwright.config.ts
import { defineConfig } from '@playwright/test';
import type { MyOptions } from './my-test';

export default defineConfig<MyOptions>({
  projects: [
    { name: '购物', use: { defaultItem: 'Buy milk' } },
    { name: '健康', use: { defaultItem: 'Exercise!' } },
  ],
});

这种「选项夹具」是项目级参数化的底层机制,配合 projects 特别好用。第 42 章讲参数化时还会用到它。

什么时候该抽夹具

我的经验:同一段准备代码在三个以上测试里出现,就值得抽成夹具;如果还要跨文件用,就放进公共 fixtures 文件。第 40 章我们会讲更大的视角——用全局 setup/teardown 处理「整套测试之前之后」的事。

小结

自定义夹具用 test.extend() 造,核心就是接收前一个夹具、调一下 use()、交出准备好的东西。实用技巧几个:夹具依赖别的夹具能自动排顺序,scope: 'worker' 让重资源只建一次,auto: true 省略声明步骤,option: true 能做参数化。抽夹具的时机很简单——同一段准备代码出现三次,就该抽了。