尧图精选

Playwright Test 内置 Fixtures 详解:browser、context、page、request 与 mount 的使用和源码实现

🕒 发布时间:2026/9/7 4:02:03 📁 来源:尧图网络
Playwright Test 内置 Fixtures 详解browser、context、page、request 与 mount 的使用和源码实现【免费下载链接】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 的测试环境管理完全建立在「fixture夹具」机制之上每个测试声明自己需要的 fixture运行器只装配被需要的部分并在测试结束后自动拆卸。本文以官方 API 参考中的 Fixtures 类文档 为主线逐一讲解browser、browserName、context、mount、page、request六个内置 fixture 的类型、作用域与用法并结合 FixturePool 注册校验 与 FixtureRunner 执行引擎 的源码说明 fixture 的依赖解析、作用域规则与执行顺序在底层是如何保证的。1. Fixture 基本模型按需装配用完即拆Playwright Test 基于 test fixtures 概念fixture 为每个测试建立环境给测试它需要的一切而不给多余的东西giving the test everything it needs and nothing else。运行器会分析每个测试声明的参数集合只为该测试准备对应 fixture并把各 fixture 的值合并成一个对象作为test回调、hook、注解以及其他 fixture 的第一个参数传入import { test, expect } from playwright/test; test(basic test, async ({ page }) { // ... });对上面的测试Playwright Test 会在测试运行前装配pagefixture测试结束后将其拆卸pagefixture 提供的是可直接使用的 Page 对象。Playwright Test 自带一批内置 fixture下文第 28 节逐一说明同时也允许通过test.extend()扩展自己的 fixture内置的browser/context/page均可通过 TestOptions 选项在配置文件中定制。内置 fixture 一览摘自 class-fixtures.mdFixture类型作用域说明browserBrowserworker同一 worker 内所有测试共享节省资源browserNamechromium \| firefox \| webkitworker当前运行测试的浏览器名默认chromiumcontextBrowserContexttest每个测试独立的隔离上下文mount返回 Locatortest挂载组件 story自 v1.62 引入pagePagetest每个测试独立的页面从属于contextrequestAPIRequestContexttest每个测试独立的 API 请求上下文从源码看作用域是 fixture 注册对象的核心字段。FixturePool 的类型定义 中FixtureScope只有test | worker两个取值且kScopeOrder [test, worker]固定了排序每条FixtureRegistration还携带auto是否自动装配、option是否可被use配置覆盖、timeout独立超时槽位、deps依赖名列表从函数签名解析而来、super被覆盖时的上一版本等字段这些字段共同决定了后文所述的校验与执行行为。2.browserfixtureworker 级共享的浏览器实例browser提供 Browser 实例在同一 worker 内的所有测试之间共享——这让测试尽量高效避免每个测试都冷启动一个浏览器。与此同时每个测试仍然运行在独立的 BrowserContext 中因此环境依旧全新。浏览器的启动方式与可用选项见 test-configuration。典型用法是在beforeAll中拿到共享浏览器并手动开页面test.beforeAll(async ({ browser }) { const page await browser.newPage(); // ... });源码实现为什么browser是 worker 级且不计入测试超时内置 fixture 全部注册在 packages/playwright/src/index.ts 的playwrightFixtures对象中第 932 行由_utilityTest.extend(playwrightFixtures)挂到导出的test上。browser的注册是 三元组形式[fn, options]关键实现如下作用域与超时注册选项为{ scope: worker, timeout: 0 }。scope: worker决定它在每个 worker 进程里只装配一次timeout: 0表示它拥有独立的超时槽位不占用测试的默认超时否则浏览器启动的几秒会被算进测试时间。浏览器名校验fixture 函数开头即检查[chromium, firefox, webkit].includes(browserName)不合法则直接抛出Unexpected browserName错误。两种启动路径若配置了connectOptions走playwright[browserName].connect(wsEndpoint)连接远端浏览器服务否则走playwright[browserName].launch()本地启动。launch()使用的参数来自另一个 worker 级自动 fixture_browserOptions第 221-236 行它由launchOptions、headless、channel等选项合并而来并默认注入handleSIGINT: false以及 traces/artifacts 目录。拆卸use(browser)之后统一执行browser.close({ reason: Test ended. })即 worker 结束时浏览器进程被关闭。值得注意的细节browserName本身也是一个option fixture注册为[({ defaultBrowserType }, use) use(defaultBrowserType), { scope: worker, option: true, box: true }]第 212 行它的值来自同样可配置的defaultBrowserType默认chromium——这就是默认chromium的实现来源且因为option: true你可以通过test.use({ browserName: firefox })或配置文件的use段覆盖它。3.browserNamefixture按浏览器名做条件跳过browserName提供当前测试运行的浏览器名称取值为chromium | firefox | webkit默认chromium。最常见的用途是结合 test 注解 按浏览器跳过或标记测试test(skip this test in Firefox, async ({ page, browserName }) { test.skip(browserName firefox, Still working on it); // ... });如第 2 节所述它本质上是 worker 级 option fixture源码第 212 行。在多浏览器并行执行test-parallel场景下不同 worker 可能运行不同浏览器因此该值对 worker 内所有测试可见且一致而测试文件内按browserName分支的跳过逻辑正是官方推荐处理浏览器差异的方式。4.contextfixture每个测试一个隔离的 BrowserContextcontext提供为每个测试单独创建的隔离 BrowserContext 实例。由于 context 彼此隔离即使多个测试跑在同一个 Browser 里追求效率每个测试依然拿到全新环境cookie、localStorage、缓存、权限等互不干扰。上下文的配置方式同样见 test-configuration可用选项见 TestOptions。默认的pagefixture 就隶属于这个 context。用法示例——在 context 层面拦截某外部域名test(example test, async ({ page, context }) { await context.route(*external.com/*, route route.abort()); // ... });源码实现context如何做到每测试隔离context的定义见 index.ts 第 469-487 行它依赖browser、video、_reuseContext和_contextFactory常规路径不复用调用内部的_contextFactoryfixture第 396-454 行scope: test。工厂内部执行browser.newContext({ ...videoOptions, ...options })其中options由_combinedContextOptions汇总——该 fixture第 294-378 行把viewport默认{ width: 1280, height: 720 }、locale默认en-US、storageState、proxy等二十余项 TestOptions 逐项合并成BrowserContextOptions。工厂返回的close闭包负责context.close()测试结束后由contextfixture 的拆卸阶段调用从而保证每个测试的 context 用完即毁。beforeAll/afterAll 限制_contextFactory创建 context 前会检查当前所处 hooktestInfoImpl._currentHookType()如果是在beforeAll或afterAll里请求context/page会直接抛出提示错误——因为这两个 fixture 按测试创建无法在文件级 hook 中复用官方建议改用browser.newContext()手动创建。复用路径可选若_reuseContext为真由_optionContextReuseMode/ 环境变量PW_TEST_REUSE_CONTEXT等决定第 461-467 行则不调用newContext()而是通过browserImpl._newContextForReuse()获取一个可跨测试复用的浏览器端上下文以提升速度测试结束后执行_disconnectFromReusedContext(closeReason)断开连接而非销毁。这是面向高级场景的优化路径默认关闭。5.mountfixture组件测试的入口v1.62 新增mount挂载一个组件 story并返回指向 story 渲染根元素的 Locator。查询必须从返回的 locator 作用域内发起用component.getByRole(button)而不是page.getByRole(button)。它的工作模型由三个角色构成story故事一个小包裹组件把被测组件固定在某个特定场景中硬编码的 props、mock 数据、providers、录制的回调。gallery画廊页由你自己实现并托管在baseURL下。gallery 需暴露window.mount(params)与window.unmount()两个函数负责把 story 渲染进其根元素。mount()调用每次调用都会先导航到baseURL再以 story id 和 props 调用window.mount()因此各测试之间完全隔离。基础用法test(click should expand, async ({ mount }) { const component await mount(components/Expandable/Stateful); await component.getByRole(button).click(); await expect(component.getByTestId(expanded)).toHaveValue(true); });把 story 类型作为模板参数传入可对 props 做类型检查import type { WithTitle } from ./Button.story; test(renders the title, async ({ mount }) { const component await mounttypeof WithTitle(Button/WithTitle, { title: Hello }); await expect(component).toContainText(Hello); });返回的 locator 上还扩展了两个方法update(props)—— 以新 props 重新渲染同一个 story 而不重新挂载保留组件状态unmount()—— 卸载 story。参数说明参数类型说明storyIdstring要挂载的 story 标识由 gallery 页解析惯例是story 文件路径 导出的 story 名如components/Button/PrimarypropsObject可选传给 story 的普通、可序列化的 props源码实现mount如何调用 gallerymountfixture 的完整实现只有 27 行位于 index.ts 第 502-528 行。它依赖测试级的page和 option fixturebaseURL核心逻辑校验baseURL未配置baseURL时直接抛出mount() requires baseURL to point at the component gallery. Set it in your Playwright config.——这与文档gallery 页托管在 baseURL的约定一一对应。导航并挂载await page.goto(baseURL)后callMount通过page.evaluate在页面里执行window.mount({ story: storyId, props })若 gallery 未定义window.mount页面内会抛出The gallery page does not define window.mount().。evaluate使用了exposeFunctions: true选项这样 props 中的回调也能变成浏览器可调用的真实函数并回传测试侧。返回增强 locator返回值是page.locator(#root)的扩展——即 story 的挂载根元素固定是 id 为root的节点update(newProps)重新调用callMountgallery 若复用根节点/实例框架会协调更新组件状态得以保留unmount()则调用window.unmount?.()。6.pagefixture最常用的测试级页面page提供为每个测试单独创建的隔离 Page 实例。页面之所以在测试之间互相隔离正是因为context的隔离。这是测试中最常用的 fixtureimport { test, expect } from playwright/test; test(basic test, async ({ page }) { await page.goto(/signin); await page.getByLabel(User Name).fill(user); await page.getByLabel(Password).fill(password); await page.getByText(Sign in).click(); // ... });源码实现page只是context的薄封装page的定义见 index.ts 第 489-500 行page: async ({ context, _reuseContext }, use) { if (!_reuseContext) { await use(await context.newPage()); return; } // First time we are reusing the context, we should create the page. let [page] context.pages(); if (!page) page await context.newPage(); await use(page); },从源码结构看page并不自己创建浏览器环境它完全依赖contextfixture进而依赖browser。常规路径下执行一次context.newPage()并把新页面交给测试复用模式下则优先取 context 中已存在的第一个页面没有才新建。这解释了文档中页面隔离源于 context 隔离的表述——隔离能力逐层委托而page本身只负责给你一个能用的页面。7.requestfixture每测试独立的 APIRequestContextrequest提供每个测试独立的 APIRequestContext 实例用于在测试中直接发起 HTTP 请求API 测试、登录拿 cookie 等无需借助页面import { test, expect } from playwright/test; test(basic test, async ({ request }) { await request.post(/signin, { data: { username: user, password: password } }); // ... });源码实现复用 playwright 实例并限制 beforeAll 用法request是 utility fixtureindex.ts 第 191-205 行依赖 worker 级的playwrightfixture第 78-80 行{ scope: worker, box: true }use的值就是require(playwright-core)。测试级装配时执行playwright.request.newContext()拆卸时dispose()。一个体现工程细节的点拆卸阶段会检查当前是否处于beforeAllhook(test.info() as TestInfoImpl)._currentHookType()。若是则抛出带完整修复建议的错误——request无法从beforeAll复用到测试中建议要么在测试里单独使用requestfixture要么在beforeAll中手动创建APIRequestContext并在afterAll中释放。这说明 fixture 引擎知道每个 hook 各自的 fixture 实例边界beforeAll装出来的实例不会泄漏给后续测试。8. 依赖解析与执行顺序源码级机制本节回答运行器凭什么知道该装配哪些 fixture、按什么顺序。相关逻辑分布在三个文件中common/fixtures.ts注册与校验、worker/fixtureRunner.ts执行、common/index.tsextend/mergeTests入口。8.1 依赖从函数签名解析而来fixture 的依赖不是显式声明而是从 fixture 函数第一个参数的解构模式解析。fixtureParameterNames 对fn.toString()做了三件事先用filterOutComments剥掉注释再用正则匹配形如async ({ a, b }, use) 的首参要求首参必须是对象解构否则报First argument must use the object destructuring pattern并且不支持 rest 属性...props会报错要求显式列出所有用到的 fixture。解析结果按函数缓存signatureSymbol并写入FixtureRegistration.depsfixtures.ts 第 31-57 行。这正是第 6 节page能自动看见context的原因——它出现在page函数的参数解构里。8.2 FixturePool 的静态校验FixturePool构造完成后立即调用 validate()对全部注册做静态检查典型错误信息包括作用域冲突同一条 worker 级 fixture 不能依赖 test 级 fixturekScopeOrder.indexOf(registration.scope) kScopeOrder.indexOf(dep.scope)判定例如worker fixture x cannot depend on a test fixture y依赖环检测用 visiting/visited 标记做 DFS发现环时输出完整链路a - b - a form a dependency cycle未知参数Fixture x has unknown parameter y选项覆盖合法性只有以{ option: true }注册的 fixture 才允许在配置use段覆盖否则报Fixture key cannot be overridden in the configuration use section。validate()还会对 worker 级 fixture 的 id 序列计算 SHA1 作为 pool 的digest。FixtureRunner.setPool 在装载新 pool 时比对 digest不一致则抛出Playwright detected inconsistent test.use() options错误——这是防止同一个 worker 先后处理了test.use()配置不同的文件这类隐蔽错误的兜底机制。8.3 FixtureRunner惰性装配、逆序拆卸每个测试/hook 运行前resolveParametersForFunction 分两步收集需要装配的 fixture自动 fixture遍历pool.autoFixtures()按模式过滤worker 级 hook 只跑 worker 级 auto测试/测试级 hook 额外跑 test 级 autoworker 级排前面显式 fixture从函数参数解析出的每个名字经 _collectFixturesInSetupOrder 递归收集其全部依赖——这就是依赖 B 先于 A 装配的实现对每个注册先递归收集 deps再把自身加入SetSet 保证去重且保序。随后逐个await _setupFixtureForRegistration(...)完成装配并把最终值合并成一个普通对象作为函数第一参源码特意包一层{ result }防止存在名为then的 fixture 时被当作 thenable 误处理。拆卸走 teardownScope取instanceForId中所有实例反转顺序后用_collectFixturesInTeardownOrderFixture 类第 173-179 行按先拆使用者、后拆被依赖者收集同作用域的 fixture。test 级 fixture 在每次测试后拆完testScopeClean标志保证worker 级 fixture 直到 worker 进程退出才拆。若拆卸因超时或错误中断teardown的finally块会强制清理残留依赖引用避免 worker 级 fixture 永远持有 test 级 fixture。8.4use()的运行时契约Fixture._setupInternal 揭示了 fixture 函数三参数(params, use, info)的精确语义use(value)只允许调用一次第二次调用抛出Cannot provide fixture value for the second timefixture 函数体必须调用use()未调用则报use() was not called in fixture nameawait use(value)之后的代码是拆卸阶段use内部await一个_useFuncFinishedpromise直到该 fixture 不再被任何使用者引用_teardownInternal里 resolve 它才继续执行——因此use()前后代码天然对应装配/拆卸无需 try/finally。第三个参数按作用域决定传什么worker 级 fixture 得到WorkerInfo含workerIndex、config、projecttest 级 fixture 得到TestInfo。8.5 一条完整依赖链以最常见的pagefixture 为例把前文各节串起来依据 index.ts 中的注册browser (worker, timeout: 0) └─ context (test) └─ _contextFactory (test) ← _combinedContextOptions (test) ← 各项 TestOptions option fixtures └─ page (test) ← context.newPage()page依赖contextcontext依赖browserworker 级 fixture 有独立超时槽位workerFixtureTimeout在 Fixture 构造器 中写入_setupDescription.slottest 级 fixture 的装配/拆卸时间则计入测试超时自动 fixture如内部_setupArtifacts、_browserOptions会在任何显式 fixture 之前按 worker→test 顺序先装配。9. 延伸阅读test-fixtures 用户指南自定义 fixture、worker 级 fixture、自动 fixture、fixture 超时、option fixture、mergeTests合并多个模块的 fixture 等完整实践TestOptions可用于配置browser/context/page的全部选项test-configuration 与 test-parallel理解 worker 复用与并行执行如何与 worker 级 fixture 配合worker/fixtureRunner.ts 与 common/fixtures.tsfixture 引擎的完整实现适合在遇到作用域报错或循环依赖报错时对照源码定位。适用前提以上内置 fixture 的类型、默认值与行为以当前仓库源码为准mountfixture 自 v1.62 引入且要求 gallery 页正确实现window.mount/window.unmount并在配置中设置baseURLbrowser、browserName、context、page、request自 v1.10 可用。【免费下载链接】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),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →