Playwright Test 测试夹具(Fixtures)完全指南:从内置夹具到 Worker 作用域与执行顺序

Playwright Test 测试夹具(Fixtures)完全指南:从内置夹具到 Worker 作用域与执行顺序 Playwright Test 测试夹具Fixtures完全指南从内置夹具到 Worker 作用域与执行顺序【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwrightPlaywright Test 以测试夹具Test Fixtures作为其测试环境的基石每个测试只拿到它需要的东西、用完即回收测试之间完全隔离。本篇基于 Playwright 仓库中的官方文档 test-fixtures-js.md 展开完整覆盖内置夹具、test.extend自定义夹具、夹具覆盖Override、Worker 作用域夹具、自动夹具、夹具超时、选项夹具options、执行顺序、多模块夹具合并mergeTests等全部机制并结合 packages/playwright/src/common/fixtures.ts 与 packages/playwright/src/worker/fixtureRunner.ts 的源码实现解释按需创建、依赖排序、setup/teardown 生命周期背后的真实逻辑帮助你写出可复用、类型安全、结构清晰的 Playwright 测试。一、核心概念为什么 Playwright Test 基于夹具官方文档对夹具的定义可以概括为三点夹具为每个测试建立运行环境给测试它需要的一切且不多给夹具在测试之间是隔离的test-scoped 夹具每个测试独立 setup/teardown有了夹具你可以按测试的含义而不是按它们的公共 setup来组织测试不再需要用describe包裹一层环境初始化层。1.1 内置夹具写第一个测试时其实就已经在用夹具了import { test, expect } from playwright/test; test(basic test, async ({ page }) { await page.goto(https://playwright.dev/); await expect(page).toHaveTitle(/Playwright/); });{ page }这个参数就是告诉 Playwright Test为本次运行准备page夹具并注入给测试函数。文档中列出的最常用的内置夹具如下夹具类型说明pagePage本次测试运行隔离的页面。contextBrowserContext本次测试运行隔离的上下文page就属于这个上下文。上下文可通过配置定制见 test-configuration-js.md。browserBrowser浏览器实例在多个测试之间共享以优化资源。browserNamestring当前运行测试的浏览器名chromium、firefox或webkit。requestAPIRequestContext本次测试运行隔离的 APIRequestContext 实例用于纯 HTTP API 测试。从源码结构看浏览器共享、上下文隔离的设计在夹具注册表里得到了印证packages/playwright/src/common/fixtures.ts 中定义了作用域顺序const kScopeOrder: FixtureScope[] [test, worker]——browser是worker作用域夹具一个 worker 进程内只启动一次而page/context是test作用域夹具每个测试一套。这也是官方文档Execution order一节中browserteardown 只在 worker 结束时执行一次的底层原因。1.2 夹具对比传统 before/after hooks文档用一个 Todo 应用示例对比了两种风格。假设你有一个遵循页面对象模型POM的TodoPage类import type { Page, Locator } from playwright/test; export class TodoPage { private readonly inputBox: Locator; private readonly todoItems: Locator; constructor(public readonly page: Page) { this.inputBox this.page.locator(input.new-todo); this.todoItems this.page.getByTestId(todo-item); } async goto() { await this.page.goto(https://demo.playwright.dev/todomvc/); } async addToDo(text: string) { await this.inputBox.fill(text); await this.inputInputBox?.press?.(Enter) ?? this.inputBox.press(Enter); } async remove(text: string) { const todo this.todoItems.filter({ hasText: text }); await todo.hover(); await todo.getByLabel(Delete).click(); } async removeAll() { while ((await this.todoItems.count()) 0) { await this.todoItems.first().hover(); await this.todoItems.getByLabel(Delete).first().click(); } } }风格一不用夹具靠 hooks 模块级变量const { test } require(playwright/test); const { TodoPage } require(./todo-page); test.describe(todo tests, () { let todoPage; test.beforeEach(async ({ page }) { todoPage new TodoPage(page); await todoPage.goto(); await todoPage.addToDo(item1); await todoPage.addToDo(item2); }); test.afterEach(async () { await todoPage.removeAll(); }); test(should add an item, async () { await todoPage.addToDo(my item); // ... }); test(should remove an item, async () { await todoPage.remove(item1); // ... }); });风格二把 setup/teardown 封装进夹具import { test as base } from playwright/test; import { TodoPage } from ./todo-page; // 通过提供 todoPage 夹具扩展基础 test。 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); // use() 之前是 setup之后是 teardown await todoPage.removeAll(); }, }); test(should add an item, async ({ todoPage }) { await todoPage.addToDo(my item); // ... }); test(should remove an item, async ({ todoPage }) { await todoPage.remove(item1); // ... });文档总结了夹具相对 hooks 的六大优势封装encapsulatesetup 与 teardown 写在同一处。如果你有一个销毁 before 钩子创建的资源的 after 钩子应考虑改写成夹具可复用reusable跨测试文件复用定义一次处处可用——内置的page就是这么工作的。某个辅助函数被多个测试使用时考虑把它变成夹具按需on-demand可以定义任意多夹具但只有测试真正用到的才会被 setup可组合composable夹具之间可以互相依赖组合出复杂行为灵活flexible测试可以按需选择任意夹具组合精确定制环境且互不影响简化分组grouping不再需要用设置环境的describe包裹测试可以纯粹按语义分组。按需这一点在源码中体现得很直接packages/playwright/src/common/fixtures.ts 中未显式声明的夹具默认auto: false, scope: test非 auto 夹具只有当测试或钩子声明依赖它时才被创建仓库自带的测试 tests/playwright-test/fixtures.spec.ts 中对 worker 夹具、auto 夹具、选项夹具都有大量断言用例验证了这些行为。二、创建与使用自定义夹具2.1 创建夹具test.extend使用Test.extend创建一个包含自定义夹具的新test对象。文档示例同时定义了两个 POM 夹具todoPage与settingsPageSettingsPage结构与TodoPage类似这里从略import { test as base } from playwright/test; import { TodoPage } from ./todo-page; import { SettingsPage } from ./settings-page; // 声明夹具的类型。 type MyFixtures { todoPage: TodoPage; settingsPage: SettingsPage; }; // 扩展 base test提供 todoPage 与 settingsPage。 // 新的 test 可以在多个测试文件中导入使用。 export const test base.extendMyFixtures({ todoPage: async ({ page }, use) { // Set up the fixture. const todoPage new TodoPage(page); await todoPage.goto(); await todoPage.addToDo(item1); await todoPage.addToDo(item2); // 测试函数中使用的夹具值由 use() 传递。 await use(todoPage); // Clean up the fixture. await todoPage.removeAll(); }, settingsPage: async ({ page }, use) { await use(new SettingsPage(page)); }, }); export { expect } from playwright/test;官方文档特别强调自定义夹具名必须以字母或下划线开头只能包含字母、数字和下划线。这个约束来自依赖解析机制——从源码看fixtureParameterNames() 是通过解析夹具函数源码的参数解构模式第一个参数必须是{ foo, bar }形式的花括号对象解构来提取依赖名的因此参数名必须是合法的标识符。同时该函数还会拒绝 rest 属性...props要求你显式列出用到的所有夹具——这保证了依赖关系可以被静态分析、排序与校验。2.2 使用夹具在测试函数参数中提到夹具名即可测试运行器会自动完成创建夹具同样可以在钩子和其他夹具中引用若使用 TypeScript夹具是类型安全的。import { test, expect } from ./my-test; test.beforeEach(async ({ settingsPage }) { await settingsPage.switchToDarkMode(); }); test(basic test, async ({ todoPage, page }) { await todoPage.addToDo(something nice); await expect(page.getByTestId(todo-title)).toContainText([something nice]); });三、覆盖Override内置夹具除了新增夹具你还可以覆盖已有夹具。文档给出的第一个例子是覆盖page让每个测试自动导航到baseURLimport { test as base } from playwright/test; export const test base.extend({ page: async ({ baseURL, page }, use) { await page.goto(baseURL); await use(page); }, });注意这里page夹具依赖了另一个内置夹具baseURLTestOptions.baseURL。baseURL既可以在配置文件中设置也可以用Test.use在测试文件内局部覆盖test.use({ baseURL: https://playwright.dev });第二种覆盖方式是完全替换基础夹具的实现。例如覆盖storageState夹具、由代码动态生成认证数据import { test as base } from playwright/test; export const test base.extend({ storageState: async ({}, use) { const cookie await getAuthCookie(); await use({ cookies: [cookie] }); }, });覆盖机制的源码依据在 FixtureRegistration每次注册都会保留super指针指向上一版本夹具A fixture override can use the previous version of the fixture并且resolve()支持通过同名字面量取回上一版实现resolve() 中if (name forFixture?.name) return forFixture.super——这正是在覆盖版page夹具中把参数名仍写作page却能拿到原始 page的原因。此外注册表还会校验同一夹具重复注册时scope、auto、option属性必须与已有定义一致否则产生加载期错误见 _appendFixtureList。四、Worker 作用域夹具Playwright Test 用 worker 进程 运行测试文件。测试夹具为单个测试服务而worker 夹具为每个 worker 进程服务一次——启动服务、拉起本地服务器、创建数据库账号等昂贵资源都应放在 worker 夹具中。文档强调只要各文件所需的 worker 夹具集合匹配即环境完全一致Playwright Test 会尽可能复用同一个 worker 进程处理多个测试文件。仓库源码同样印证了worker 夹具决定进程复用这一点FixturePool 的validate()方法会对所有scope worker的夹具注册 id 计算 SHA-1 摘要digest该摘要即用于把测试文件分组到可复用的 worker 池中。文档示例创建一个在 worker 内共享的account夹具并覆盖page夹具让每个测试自动登录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) { // 唯一的用户名使用任何测试/夹具都可用的 WorkerInfo.workerIndex。 const username user workerInfo.workerIndex; const password verysecure; // 用 Playwright 创建账号。 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 }], page: async ({ page, account }, use) { // 用该账号登录。 const { username, password } account; await page.goto(/signin); await page.getByLabel(User Name).fill(username); await page.getByLabel(Password).fill(password); await page.getByText(Sign in).click(); await expect(page.getByTestId(userinfo)).toHaveText(username); // 测试中使用已登录页面。 await use(page); }, }); export { expect } from playwright/test;关键细节worker 夹具使用元组语法[fn, { scope: worker }]必须显式传入{ scope: worker }才会每 worker 只 setup 一次与 test 作用域夹具不同每个 worker 作用域夹具拥有独立的超时默认等于默认测试超时可通过timeout选项单独调整见第六节注意test 夹具不能依赖 worker 夹具、worker 夹具也不能依赖 test 夹具的层级约束在源码中被强制fixtures.ts 里按kScopeOrder比较两个夹具的作用域若依赖方作用域更内层则直接报加载错误cannot depend on a ... fixture。仓库测试 tests/playwright-test/fixtures.spec.ts 中有大量形如[ async ({}, test) ..., { scope: worker } ]的用例如 L294-L316 验证多个测试文件对 worker 夹具的复用可直接作为行为验证参考。五、自动夹具auto fixtures自动夹具即使测试没有显式声明也会为每个测试/worker 创建。创建方式同样是元组语法 { auto: true }。文档示例测试失败时自动收集调试日志并附加到报告注意它利用了每个测试/夹具都可用的TestInfo对象获取测试元数据import debug from debug; import fs from fs; import { test as base } from playwright/test; export const test base.extend{ saveLogs: void }({ saveLogs: [async ({}, use, testInfo) { // 测试期间收集日志。 const logs []; debug.log (...args) logs.push(args.map(String).join()); debug.enable(myserver); await use(); // 测试后可检查测试是通过还是失败。 if (testInfo.status ! testInfo.expectedStatus) { // outputPath() API 保证文件名唯一。 const logFile testInfo.outputPath(logs.txt); await fs.promises.writeFile(logFile, logs.join(\n), utf8); testInfo.attachments.push({ name: logs, contentType: text/plain, path: logFile }); } }, { auto: true }], }); export { expect } from playwright/test;从源码看auto 的语义在 FixturePool.autoFixtures() 中体现凡是auto ! false的注册都会进入自动夹具集合由 worker 主循环在每个测试或 worker开始/结束时统一 setup/teardown不需要测试函数声明。六、夹具超时Fixture timeout夹具被视为测试的一部分其 setup/teardown 耗时计入测试超时慢夹具会拖垮测试超时预算。可以给夹具单独设置更大的超时同时保持测试整体超时较小import { test as base, expect } from playwright/test; const test base.extend{ slowFixture: string }({ slowFixture: [async ({}, use) { // ... perform a slow operation ... await use(hello); }, { timeout: 60000 }] }); test(example test, async ({ slowFixture }) { // ... });worker 作用域夹具则各自拥有独立超时默认等于测试超时修改方式相同。源码层面packages/playwright/src/worker/fixtureRunner.ts 在构造夹具描述时若注册项显式带了timeout就用它建立独立的超时槽位否则 worker 作用域夹具使用runner.workerFixtureTimeout而带独立超时的夹具耗时不计入测试时长fixtures.ts L45-L46 的注释Fixture with a separate timeout does not count towards the test time。七、选项夹具Fixture options声明式、类型安全的配置Playwright Test 支持多个可独立配置的测试项目projects。option 夹具可以把你自定义的配置项变成声明式且类型安全的选项配合参数化测试使用。示例在todoPage夹具之外再定义一个defaultItem选项默认值Something nice可在配置文件中按项目覆盖。注意元组语法和{ option: true }import { test as base } from playwright/test; import { TodoPage } from ./todo-page; // 声明选项用于对配置做类型检查。 export type MyOptions { defaultItem: string; }; type MyFixtures { todoPage: TodoPage; }; // 同时指定 option 与 fixture 类型。 export const test base.extendMyOptions MyFixtures({ // 定义选项并给出默认值之后可以在 config 中覆盖。 defaultItem: [Something nice, { option: true }], // todoPage 夹具依赖该选项。 todoPage: async ({ page, defaultItem }, use) { const todoPage new TodoPage(page); await todoPage.goto(); await todoPage.addToDo(defaultItem); await use(todoPage); await todoPage.removeAll(); }, }); export { expect } from playwright/test;然后在配置文件中按项目设置该选项import { defineConfig } from playwright/test; import type { MyOptions } from ./my-test; export default defineConfigMyOptions({ projects: [ { name: shopping, use: { defaultItem: Buy milk }, }, { name: wellbeing, use: { defaultItem: Exercise! }, }, ] });源码中选项覆盖的合法性由 FixturePool 构造函数 强制只有以{ option: true }注册的夹具才允许出现在配置文件的use段否则报cannot be overridden in the configuration use section。同时 isFixtureOption() 会沿super链向上追溯保证即使选项是被覆盖过的新注册只要原始定义是 option 即可被配置覆盖。7.1 选项值是数组时的包裹写法如果选项值本身是数组例如[{ name: Alice }, { name: Bob }]由于元组语法与数组值形式上冲突需要再包一层数组并显式给 scopetype Person { name: string }; const test base.extend{ persons: Person[] }({ // 声明选项默认值为空数组。 persons: [[], { option: true }], }); // 选项值是人员数组。 const actualPersons [{ name: Alice }, { name: Bob }]; test.use({ // 正确把值再包一层数组并指定 scope。 persons: [actualPersons, { scope: test }], }); test.use({ // 错误直接传数组值不会生效。 persons: actualPersons, });判断依据同样在源码isFixtureTuple() 把形如[value, {选项对象}]的数组整体识别为元组所以裸数组值会被误判。7.2 重置选项Reset an option把选项设为undefined可以把它重置回配置文件或原始声明中的值。例如配置定义了baseURLimport { defineConfig } from playwright/test; export default defineConfig({ use: { baseURL: https://playwright.dev, }, });文件级覆盖、以及单个测试组内退回配置值import { test } from playwright/test; // 为本文件配置 baseURL。 test.use({ baseURL: https://playwright.dev/docs/intro }); test(check intro contents, async ({ page }) { // 该测试使用上面定义的 https://playwright.dev/docs/intro。 }); test.describe(() { // 重置为配置文件中定义的值。 test.use({ baseURL: undefined }); test(can navigate to intro from the home page, async ({ page }) { // 该测试使用配置中定义的 https://playwright.dev。 }); });如果想彻底把值置为undefined而不只是回退到配置值要用长格式夹具写法import { test } from playwright/test; // 彻底取消本文件的 baseURL。 test.use({ baseURL: [async ({}, use) use(undefined), { scope: test }], }); test(no base url, async ({ page }) { // 该测试将没有 base url。 });这与源码行为一一对应_appendFixtureList 中注释明确写着Overriding option with undefined value means setting it to the default value from the config or from the original declaration of the option——当覆盖值为undefined且原夹具是 option 时实现会沿super链找到最原始声明的fn直接复用。八、执行顺序Execution order夹具的执行遵循三条规则依赖优先若夹具 A 依赖夹具 BB 一定先于 A setup、晚于 A teardown洋葱式惰性非 auto 夹具只有当测试/钩子需要时才执行作用域决定生命周期test 作用域夹具在每个测试之后 teardownworker 作用域夹具只在执行测试的 worker 进程销毁时 teardown。文档给出了一个覆盖全部场景的示例worker/test 夹具 × 普通/autoimport { test as base } from playwright/test; const test base.extend{ testFixture: string, autoTestFixture: string, unusedFixture: string, }, { workerFixture: string, autoWorkerFixture: string, }({ workerFixture: [async ({ browser }) { // workerFixture setup... await use(workerFixture); // workerFixture teardown... }, { scope: worker }], autoWorkerFixture: [async ({ browser }) { // autoWorkerFixture setup... await use(autoWorkerFixture); // autoWorkerFixture teardown... }, { scope: worker, auto: true }], testFixture: [async ({ page, workerFixture }) { // testFixture setup... await use(testFixture); // testFixture teardown... }, { scope: test }], autoTestFixture: [async () { // autoTestFixture setup... await use(autoTestFixture); // autoTestFixture teardown... }, { scope: test, auto: true }], unusedFixture: [async ({ page }) { // unusedFixture setup... await use(unusedFixture); // unusedFixture teardown... }, { scope: test }], }); test.beforeAll(async () { /* ... */ }); test.beforeEach(async ({ page }) { /* ... */ }); test(first test, async ({ page }) { /* ... */ }); test(second test, async ({ testFixture }) { /* ... */ }); test.afterEach(async () { /* ... */ }); test.afterAll(async () { /* ... */ });在所有测试通过、无异常的理想情况下执行顺序为worker setup 与 beforeAll 段因autoWorkerFixture需要而 setupbrowserauto 的 worker 夹具总会最先 setup故 setupautoWorkerFixture运行beforeAll。first test 段setupautoTestFixtureauto test 夹具总先于测试与 beforeEach 钩子setuppagebeforeEach 钩子需要它运行beforeEach→first test→afterEach测试结束后 teardownpage与autoTestFixturetest 作用域逐测试回收。second test 段再次 setupautoTestFixture与page运行beforeEachsetupworkerFixture被testFixture间接需要惰性触发setuptestFixture运行second test再运行afterEach依次 teardowntestFixture、page、autoTestFixture。afterAll 与 worker teardown 段运行afterAllteardownworkerFixture、autoWorkerFixture、browserworker 作用域只回收一次。文档总结的几条观察值得记住page与autoTestFixture每个测试都 setup/teardown 一次test 作用域unusedFixture从未被任何测试/钩子使用永远不会被 setup惰性原则testFixture依赖workerFixture并触发其 setup但workerFixture直到 worker 关闭才 teardownautoWorkerFixture为beforeAll钩子提前 setup而autoTestFixture不会auto worker 夹具在 beforeAll 之前就绪auto test 夹具则按测试粒度生效。这套洋葱式 依赖图的行为由 packages/playwright/src/worker/fixtureRunner.ts 实现use()在源码中是一个ManualPromise门闩useFuncuse(value)调用前所有代码是 setup、之后是 teardown且不能第二次提供夹具值_setupInternal会递归解析依赖并建立_deps/_usages引用关系teardown 时root → leaves地先销毁使用者再销毁被依赖者。九、合并多模块夹具mergeTests当夹具分别定义在多个模块例如数据库工具包、无障碍工具包时可以用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);import { test } from ./fixtures; test(passes, async ({ database, page, a11y }) { // 使用 database 与 a11y 夹具。 });实现上mergeTests() 接收若干test函数参数如果你误把 fixtures 对象传给test.extend()运行器会明确提示Did you mean to call mergeTests()?见 testType.ts L312两个 API 的职责边界非常清晰extend用于扩展并定义mergeTests用于合并多个已扩展的 test。十、报告降噪与展示Box 夹具、自定义标题10.1 Box fixtures通常自定义夹具会在 UI mode、Trace Viewer 和各类测试报告中以独立步骤呈现并出现在运行器错误信息里。对高频使用的辅助夹具而言这可能意味着大量噪音。用box: true可以把该夹具的步骤从报告中装箱隐藏import { test as base } from playwright/test; export const test base.extend({ helperFixture: [async ({}, use, testInfo) { // ... }, { box: true }], });它特别适合不感兴趣的辅助夹具——例如一个自动automatic初始化公共数据的夹具可以安全地隐藏起来。box: self则只隐藏该夹具自身夹具内部的所有步骤仍然出现在报告中。从源码看fixtureRunner.ts 中box self时直接不创建步骤_stepInfo undefined而box为真非 self时步骤被归入configuration/internal分组在报告中折叠展示。另外 validate() 会先校验非 box 夹具再校验 box 夹具保证错误信息优先来自用户可见的夹具。10.2 Custom fixture title不想用默认的夹具名作为展示标题时可以用title自定义报告显示与错误信息中都会用这个标题import { test as base } from playwright/test; export const test base.extend({ innerFixture: [async ({}, use, testInfo) { // ... }, { title: my fixture }], });源码中customTitle会直接参与步骤标题fixtureRunner.ts L48-L50 的title this.registration.customTitle || this.registration.name。综合来看官方类型定义types/test.d.ts给出的元组选项全集为{ auto?, scope?, option?, timeout?, title?, box? }与 FixtureOptions 一一对应。十一、用自动夹具实现全局 beforeEach/afterEach 与 beforeAll/afterAll11.1 全局 beforeEach/afterEachTest.beforeEach/Test.afterEach只作用于同一文件、同一 describe 块内声明的测试。想全局生效可以声明 auto 夹具import { test as base } from playwright/test; export const test base.extend{ forEachTest: void }({ forEachTest: [async ({ page }, use) { // 这段代码在每个测试之前运行。 await page.goto(http://localhost:8000); await use(); // 这段代码在每个测试之后运行。 console.log(Last URL:, page.url()); }, { auto: true }], // 自动为每个测试启动。 });然后在所有测试中导入该夹具import { test } from ./fixtures; import { expect } from playwright/test; test(basic, async ({ page }) { expect(page).toHaveURL(http://localhost:8000); await page.goto(https://playwright.dev); });11.2 全局 beforeAll/afterAll同理Test.beforeAll/Test.afterAll只作用于同文件同 describe 块、每个 worker 进程执行一次。想每个 worker 的全局前后钩子声明{ scope: worker, auto: true }的 auto 夹具import { test as base } from playwright/test; export const test base.extend{}, { forEachWorker: void }({ forEachWorker: [async ({}, use) { // 这段代码在该 worker 进程的所有测试之前运行一次。 console.log(Starting test worker ${test.info().workerIndex}); await use(); // 这段代码在该 worker 进程的所有测试之后运行一次。 console.log(Stopping test worker ${test.info().workerIndex}); }, { scope: worker, auto: true }], // 自动为每个 worker 启动。 });测试文件中直接导入即可import { test } from ./fixtures; import { expect } from playwright/test; test(basic, async ({ }) { // ... });注意这类夹具依然每个 worker 进程只运行一次见 test-parallel-js.md 的 worker 进程章节你不需要在每个文件里重复声明——auto worker 夹具随 worker 生命周期自动生效。十二、速查夹具元组选项与验证路径选项默认说明scopetesttest逐测试创建worker逐 worker 进程创建worker 夹具集合还决定 worker 进程能否跨文件复用autofalsetrue时不声明也自动 setup/teardownoptionfalsetrue时该夹具成为可在配置文件use段设置的声明式选项timeout无计入测试超时为 setup/teardown 建立独立超时槽位不占用测试超时预算title夹具名报告中展示的自定义标题box无true折叠整个夹具步骤self隐藏夹具自身但保留内部步骤行为验证入口仓库自带测试tests/playwright-test/fixtures.spec.tsworker 夹具复用、auto 夹具、选项夹具等核心行为tests/playwright-test/test-extend.spec.tsextend链式派生、scope: worker选项、worker 夹具覆盖等tests/playwright-test/fixture-errors.spec.ts夹具参数校验如首参必须是对象解构、未知参数报错等实现层注册与校验 packages/playwright/src/common/fixtures.ts、setup/use/teardown 执行 packages/playwright/src/worker/fixtureRunner.ts、夹具类型定义 packages/playwright/types/test.d.ts。小结Playwright Test 的夹具机制可以概括为一句话用base.extend声明环境如何构造用元组选项scope/auto/option/timeout/box/title控制何时构造、为谁构造、如何呈现用use()划分 setup 与 teardown 的边界。它相对传统 hooks 的收益——封装、复用、按需、可组合、灵活、简化分组——都建立在源码中可验证的机制之上静态解析参数解构得到依赖图fixtureParameterNames、worker 夹具 digest 决定进程复用validate()、super链支撑覆盖语义resolve()、ManualPromise门闩支撑use()生命周期fixtureRunner.ts。掌握了这些你就能把登录态、数据库账号、服务端点、全局日志等一切测试环境都纳入统一、类型安全且可组合的夹具体系。【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考