首页
/ Bruno Playwright E2E 测试速查手册:Fixture 体系、项目划分、测试数据约定与常见陷阱

Bruno Playwright E2E 测试速查手册:Fixture 体系、项目划分、测试数据约定与常见陷阱

2026-09-05 13:37:32作者:滕妙奇

Bruno 使用 Playwright 对其 Electron 应用做端到端测试,仓库中的 testing.md 是一份面向 tests/playwright/playwright.config.ts 的路径作用域速查手册,配套的叙述型完整指南在 playwright-testing-guide.md。本文以这份速查手册为主体,结合 playwright/index.tsplaywright.config.tstests/utils/ 下的真实实现,讲清楚 Bruno E2E 测试的完整工作流:如何运行测试、理解五个测试项目、使用自定义 fixture 拉起隔离的 Electron 实例、管理 init-user-data 测试数据、以及绕开六个高频坑。

一、测试运行方式

速查手册给出的核心命令如下:

npm run test:e2e                           # default + system-pac projects(自动拉起 dev server)
npx playwright test tests/request/         # 运行指定目录
npx playwright test --project=default      # 运行指定项目
npx playwright test --headed               # 有头模式(Watch mode)

对照 package.json 中实际注册的脚本,完整的运行入口为:

脚本 实际命令 用途
npm run test:e2e playwright test --project=default --project=system-pac 主测试 + 系统 PAC 代理测试
npm run test:e2e:ssl playwright test --project=ssl 自定义 CA 证书测试
npm run test:e2e:auth playwright test --project=auth OAuth 等认证测试
npm run test:e2e:mock-server playwright test --project=mock-server mock server 测试
npm run test:e2e:sanity playwright test --project=default --project=system-pac --grep @sanity 仅跑打了 @sanity 标签的用例
npm run test:codegen node playwright/codegen.ts 录制式生成测试
npm run test:benchmark playwright test --config=playwright.benchmark.config.ts 基准测试(独立配置)

配置层面的关键参数(见 playwright.config.ts):

  • fullyParallel: true —— 测试文件级并行;
  • workers: undefined —— 不设单 worker,采用 Playwright 默认并发数;
  • forbidOnly: !!process.env.CI —— CI 上禁止 .only 漏网;
  • retries: process.env.CI ? 2 : 0 —— 本地 0 次重试,CI 2 次;
  • use.trace —— CI 为 on-first-retry,本地为 on(本地默认全量录 trace);
  • reporter 为 list + html + json(输出到 playwright-report/results.json),CI 上额外追加 github 与可跨分片合并的 blob reporter。

webServer 会同时拉起两个服务:npm run dev:web(Bruno 前端 dev server,等待 stdout 匹配 ready built in)和 npm start --workspace=packages/bruno-tests(提供 http://localhost:8081/ping 等 mock API 的测试后端),两者在非 CI 环境下均 reuseExistingServer

二、测试项目(Project)划分

playwright.config.ts 定义了五个项目:

项目 testDir 说明
default ./tests 主项目,但通过 testIgnore 排除 ssl/**auth/**benchmarks/**proxy/system-pac/**mock-server/**
auth ./tests/auth 认证测试有独立项目(配置独立)
ssl ./tests/ssl 自定义 CA 证书测试需要独立的服务器配置与证书生成
system-pac ./tests/proxy/system-pac proxy/pac 共享 PAC/代理/目标端口,需在 default 之后独立运行
mock-server ./tests/mock-server 独立项目,使用 workerIndex 端口分配与每 worker 的 Electron 状态隔离

default 项目的 testIgnore 注释写明了排除原因:ssl 测试需要单独的服务器与证书生成;auth 测试拥有自己的项目;proxy/system-pacproxy/pac 共享端口;mock-server 依赖 workerIndex 端口加每 worker 独立 Electron 状态。因此 npm run test:e2e 只跑 default + system-pac,其余项目各有自己的 test:e2e:* 脚本。

三、自定义 Fixture 体系(playwright/index.ts)

[E2E 的全部自定义 fixture 定义在 playwright/index.ts 中,基于 baseTest.extend 扩展。速查手册列出的常用 fixture 与源码对照如下:

Fixture 作用域 用途 源码行为
page test electronApp 取默认页面 waitForReadyPage 等待窗口并等待应用加载完成
electronApp worker 默认 Electron 启动(跳过 onboarding) 调用 launchElectronApp(),worker 结束后统一关闭
launchElectronApp(opts) worker 自定义启动:可传 userData、env、init data 核心启动逻辑,详见下文
reuseOrLaunchElectronApp(opts) worker 按 key 缓存应用实例 key 为 testFile || userDataPath || initUserDataPathclosePrevious: true 可关闭并重建
pageWithUserData test 加载测试目录的 init-user-data/ 并等待应用就绪 递归拷贝 init-user-data 到临时目录后启动
createTmpDir(tag?) worker 临时目录,worker 结束后自动清理 mkdtemp 在系统 tmp 下建 pw-<tag>-* 目录
collectionFixturePath test fixtures/collection(s)/ 拷贝到临时目录 自动探测 fixtures/collections(多集合)或 fixtures/collection(单集合)
restartApp(opts) test 以全新 user data 重启 关闭上一个实例并以 closePrevious: true 重启
newPage test 带 tracing 的全新应用实例 每次调用都 launchElectronApp 一个新实例
installFakeClipboard(page) test 替换该页面的 navigator.clipboardcopiedText() 返回应用复制的内容 通过 Object.defineProperty 注入 fake,teardown 时还原

几个值得从源码层面理解的细节:

createTmpDir 的 tag 清洗与自动清理。 定义于 playwright/index.ts,tag 会先替换掉 Windows 文件名非法字符(<>:"/\|?*)与空白字符,避免由测试标题派生的 tag 导致 mkdtemp 创建失败;所有创建的目录在 worker 退出时统一 rm -rfmaxRetries: 10)。

launchElectronApp 的环境注入。 启动参数为 [packages/bruno-electron, '--disable-gpu'](见 playwright/index.ts),并注入如下环境变量:

env: {
  ...process.env,
  ELECTRON_USER_DATA_PATH: userDataPath,        // 每实例独立的 userData 目录
  DISABLE_SAMPLE_COLLECTION_IMPORT: 'true',      // 默认禁用示例集合导入
  PLAYWRIGHT: 'true',                           // 标记 Playwright 测试环境
  DISABLE_SINGLE_INSTANCE: 'true',              // 允许多实例并行
  ...dotEnv                                      // 测试可用 dotEnv 覆盖以上默认值
}

Electron 进程的 stdout/stderr 会被加上 [Electron #<workerIndex>] | 前缀转发,方便多 worker 并行时定位日志。关闭走 closeElectronAppplaywright/index.ts):先在主进程内关闭全部 BrowserWindow 触发正常退出链,每步都有超时(3s/5s),只有进程确实卡死才发 SIGKILL——这是为了避免 macOS Crash Reporter 弹窗和旧的 app.exit(0) 引发的异常退出对话框。

init-user-data 模板变量。 launchElectronAppinitUserDataPath 中的文件做 {{key}} 模板替换,内置 {{projectRoot}},并可通过 templateVars 扩展;未定义的 key 会直接抛错。preferences.json 文件会与内置 defaultPreferences mock 做 lodash merge(注意数组按索引合并而非拼接)。

四、测试数据约定

速查手册约定的测试数据目录结构:

  • tests/<suite>/init-user-data/ —— 已有用户测试(pageWithUserData)的种子数据;
  • tests/<suite>/init-user-data-fresh/ —— 新用户测试(hasLaunchedBefore: false);
  • tests/<suite>/fixtures/collection(s)/ —— 集合 fixture,自动拷贝到临时目录;
  • initUserDataPath 中的文件支持 {{projectRoot}} 模板变量;
  • 不提供 initUserDataPath 时,写入默认 prefs 且 hasLaunchedBefore: true(跳过 onboarding);
  • dotEnv 可覆盖默认环境变量(如 DISABLE_SAMPLE_COLLECTION_IMPORT: 'false' 以验证 onboarding)。

内置的 defaultPreferences mock 位于 playwright/index.ts,当前包含 onboarding.hasLaunchedBeforeonboarding.hasSeenWelcomeModalonboarding.lastSeenVersion(取自 bruno-app 的 package.json version)以及 ai.enabled: false关键维护约定:当你在应用的 preferences.json 中新增或修改默认键时,必须同步更新这个 mock,否则测试会在未设置的偏好上运行,与真实应用行为产生偏差(完整说明见 playwright-testing-guide.md 第 3 节)。

pageWithUserData 的实现(playwright/index.ts)会 recursiveCopy 整个 init-user-data/ 到全新的临时目录,再经 reuseOrLaunchElectronApp 启动;若 init-user-data/ 不存在,会抛出明确指引:要么补上种子目录,要么改用 page fixture。collectionFixturePathworkspaceFixturePath 会把对应 fixture 拷贝到临时目录并把路径归一化为正斜杠(避免 Windows 反斜杠代入模板 JSON 时产生非法转义)。

五、测试辅助模块(tests/utils/)

约定:优先复用现有 helper,不要手工拼接点击序列;不确定模块导出了什么时,直接读文件(例如 grep "^export" tests/utils/page/actions.ts)。按职责划分的模块:

模块 职责
tests/utils/page/actions.ts 高层用户动作:创建/打开集合、请求、文件夹、环境;发送/保存请求;workspace 与 tab 管理;导入
tests/utils/page/locators.ts locator 构建器(buildCommonLocators(page) + 按协议/功能的变体)与表格辅助
tests/utils/page/runner.ts 集合/文件夹 runner 动作与结果断言
tests/utils/cli.ts 速查手册中记载的 runCLI(cwd, args),用于 bruno-cli 测试(以仓库当前实际导出为准)
tests/utils/wait.ts 轮询辅助,如 waitForPredicate(predicate, {tries, interval}),默认 tries=10, interval=100ms

tests/utils/page/index.ts 通过 export * 汇总各页面模块,spec 中一次 import 即可拿到全部动作与 locator 构建器。

核心约定:spec 中绝不内联裸选择器。 每个 tests/utils/page/* 模块拥有一个 UI 区域,同时导出该区域的 locator 构建器与动作;新区域要新建文件,并将其构建器映射进 buildCommonLocators(当前定义于 tests/utils/page/locators.ts)。完整模式与示例见 playwright-testing-guide.md 第 1 节。速查手册强调该约定对新测试是硬性要求,对存量测试则宽容;重写或大改某个 spec 时,应顺带把它的选择器抽取进 page module。

六、等待应用就绪

所有等待应用加载完成的 spec 统一使用:

await page.locator('[data-app-state="loaded"]').waitFor();

这条链路的底层实现可以从源码确认:主进程在 packages/bruno-electron/src/index.js 发出 main:app-loaded IPC;渲染进程在 packages/bruno-app/src/pages/Bruno/index.js 监听该事件,收到后为 mainSectionRef 设置 data-app-state="loaded"(初始值为 loading)。速查手册特别指出:该属性在 main:app-loaded 到达时设置,独立于 renderer:ready,因此不会因 ready 链路上游问题而误判。playwright/index.ts 中的 waitForReadyPage 也以此为准:先 firstWindow()(失败则 waitForEvent('window'),默认超时 45s 以覆盖 Windows 首窗口慢的情况),再等待 [data-app-state="loaded"] 并额外 waitForTimeout(200) 让 UI 稳定。

七、Fixture 变更与隔离原则

套件必须隔离:唯一 tmp 路径、无共享状态。速查手册给出了清晰的分界:

  • 大多数 spec 通过 collectionFixturePath(拷贝 fixtures/collection(s)/ 到临时目录)或 pageWithUserData(把 init-user-data/ 载入全新临时 userData 目录)加载 fixture。这些操作针对的是临时副本——即使在磁盘上修改了文件(例如持久化 spec 写 environments/*.brucollection.bru),也不需要清理
  • 只有原地修改已提交 fixture 的 spec 才必须在 afterAll 中恢复,恢复手段是 git checkout <fixturePath>

换句话说:看到临时副本 spec 缺少 afterAll 不是问题,不应将其当作缺陷上报。

八、常见陷阱(Common Pitfalls)

速查手册列出的六个高频坑,均与 worker 作用域 Electron 这一架构直接相关:

  1. worker 作用域 fixture 跨重试存活 —— electronApp 是 worker 作用域,失败重试时上一次尝试创建的资源(文件夹、集合)仍然在应用里,重试会因重名失败。创建类 spec 要么容忍已存在,要么在重试前清理。
  2. 折叠的文件夹会把子节点移出 DOM —— 对折叠文件夹内的元素做 toHaveCount / toBeVisible 会失败,必须先展开文件夹再断言其子节点。
  3. 同组内共享状态的串行测试会级联失败 —— 同一 describe 中依赖前一个测试状态的用例会连锁挂掉;afterAll 清理只在组结束时运行一次,不会在两次重试之间运行。
  4. 文件监听时序 —— 通过 IPC 创建文件后,集合 watcher 需要检测到变更并处理完毕,UI 才会更新。应使用 Playwright 自动重试断言(expect(locator).toBeVisible() 等),而非即时检查。
  5. fixture 里的默认环境变量 —— launchElectronApp 默认设置 DISABLE_SAMPLE_COLLECTION_IMPORT: 'true',需要走 onboarding 流程的测试必须通过 dotEnv 覆盖。
  6. renderer 比测试活得久 —— electronApp 为 worker 作用域,page 每次取回的都是同一个窗口;spec 打补丁到页面上的任何东西(全局变量、被 stub 的浏览器 API)都会泄漏到该 worker 的后续 spec。解决方式是使用带 teardown 还原的 fixture;真实剪贴板在所有 worker 间共享,复制类断言必须走 installFakeClipboard(page),而不是读 navigator.clipboard.readText()installFakeClipboard 的实现(playwright/index.ts)用 defineProperty 注入 fake,teardown 时 Reflect.deleteProperty 还原,且对可能已丢失执行上下文的页面用 Promise.allSettled 独立处理,保证单个页面失败不影响其余还原。

九、测试覆盖范围与调试入口

测试套件统一位于 tests/,一个功能域一个目录,当前覆盖范围举例:request/collection/environments/runner/scripting/graphql/grpc/websockets/auth/ssl/(完整集合以目录列表为准)。

调试与生成测试的辅助设施:

  • codegen 录制playwright/codegen.ts 通过 playwright/electron.tsstartApp 启动真实 Electron 应用并开启 Playwright recorder,运行 npm run test:codegen my-new-test 即可在 tests/ 下生成 .spec.ts(不带参数会交互询问文件名);
  • 调试与 tracenpx playwright test --debugnpx playwright test --trace on,本地默认全量 trace,CI 上首次重试时开启(见 playwright.config.tsuse.trace);
  • 基准测试:独立配置 playwright.benchmark.config.ts,对应 npm run test:benchmark

十、延伸阅读

速查手册自身声明:更完整的叙述型指南——测试结构、page-module locator/动作模式、完整示例——以 docs/playwright-testing-guide.md 为准,其中的最佳实践(语义化选择器、断言留在 spec 而动作只负责 waitFor 同步、测试数据管理、超时排障等)与本文速查内容互为补充,两者约定冲突时以该指南为权威来源。

登录后查看全文
热门项目推荐
相关项目推荐