Vitest @vitest/browser-playwright:用 Playwright Provider 运行浏览器测试的原理与配置详解
【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest
本篇围绕 Vitest 官方 Playwright 浏览器 Provider 包@vitest/browser-playwright展开:它只负责“把浏览器跑起来”(browser provider),而非替代 Playwright 测试框架。读完本文,你将掌握该包的完整安装与browser.provider配置方式、五个 Provider 选项(launchOptions、connectOptions、contextOptions、actionTimeout、persistentContext)的取值语义与默认行为,以及从源码层面理解浏览器预热(prewarm)、按测试文件隔离 Context/Page、基于 Playwright 路由拦截的模块 Mock 机制等底层实现。
定位:是浏览器 Provider,不是测试运行器
packages/browser-playwright/README.md开宗明义:
Run your Vitest browser tests using playwright API. Note that Vitest does not use playwright as a test runner, but only as a browser provider.
也就是说,Vitest 仍然用自己的describe/it执行测试、做断言与报告;Playwright 在这里的角色是浏览器驱动层——负责启动浏览器进程、创建 Context/Page、执行点击/填写/截图等交互命令、拦截请求以支持模块 Mock。官方给出的选型建议是:如果你的项目已经在用 Playwright,或者还没有 E2E 测试框架,推荐使用这个包。
从包元信息可以确认其定位(packages/browser-playwright/package.json):
- 版本
5.0.0,ESM 包("type": "module"),导出入口为./dist/index.js,另有./context子路径仅导出类型声明(packages/browser-playwright/context.d.ts 只做export * from '@vitest/browser/context'); peerDependencies中playwright为必需("optional": false),vitest为同 workspace 版本——说明它必须与 Playwright 本体共同安装,才能复用其浏览器驱动;dependencies中的@vitest/browser、@vitest/mocker说明它与 Vitest 浏览器层和 Mock 引擎强耦合。
安装
使用你喜欢的包管理器安装(继承自 packages/browser-playwright/README.md):
npm install -D @vitest/browser-playwright # or yarn add -D @vitest/browser-playwright # or pnpm add -D @vitest/browser-playwright注意还需要安装playwright本身(peer dependency),并按 Playwright 的常规流程安装浏览器内核(如npx playwright install)。
基本配置:browser.provider与--browser运行
在 Vitest 配置的browser.provider字段指定playwright()工厂的返回值,并通过browser.instances声明要跑的浏览器(packages/browser-playwright/README.md):
// vitest.config.ts import { defineConfig } from 'vitest/config' import { playwright } from '@vitest/browser-playwright' export default defineConfig({ test: { browser: { provider: playwright({ // ...custom playwright options }), instances: [ { browser: 'chromium' }, ], }, }, })随后以浏览器模式运行:
npx vitest --browser该写法与全局配置文档 docs/config/browser/provider.md 一致:browser.provider的类型是BrowserProviderOption,即 Provider 工厂的返回值;你也可以像webdriverio()、preview()一样换成其他 Provider 工厂,配置结构不变。
Provider 支持共享选项 + 按实例覆盖:顶层provider的选项对所有实例生效,单个实例内再写provider: playwright({...})时是整体覆盖而非合并(docs/config/browser/playwright.md):
import { playwright } from '@vitest/browser-playwright' import { defineConfig } from 'vitest/config' export default defineConfig({ test: { browser: { // shared provider options between all instances provider: playwright({ launchOptions: { slowMo: 50, channel: 'chrome-beta', }, actionTimeout: 5_000, }), instances: [ { browser: 'chromium' }, { browser: 'firefox', // overriding options only for a single instance // this will NOT merge options with the parent one provider: playwright({ launchOptions: { firefoxUserPrefs: { 'browser.startup.homepage': 'https://example.com', }, }, }), }, ], }, }, })五个 Provider 选项详解
PlaywrightProviderOptions的完整定义在 packages/browser-playwright/src/playwright.ts,共五个字段,逐一说明:
launchOptions
透传给playwright[browser].launch()(源码中做了Omit<LaunchOptions, 'tracesDir'>处理)。常用项如channel、slowMo、args、firefoxUserPrefs均可直接使用。两个重要约束(docs/config/browser/playwright.md):
- Vitest 会忽略
launch.headless:无头/有头统一由test.browser.headless控制。源码印证了这一点——resolveLaunchOptions在展开用户传入的launchOptions之后,会用browser.headless覆盖headless字段(playwright.ts#L193-L227)。 - 开启
--inspect时,Vitest 会向launch.args追加--remote-debugging-port=<port>(默认 9229),且 Chromium 只允许 localhost 远程调试,非 localhost 的 inspector host 会被忽略并给出警告(playwright.ts#L304-L312)。
另外,若配置了browser.ui且浏览器为 chromium,源码会自动追加--start-maximized,让 Vitest UI 以最大化窗口打开(playwright.ts#L217-L224)。
connectOptions
透传给playwright[browser].connect(),并在其基础上要求必须提供wsEndpoint(类型定义为ConnectOptions & { wsEndpoint: string })。用于连接已有的 Playwright 服务端(playwright run-server),典型场景是把浏览器放进 Docker/CI/远端机器。一个关键细节:Vitest 会把自己的启动参数 JSON 序列化后塞进x-playwright-launch-options请求头转发给远端(playwright.ts#L316-L334);如果你自己在 headers 里已带了该键,则说明远端自行控制启动参数,Vitest 只会打印黄色警告而不覆盖。
contextOptions
透传给browser.newContext()。类型上Omit掉了ignoreHTTPSErrors和serviceWorkers——因为源码getContextOptions中强制ignoreHTTPSErrors: true(playwright.ts#L551-L565),以兼容 HTTPS 部署;同时当开启 UI 且非 headless 时会强制viewport = null,让页面采用真实窗口尺寸,避免无头/有头两种模式截图产生 deviceScaleFactor 差异。官方还建议 viewport 优先用test.browser.viewport配置而不是写在这里。
actionTimeout
- 默认:
0(不超时),源码中表现为context.setDefaultTimeout(actionTimeout),仅在非 null 时设置(playwright.ts#L543-L545)。
该值控制 Playwright 等待可访问性检查通过、交互动作真正完成的最长时间。也可以逐次动作覆盖:
import { page, userEvent } from 'vitest/browser' await userEvent.click(page.getByRole('button'), { timeout: 1_000, })persistentContext
- 类型:
boolean | string,默认false(4.1.0 引入)。
启用后改用launchPersistentContext启动浏览器,使 cookies、localStorage、DevTools 设置等状态在多次测试运行间保留:
- 设为
true:用户数据目录为./node_modules/.cache/vitest-playwright-user-data; - 设为字符串:作为自定义用户数据目录路径。
源码中的处理逻辑(playwright.ts#L336-L357):若测试并行运行(如 headless 且开启fileParallelism),该选项会被静默降级为false并打印警告,因为持久化 Context 无法在并行会话间共享。
export default defineConfig({ test: { browser: { provider: playwright({ persistentContext: true, // or specify a custom directory: // persistentContext: './my-browser-data', }), instances: [{ browser: 'chromium' }], }, }, })支持哪些浏览器
源码中硬编码了支持列表(playwright.ts#L41):
const playwrightBrowsers = ['firefox', 'webkit', 'chromium'] as constplaywright()工厂通过defineBrowserProvider声明了name: 'playwright'与supportedBrowser: playwrightBrowsers(playwright.ts#L94-L106)。因此instances中这三个值均可用;若配置了connectOptions之外的远端方案,也可以借此列表之外的浏览器名(工厂会原样调用playwright[browserName])。
源码深读一:启动链路、预热与 Context/Page 生命周期
PlaywrightBrowserProvider类(playwright.ts#L229)是核心,几个值得注意的实现事实:
1. 支持文件级并行。supportsParallelism = true表明该 Provider 已加入 Vitest 的并行实验特性;这也是persistentContext在并行下被禁用的原因。
2. 浏览器预热(prewarm)。源码维护了两个WeakMap(warmBrowsers按解析后的 browser 配置键控、pendingWarmBrowsers按 vitest 实例键控,playwright.ts#L121-L124)。prewarm()在 Node 端还在创建 Vite dev server 时就提前import('playwright')并发起launch,把浏览器启动延迟与 Vite 启动时间重叠。安全性由一条规则保证:预热与真实启动用同一函数resolveLaunchOptions计算启动参数,真实打开时若两份参数 JSON 不一致(或预热失败),预热的浏览器实例会被丢弃/关闭、走正常启动路径重试(playwright.ts#L359-L372)。注意connectOptions、persistentContext、inspector 开启三种情况会跳过预热。
3. 一个 Context/Page 对应一个测试文件。createContext(sessionId, ...)以会话 ID(即测试文件维度)为键缓存 Context(playwright.ts#L524-L549);官方文档也明确警告:与 Playwright 测试框架不同,Vitest 为同一测试文件中的所有测试打开同一个 Page,隔离粒度是测试文件而非单个测试(docs/config/browser/playwright.md)。重开同一 sessionId 的 Page 时,旧 Page 会先close()再创建新 Page(playwright.ts#L608-L616)。
4. 崩溃与信号兜底。Page 触发crash事件时,Vitest 会把归因明确的错误(“页面崩溃,可能是浏览器内存耗尽”)直接 fail 给对应会话,而不是留成一个模糊的 WebSocket 断连(playwright.ts#L626-L630)。构造函数中还注册了SIGTERM监听,在进程被杀时尽力把未完成的 tracing chunk 落盘(playwright.ts#L262-L278);close()时按 Page → Context(或 persistentContext)→ Browser 的顺序级联关闭。
5. Service Worker 网络拦截。模块顶部有一行:process.env.PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS ??= '1'(playwright.ts#L44-L46)。注释说明:这是 Playwright 的实验性 API(仅 Chromium 系可用),目的是让 Service Worker 发出的请求也能被context.route()拦截——这是 Mock Service Worker(MSW)等基于 SW 的网络 Mock 方案在 Vitest 浏览器模式下能工作的底层前提。
源码深读二:模块 Mock 如何实现
Vitest 的浏览器模块 Mock 依赖“请求拦截 + 路由改写”。createMocker()返回register/delete/clear三个方法(playwright.ts#L382-L522),核心机制:
- 精确匹配 URL:
createPredicate用模块 URL 构造谓词,先剔除t、v、import等缓存/内部查询参数再逐参数比较,避免对同一模块的不同变体误拦截; - manual mock:直接
route.fulfill一段由@vitest/mocker/node的createManualModuleSource生成的 JS 源码; - automock / autospy:
route.fulfill({ status: 302, Location: <原URL>?mock=<type> }),用 302 重定向让浏览器带 mock 标记重新请求,由 Vitest 的 Vite 转换管线返回自动 Mock 版本; - redirect mock:同样以 302 转发到
module.redirect; - webkit 特判:由于 WebKit 不支持路由重定向响应(Playwright 已知限制),webkit 分支改为通过
vite.transformRequest直接拿到转换后代码、内联 base64 source map 后fulfill(playwright.ts#L446-L474)。
register之前若同一 sessionId 已有旧谓词,会先unroute再替换,保证 mock 的增删清语义与 Node 环境一致。
源码深读三:交互命令与 Locator 扩展
命令注册。Provider 构造函数遍历commands对象,把 24 个以__vitest_前缀的命令注册到当前 TestProject(playwright.ts#L258-L260)。命令清单见 packages/browser-playwright/src/commands/index.ts:点击/双击/三击(__vitest_click等)、填写/输入/清空(fill/type/clear)、拖拽(dragAndDrop)、悬停(hover)、滚轮(wheel)、键盘(keyboard/cleanup)、选项选择(selectOptions)、Tab 切换、文件上传(upload)、视口(viewport)、截图(takeScreenshot),以及一组 tracing 命令(startTracing/startChunkTrace/stopChunkTrace/markTrace/annotateTraces/groupTraceStart/groupTraceEnd/deleteTracing)——对应 Vitest 的 Playwright Traces 集成。
Locator 体系。浏览器端初始化脚本dist/locators.js由 packages/browser-playwright/src/locators.ts 构建(Provider 的initScripts在 playwright.ts#L247-L249 声明)。PlaywrightLocator继承自@vitest/browser/locators的通用Locator,并借助page.extend暴露getByRole、getByTestId、getByText、getByLabel、getByAltText、getByPlaceholder、getByTitle、elementLocator、frameLocator等 API——这就是测试中page.getByRole('button')的来源。其中getByTestId的测试 ID 属性名取自browser.locators.testIdAttribute配置。
一个体现 iframe 细节的实现:定位器支持>>与internal:control=enter-frame的 Playwright 选择器拼接;而position类参数在 click/hover/dragAndDrop 时会乘以getIframeScale()换算坐标(locators.ts#L129-L138),因为 Vitest 浏览器测试实际运行在页面内的 iframe 中,UI 缩放会改变坐标比例。
类型系统整合。declare module 'vitest/browser'把UserEventClickOptions、ScreenshotOptions等接口扩展为 Playwright 原生类型(如Page['click']的参数类型),所以你可以把 Playwright 文档里的所有点击/悬停/截图参数直接写进userEvent调用;declare module 'vitest/node'则声明了 Provider 给命令上下文的page/frame()/iframe/context四件套(playwright.ts#L750-L799),并在_BrowserNames中注册'playwright'浏览器名。
调试技巧
从源码还能挖出两个实用的调试入口:
VITEST_PW_DEBUG环境变量:设置后每个 Page 会监听requestfailed事件,把失败请求的资源类型、URL 与错误文本打印到控制台(playwright.ts#L632-L643),适合排查容器/网络类问题;- inspector 远程调试:
--inspect运行后,控制台会输出Debugger listening on ws://127.0.0.1:9229,可用 DevTools 附加到测试浏览器(playwright.ts#L303-L312)。
小结
@vitest/browser-playwright的价值在于把 Playwright 作为可替换的浏览器驱动接入 Vitest:五个选项覆盖启动、远端连接、Context、交互超时与持久化状态;底层通过“每测试文件一个 Context/Page”实现隔离,通过context.route()的 302 重定向/直接 fulfill 实现与 Node 环境一致的模块 Mock,并通过浏览器预热、tracing 命令、CSP 式请求头注入等工程细节保证体验。相关文档入口为 docs/config/browser/playwright.md 与 docs/config/browser/provider.md,完整实现集中在 packages/browser-playwright/src/playwright.ts。
【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考