Playwright 最佳实践
Jun 4, 2026 - ⧖ 5 minTLDR
本文基于 flomo-cleaner 油猴脚本的 E2E 自动化实践经验,归纳一套 Playwright 最佳实践。核心框架是 3SQ(Size / Speed / Secure / Quality),与 Taskfile 最佳实践和 CI 最佳实践的结构一致。
核心结论:
- Size 侧:依赖跟仓库走、复用系统 Chrome、trace/screenshot 仅 retain-on-failure、Fixture 抽公共代码、workers:1 保稳定、按文件拆分
- Speed 侧:Web-first assertions、60s 超时、headed/headless 分离、test.step 分组、retries:0
- Secure 侧:destructive gate、独立 profile、凭证环境变量、precondition fast-fail、channel:chrome
- Quality 侧:trace/screenshot 作为 AI 故障证据、page.on 多维度验证、reload 后验证、describe/tag 组织、双 reporter、配置文件版本化
前情提要:AI 时代的油猴脚本自动化开发闭环 描述了 flomo-cleaner 的工程架构闭环,工具分类框架 提供了 Playwright 在 web access tools 中的定位分析。
技术选型:为什么是 Playwright Test
在 web automation 领域,可供选择的工具有不少:Puppeteer、Cypress、Selenium、Playwright。为什么最终选择 Playwright Test 作为 E2E 验证层?
Playwright 的核心优势在于它的 design choice 天然匹配工程化需求:
- Web-first assertions:断言会自动等待目标状态满足,不再需要手写
sleep或waitForTimeout。这不仅减少了代码量,也消除了最常见的 flaky 来源。 - 浏览器通道复用:支持
channel: chrome直接使用系统安装的 Google Chrome,而不是下载 bundled Chromium。对于油猴脚本自动化场景,这意味着可以直接加载用户已安装的 Tampermonkey 扩展。 - 多维度验证能力:
page.on('console')捕获 JS 错误、page.on('response')监控 API 状态、trace 录制完整操作回放。这些组合让测试不仅能断言"页面长什么样",还能判断"页面是否真正可用"。 - 工程化原生:
@playwright/test作为 npm 包安装,配置文件和代码一起版本化。这与 Puppeteer 或 CDP 的一次性调用模式有本质区别——Playwright 的设计起点是"测试资产应该被长期维护和复用",而不是"临时调试工具"。
技术上还有更大的一层考量需要单独细说,即 工具分类 里的 三层分类框架:全局 CLI / MCP 适合一次性实验和交互式查询;Playwright Test 项目内安装适合工程化闭环;Chrome DevTools 适合手动调试。三层各有所长,不要错位使用。
更具体的说,Playwright Test 之所以必须作为 devDependency 安装,而非做成全局 CLI 或 MCP,核心原因在其长期复用属性:E2E 测试资产(断言逻辑、等待策略、失败证据、报告)需要跟项目一起演进,每次调试都是一个可复现的测试 case,而不是一次性终端命令。全局 CLI 天生适合"用完即走"的场景,MCP 适合 AI agent 驱动的交互式调试,但都不适合作为工程化闭环的验证层。
工程化定位:devDependency 形式
Playwright Test 的核心交付形式是 @playwright/test devDependency,而非全局 CLI 或 MCP server。这一选择有两点考量:
第一,版本锁定是测试可靠性的基础。全局 CLI 的版本取决于安装时机和环境,无法保证本地、CI、协作者运行的是同一套 Playwright API。devDependency + lockfile 锁住版本后,所有环境的行为都可预测。
第二,配置与代码共库是协作的前提。playwright.config.ts 定义了 timeout、workers、reporter、channel、trace policy 等所有行为参数。成员 clone 后 pnpm install 接 pnpm exec playwright test 就能跑,不需要任何手动配置。
import { defineConfig } from "@playwright/test" ;
export default defineConfig ({
testDir : "./e2e" ,
timeout : 60_000 ,
retries : 0 ,
workers : 1 ,
reporter : [[ "list" ], [ "html" ]],
use : {
channel : "chrome" ,
headless : false ,
trace : "retain-on-failure" ,
screenshot : "only-on-failure" ,
video : "retain-on-failure" ,
},
});
这个配置即是本文所有 BP 的落地表现。以下逐节展开。
Size — 规模控制
Size 维度关注测试体量、依赖规模、产物控制以及资源占用。对于 E2E 测试,体量膨胀的常见路径是:依赖越来越大(bundled browser)、产物越积越多(trace/screenshot 全量保留)、代码越来越臃肿(缺少抽象)、并发竞争导致 flaky。
1. devDependency 安装 @playwright/test
@playwright/test 作为 devDependencies 安装,由 pnpm 锁定版本。保证本地、CI、协作者运行的是同一套 Playwright API 和行为。
之所以不能使用全局 CLI 或 MCP,核心原因是 Playwright 在这一场景的核心价值是长期可复用的测试资产而不是临时一次性工具。全局 CLI/MCP 适合"用完即走"的交互式查询,不适合需要回归、断言、产物留痕的工程化闭环。上方的技术选型节已详细论述。
2. 复用系统 Chrome,不下载 bundled browser
npx playwright install chromium 会额外下载约 200MB 的 bundled Chromium。这个浏览器和系统安装的 Google Chrome 行为可能有细微差异(编码、GPU、扩展支持)。
use: {
channel : "chrome" , // 复用系统 Google Chrome
}
配置 channel: chrome 后 Playwright 直接复用系统 Chrome,既省磁盘,也保证测试环境与用户真实环境一致。对油猴脚本自动化场景,这更是硬性要求——系统 Chrome 能加载 Tampermonkey 扩展,bundled Chromium 做不到。
3. trace/screenshot/video 仅 retain-on-failure
trace : "retain-on-failure" ,
screenshot : "only-on-failure" ,
video : "retain-on-failure" ,
trace 文件通常几百 KB 到数 MB,全量录制在长时间运行中会快速耗尽磁盘。retain-on-failure 只在失败时保留证据,成功用例不需要排查,不需要留痕。
screenshot 同理。only-on-failure 是更经济的策略——trace 已包含页面状态回放,screenshot 作为冗余证据。
4. Fixture 抽取公共代码
Fixture 是 Playwright 原生的依赖注入机制,比传统的 beforeEach 更可控、更可组合:
import { test as base , type Page } from "@playwright/test" ;
export const test = base . extend <{ authenticatedPage : Page }>({
authenticatedPage : async ({ browser }, use ) => {
const context = await browser . newContext ({ storageState : "auth.json" });
const page = await context . newPage ();
await page . goto ( "https://flomoapp.com" );
await use ( page );
await context . close ();
},
});
Fixture 支持自动清理、按 test 粒度的生命周期控制、多个 Fixture 的自由组合。跨用例公共逻辑应该优先进 Fixture,而不是复制粘贴到每个 test 的 beforeEach。
5. workers: 1 显式控制
workers : 1 ,
前端 E2E 测试常见的问题是浏览器状态共享:登录态、localStorage、IndexedDB、cookie——多个 worker 并行时这些状态会互相污染,导致测试结果不确定。
workers: 1 牺牲了并行速度,但换来的是测试稳定性。速度的损失可以通过更细粒度的测试文件和有针对性的 grep 过滤来弥补。
6. 按文件拆分 + 单一职责
每个测试文件覆盖一个独立场景或页面,文件名即场景名:
e2e/
probe.spec.ts # 环境探活
login.spec.ts # 登录流程
flomo-cleaner.spec.ts # 核心清理场景
不要在一个文件里塞几十个 test。拆分的标准是:一个文件只测一个业务场景。通过 describe 嵌套进一步组织场景内的子场景,通过 test title 描述具体的验证点。
Speed — 执行效率
Speed 关注测试的反馈速度。E2E 测试天然比单元测试慢,但不代表应该慢得不可忍受。关键在于使用正确的等待策略和超时设置。
7. Web-first assertions 自动等待
Playwright 的 expect(locator).toBeVisible() 等 Web-first assertion 内置自动等待(默认最多 5s)。它们会反复检查目标状态直到满足或超时,期间页面可以继续渲染。
反模式是显式使用 page.waitForTimeout(3000)。这类硬编码等待要么太长(浪费等待时间),要么太短(导致 flaky)。只有 reload 后需要等待页面重新初始化的极端场景,才应考虑有限度的稳定等待。
8. timeout 60s
E2E 测试的超时设置应与测试类型匹配:单元测试用秒级超时,E2E 测试用分钟级超时。
timeout : 60_000 ,
60s 是一个经验值:足够完成页面加载、异步渲染、网络请求的完整链路,又能避免测试在异常状态下无限挂起。每个 expect 的默认 timeout 是 5s,全局 60s 的 timeout 留给整条测试链路。
9. headed 开发 / headless CI 分离
这条原则在 AI 时代的油猴脚本自动化开发闭环 中有详细讨论。核心思路是:
- 开发阶段:
headless: false,直观看到页面状态,方便排查定位。尤其是在调试油猴脚本这类依赖 Tampermonkey 扩展的场景,headed 模式可以直接观察扩展菜单和页面交互。 - CI / 回归阶段:
headless: true,不干扰前台工作,适合批量执行。
开发阶段也可以考虑两者的混合:headed 主链路作为最终验收,headless smoke 作为日常快速检查。
10. test.step 细粒度分组
test ( "清理笔记" , async ({ page }) => {
await test . step ( "打开 Flomo 页面" , async () => { ... });
await test . step ( "执行清理操作" , async () => { ... });
await test . step ( "刷新后验证结果" , async () => { ... });
});
test.step 把长用例拆成可读的语义段落。失败时 step 名称直接出现在 HTML report,不用逐行读日志就能定位失败环节。对 AI 调试场景尤其重要——step 名称是 AI 理解"哪里失败"的关键上下文。
11. retries: 0(本地)
retries : 0 ,
retry 是 CI 场景的 flaky 补偿机制,不是本地开发的标准配置。本地 retry 会掩盖真实问题,让开发者在等待 retry 后看到"又绿了"而不去深究根因。
CI 可根据实际 flaky 情况单独配置 retries: 1-2,但配置前应先在 retry 的 trace 中分析 flaky 根因——retry 从来不应该成为 flaky 的默认解。
Secure — 安全/边界控制
Secure 维度关注执行安全与边界控制。E2E 测试操作的是真实页面和真实数据,安全防护优先级高于执行效率。
12. destructive env gate
对于删除、清空、批量修改等 destructive 操作,必须由显式环境变量开启:
test . skip (
() => process . env . ENABLE_DESTRUCTIVE !== "true" ,
"destructive test requires ENABLE_DESTRUCTIVE=true"
);
默认不跑,避免本地日常验证或 CI 误触真实数据。这个 gate 确保了 destructive 行为无法被意外触发——无论是开发者忘记注释、CI 配置变更,还是 AI agent 自主执行。
13. 独立 Chrome automation profile
Playwright 默认使用临时浏览器 profile,每次测试都是全新的浏览器环境。这带来了隔离性,但也意味着:
- 登录态每次都需要重新建立
- Tampermonkey 脚本需要重新安装
- 浏览器扩展需要重新加载
对于需要持久化状态的场景(如油猴脚本 E2E),应单独指定持久化 profile 路径:
const context = await browser . newContext ({
userDataDir : "./profiles/flomo-automation" ,
});
这个 profile 与日常浏览 profile 隔离,不会互相干扰。即使自动化操作出现问题,日常浏览数据也不受影响。
14. 凭证只从环境变量读
账号密码、API token 等敏感信息只从 process.env 读取:
const email = process . env . FLOMO_EMAIL ;
const password = process . env . FLOMO_PASSWORD ;
不写入配置文件、测试代码、Git 历史——一旦凭证进入 Git,清除成本极高。CI 通过 GitHub Secrets 注入,本地通过 .env 文件或 shell 环境变量传递。
15. 前置条件检查 + fast-fail
目标页面是否可达、环境变量是否设置、登录态是否有效——这些条件在 beforeEach 或 test setup 阶段就应该检查:
test . beforeEach ( async ({ page }) => {
test . skip ( ! process . env . FLOMO_EMAIL , "FLOMO_EMAIL not set" );
test . skip ( ! process . env . FLOMO_PASSWORD , "FLOMO_PASSWORD not set" );
await page . goto ( "https://flomoapp.com" );
await expect ( page . locator ( "body" )). toBeVisible ();
});
快速失败的好处是尽早暴露环境问题,避免测试执行到一半才爆炸——既节省时间,也让失败信息更清晰:"环境变量未设置"远比"元素找不到"容易排查。
16. channel: chrome(非 bundled)
use: {
channel : "chrome" ,
}
除了省磁盘之外,channel: chrome 在安全层面的价值是使用真实的浏览器环境:
- 系统 Chrome 拥有用户安装的扩展(Tampermonkey、uBlock Origin 等)
- 系统 Chrome 使用真实的 GPU、字体、编解码器
- Hit-test、事件冒泡、CSS 渲染路径更接近真实用户
对油猴脚本自动化场景,bundled Chromium 无法加载 Tampermonkey 扩展,这意味着测试无法验证用户入口(GM 菜单)、自动更新(@downloadURL)、或者脚本在真实扩展沙箱中的行为。channel: chrome 不是可选项,是必要条件。
Quality — 可维护性
Quality 关注测试的可维护性、可诊断性、可复用性和组织一致性。这是 E2E 测试长期投入后最见分晓的维度——初期的干净项目在几十个测试之后是否还能快速排查和有效回归。
17. Trace/Screenshot as AI failure artifacts
Playwright 的 trace viewer 录制了完整的操作回放:每个 action 的 DOM 快照、网络请求、控制台日志、时间轴。截图则是失败瞬间的视觉快照。
这组证据对 AI 修复循环至关重要。AI 不需要猜测"哪里失败了"——trace viewer 可以直接回放操作,screenshot 展示失败时的页面状态。稳定的证据链让 AI 从"根据经验猜测"升级为"根据证据诊断",修复效率和准确度都会显著提升。
结合 reporter: "html" 生成的完整报告,每次 CI 失败都有一份可独立查看的证据包,不需要复现问题。
18. page.on('console') + page.on('response') 多维度验证
页面 DOM 正常,不等于页面没有 JS 错误或 API 异常。Playwright 的 event 监听提供了额外的验证维度:
page . on ( "console" , ( msg ) => {
if ( msg . type () === "error" ) {
// 记录或断言 JS 错误
}
});
page . on ( "response" , ( response ) => {
if ( ! response . ok ()) {
// 记录或断言 API 异常
}
});
实际案例中,flomo-cleaner 的 probe 逻辑曾出现过清理触发的 API 返回 500,但页面 DOM 因为错误边界包裹而显示正常。如果没有 page.on('response') 监听,这个错误会在测试中被忽略。多维度验证是"页面真实可用"的必要条件。
19. reload 后验证(非瞬时 DOM)
只看"操作后瞬时 DOM"是 E2E 测试最常见的假阳性来源。前端框架(React、Vue)有异步渲染流程,操作后的 DOM 可能只是中间过渡态:
// ❌ 只看瞬时 DOM
await page . click ( ".delete-btn" );
await expect ( page . locator ( ".memo" )). toHaveCount ( 0 );
// ✅ reload 后验证真实状态
await page . click ( ".delete-btn" );
await page . reload ();
await expect ( page . locator ( ".memo" )). toHaveCount ( 0 );
reload 后的状态才是服务器端持久化的真实状态。特别适合 SPA 和需要同步数据到后端的场景。这也是 flomo-cleaner case 中的核心教训之一。
20. describe + grep/tag 组织
describe 嵌套结构体现场景层级,test 通过 tag 支持分类筛选:
describe ( "flomo-cleaner" , () => {
test ( "probe 不删除任何笔记 @smoke" , async () => { ... });
test ( "run 清理指定笔记 @destructive @regression" , async () => { ... });
test ( "重复执行幂等 @regression" , async () => { ... });
});
按 tag 分组执行:
npx playwright test --grep @smoke # 快速 smoke
npx playwright test --grep @destructive # 只跑 destructive 测试
grep 支持 --grep-invert 排除某些 tag,支持多个 tag 组合。几百个测试文件通过 describe + tag 的双层组织,随时可以按需筛选执行。
21. reporter: [['html'], ['list']] 双输出
reporter : [[ "list" ], [ "html" ]],
两个 reporter 各司其职:
- list reporter:终端实时输出每个测试的执行状态,适合开发阶段和 CI 日志查看进度
- html reporter:生成 HTTP 报告,包含测试结果、trace 链接、截图、步骤详情,适合事后排查和团队共享
list 管实时、html 管存档,缺一不可。
22. 配置文件版本化、跟项目走
playwright.config.ts 是 Playwright 行为的唯一入口,必须进入 Git:
// playwright.config.ts — 所有 Playwright 行为在此定义
export default defineConfig ({
testDir : "./e2e" ,
timeout : 60_000 ,
retries : 0 ,
workers : 1 ,
reporter : [[ "list" ], [ "html" ]],
use : {
channel : "chrome" ,
headless : false ,
trace : "retain-on-failure" ,
screenshot : "only-on-failure" ,
video : "retain-on-failure" ,
},
});
配置文件版本化的核心价值是行为一致性:timeout、workers、reporter、channel、trace policy——所有参数都和代码一起演进。新成员 clone 后直接 pnpm install && pnpm exec playwright test 就能跑,不需要手动翻文档配置。CI 与本地共用同一份配置,不存在"本地能过 CI 过不了"的配置漂移问题。
总结:3SQ 实践框架一览
| 维度 | 核心关注点 | BP 条目 |
|---|---|---|
| Size | 测试体量与复杂度控制 | devDep 安装、复用系统 Chrome、retain-on-failure、Fixture 抽象、workers:1、按文件拆分 |
| Speed | 执行效率与反馈速度 | Web-first assertions、timeout 60s、headed/headless 分离、test.step、retries:0 |
| Secure | 执行安全与边界控制 | destructive gate、独立 profile、环境变量凭证、fast-fail、channel:chrome |
| Quality | 可维护性与可诊断性 | trace/screenshot AI 证据、page.on 多维度验证、reload 后验证、describe+tag、双 reporter、配置版本化 |
这 22 条实践不是一次性 checklist,而是和项目一起演进的基线标准。当测试项目增长、团队扩展、或者遇到新的挑战时,用这组框架重新审视测试资产,比从零开始制定规范更有参照系。
最后,这篇实践也和 Taskfile 最佳实践和 CI 最佳实践一样,都采用 3SQ 框架作为结构组织——跨领域保持同一套心智模型,减少在不同主题间切换的认知负担。