Playwright Test 中的 Fixtures:按测试需要组装环境
原文:Microsoft Playwright 项目文档,Fixtures。这是一个随仓库 main 分支更新的 JavaScript/TypeScript 指南;页面当时对应的源文件共 774 行。本文保留其主要论述与示例的技术含义,并将重复的 TodoPage 示例整合到一处。核对日期:2026-10-05。
安全与版本说明:示例使用 Playwright Test API。文中某些演示值(如密码、测试网址)是教学占位数据,不应直接作为真实账户凭证。Playwright 版本、测试并行模型和 reporter 行为可能随主分支变动,项目应固定并核对实际安装版本。本文未运行这些示例。

Fixtures 是什么
Playwright Test 把每个测试所需的环境封装成 fixture。测试只接收它需要的部分;fixture 在测试之间隔离,并把初始化与清理放在一起。这样测试可以按业务含义分组,而不必依赖一组散落的公共 setup/teardown。
常用内置 fixture 包括:
page:本次测试运行隔离的页面,属于它的 BrowserContext。context:本次测试的隔离浏览器上下文。browser:多个测试共享以节省资源的浏览器实例。browserName:当前运行的浏览器名,如 chromium、firefox、webkit。request:隔离的 APIRequestContext。
在测试函数参数中写 { page },运行器就会创建并注入页面 fixture:
import { test, expect } from '@playwright/test';
test('基本页面', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
把钩子改造成可复用的 fixture
传统测试常在 beforeEach 中初始化 TodoPage,并在 afterEach 中删除测试数据。它能运行,但 setup 与 teardown 分散,且所有同一 describe 的测试都要共享那套环境。fixture 则把两者封装在一个定义中,在 await use(value) 前准备资源,把资源交给测试,等待测试完成,再执行清理:
export const test = base.extend<{ todoPage: TodoPage }>({
todoPage: async ({ page }, use) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
await use(todoPage);
await todoPage.removeAll();
},
});
test('添加待办项', async ({ todoPage }) => {
await todoPage.addToDo('一个新任务');
});
fixtures 可在多个测试文件复用、可组合依赖,也可按每个测试所需组合,而不影响别的测试。无需用 describe 专门承载环境准备,测试分组可以关注行为本身。
如果从零创建,先写含页面操作的 page object,再用 base.extend 建立新 test 对象。TypeScript 可声明 fixture 类型,让注入对象有类型检查;名称必须以字母或下划线开头,后续只能包含字母、数字和下划线。使用方式与内置 fixture 一样:放入测试函数、hook 或另一 fixture 的解构参数中。
替换或覆盖既有 fixture
扩展 test 时可以重新定义已有 fixture。例如覆盖 page,让每个依赖该 fixture 的测试先导航到配置中的 baseURL:
export const test = base.extend({
page: async ({ baseURL, page }, use) => {
await page.goto(baseURL);
await use(page);
},
});
test.use({ baseURL: 'https://playwright.dev' });
如果新定义同名 fixture,它会取代基础 fixture。官方示例还演示了覆盖 storageState 并通过 getAuthCookie() 自行构造 cookie。这个示例展示 API 扩展点,不是通用身份验证推荐:真实系统应使用隔离的测试身份、限定的测试域和受保护的状态文件,避免把生产 cookie、个人登录态或凭证写入源码、日志或构建产物。
Worker 作用域与隔离的测试账户
默认 fixture 为单个测试初始化;worker-scoped fixture 则在每个测试工作进程中建立一次,供该 worker 运行的多个测试文件使用。这样适合启动每个 worker 共用的服务或准备可复用资源。运行器会在 worker 环境一致时复用进程。worker fixture 有独立于单测 fixture 的超时;默认值等于测试超时,可单独配置。
文档通过一个 account fixture 演示每个 worker 创建一个账户,并覆盖 page 以便各测试分别登录。示例用 workerInfo.workerIndex 生成用户名。原例提醒读者清理,但没有给出删除账户的代码;实际测试应在 fixture teardown 中补上项目自己的清理逻辑:
account: [async ({ browser }, use, workerInfo) => {
const username = 'test-user-' + workerInfo.workerIndex;
const password = process.env.TEST_ACCOUNT_PASSWORD;
if (!password) throw new Error('TEST_ACCOUNT_PASSWORD is required');
const page = await browser.newPage();
await page.goto('/signup');
await page.getByLabel('User Name').fill(username);
await page.getByLabel('Password').fill(password);
await page.getByText('Sign up').click();
await expect(page.getByTestId('result')).toHaveText('Success');
await page.close();
await use({ username, password });
// 此处补入测试环境的账户清理逻辑;原例未提供具体实现
}, { scope: 'worker' }]
本稿与原例的安全差异:原文演示密码字面量为 verysecure。此处改成从环境变量读取,避免读者把演示密码误带进真实仓库;环境变量中也应使用短期、最小权限的测试凭证,并通过安全的 CI secret 管理注入。workerIndex 可区分单个运行中的 worker,但分布式 shard、并行 CI 任务或历史遗留账户可能仍有命名碰撞,需要为运行批次加随机/唯一标识并保证清理失败可发现。本文没有连接站点或创建账户。
自动执行的 fixture 与专用超时
普通 fixture 仅在测试或 hook 声明依赖时才会初始化;自动 fixture 会为每个测试或 worker 运行,即使没有显式写在参数中。使用 tuple 形式并设置 { auto: true }。原文用自动 fixture 收集 debug 日志:测试结束后若失败,把日志写入 testInfo.outputPath('logs.txt'),再将文件登记为附件。outputPath 为该测试提供唯一输出路径。
附件可能包含 token、个人信息、URL query、请求正文或测试账户数据。上传 CI reporter 前应脱敏并限制可读权限;不要因路径唯一就默认日志适合公开保存。原文示例覆写全局 debug.log,真实项目还应保证在并发测试中不会互相覆盖日志收集器。
fixture 的 setup 与 teardown 时间计入测试超时;较慢资源可给 fixture 单独设置 timeout,而不用整体放宽所有测试的超时。worker fixture 同样支持独立配置。
将配置写成类型安全的 option fixture
测试项目有时需要每个 project 采用不同配置值。可以声明 option fixture,并用 { option: true } 标记,在配置文件按项目覆盖。例如 TodoPage 的 defaultItem 可以由 “shopping” 项目设置成“Buy milk”,由 “wellbeing” 项目设置成“Exercise!”;TodoPage fixture 依赖该选项后,就用配置值准备页面。
如果 option 的值本身是数组,传入时要再包一层 tuple,并提供 scope 选项,避免运行器把数组误解成 fixture 语法。例如数组值 actualPersons 要写成 [actualPersons, { scope: 'test' }]。可把某项重置为配置文件中的默认值:test.use({ baseURL: undefined });若需要把值彻底设为 undefined,则用长形式的 fixture 返回 undefined。
Fixture 执行顺序
fixture 有 setup 与 teardown 两段:前半在 await use() 前执行,后半在其后执行。依赖项先 setup、后 teardown。自动 fixture 会先于测试及 hook 初始化;普通 fixture 懒加载,只在被引用时启动;test-scoped fixture 在每个测试后销毁,worker-scoped fixture 在 worker 进程结束时销毁。
文档的执行顺序例展示了:若 beforeAll 需要浏览器,则自动 worker fixture 与 browser 会先准备;每个测试开始时,自动 test fixture 在 beforeEach 前执行;hook 中所需的 page 随后创建;测试结束后先运行 afterEach,再按依赖逆序销毁页面与 test fixture。某个测试需要依赖 worker fixture 时,它会在该测试前延迟创建,但到 worker 结束才销毁。未被任何测试或 hook 使用的普通 fixture 不会初始化;自动 worker fixture 会为 beforeAll 建立,而自动 test fixture 不会因为 beforeAll 就提前运行。
组合多个模块的 fixture
如果数据库工具和无障碍工具各自导出一套 test fixtures,可以使用 mergeTests(dbTest, a11yTest) 合并;测试随后能从统一 test 对象中请求 database、page、a11y 等 fixture。
减少报告中的 fixture 噪声
默认情况下,fixture 会作为独立步骤出现在 UI mode、Trace Viewer、测试报告和错误信息里。对于经常复用、但内部细节不重要的辅助 fixture,可设置 { box: true } 把它折叠/隐藏,让报告更聚焦。box: 'self' 只隐藏当前 fixture 步骤,保留内部步骤,便于在需要时仍能查看细节。
还可以用 title 属性给 fixture 起一个自定义标题,用更贴合测试含义的名字显示在报告和错误信息中。
用自动 fixture 全局注册 hook
每个文件、每个 describe block 都声明的 beforeEach / afterEach hook 只覆盖对应作用域。如果要为导入自定义 fixtures 的所有测试运行全局每测 hook,可注册自动 test-scoped fixture:先执行公共准备,调用 await use(),再执行收尾。例如文档演示在每次测试前打开本地服务地址,在测试结束后输出最后访问的 URL。
类似地,要在每个 worker 的所有测试文件开始前和结束后执行逻辑,可注册自动的 worker-scoped fixture:在 use() 前记录 worker 启动,之后记录关闭。使用该模块的测试可在自己的测试文件导入这个 test 对象,无须逐个重声明 worker hooks。
静态安全审查:示例中存在教学用硬编码密码,并有可能将调试日志附到报告的代码;本文已改用安全占位读取方式并补充凭证、日志脱敏提醒。没有发现外部攻击输入被拼入 shell/HTML 的示例。所有 JS/TS 仅作为文档代码阅读,没有运行。
本文覆盖 Fixtures 原文件的核心正文主题与各执行模式:内置、创建、使用、覆盖、worker、自动运行、超时、选项、顺序、合并、boxing、自定义标题和全局 hook。为控制重复,多个 TodoPage/配置代码块整合为同一组代表性示例,原文中的重复 HTML/UI 图片以文字说明替代。来源为 main 分支,未固定 commit;上线前应按 lockfile 中的 Playwright 版本复核。
来源与许可:Microsoft Playwright 项目贡献者,《Fixtures》,源文件(main 分支,2026-10-05 核对)。Playwright 仓库采用 Apache License 2.0;随稿保留版 LICENSE 与 NOTICE;本译编保留原作者和来源,注明改写:整合重复示例、将硬编码教学密码改为环境变量,并补充安全边界。本文译文及原创配图依据另行取得的授权使用。原项目 NOTICE 保留 Playwright 的 Microsoft 版权归属及相关第三方来源说明。示例仅作静态阅读,未运行。











暂无评论内容