E2E 测试不是「测完就好」,是一套活的置信系统
写第一条 E2E 测试的那天,很多人会觉得自己解决了一个大问题——CI 跑绿了,功能能跑通,完美。然后三个月后,这套测试开始变成噩梦:随机 fail、本地跑过 CI 跑挂、没人敢动配置文件,因为改一行不知道会不会炸掉 20 个用例。
这不是 Playwright 的问题,也不是测试本身的问题。是把「能跑」等同于「可信赖」了。
选择器是测试套件的地基
用例写得再漂亮,选择器一脆,全崩。
最常见的错误是用 CSS 类名做定位:.btn-primary.submit-action。类名是样式层的产物,设计师调一下 Tailwind 类就换了,测试毫不知情地挂掉。
稳定的选择器策略只有一条主线:给可测元素加 data-testid,其余选择器只做补充。
// 脆:耦合样式实现
page.locator('.search-bar input.ant-input')
// 稳:语义化 testid
page.locator('[data-testid="search-input"]')data-testid 的好处是它明确传达意图——这个属性存在,是因为有人在测它,不是样式副产物。删掉它需要主动决策,不会被「顺手重构」误伤。
等待策略决定用例是否 flaky
Playwright 的 auto-wait 是个好东西,但不能全指望它。三种常见的不稳定来源,各有对应解法:
竞态(Race condition):直接调 .click() 不是问题,但在 locator 上调才能拿到 Playwright 的内置等待。裸 page.click() 已经是旧 API,现在统一用 page.locator(...).click()。
网络时序:绝对不要用 waitForTimeout(5000) 这类数字魔法。时间够了就偶尔能过,时间不够就偶尔挂——这就是 flaky 的来源。换成等具体条件:
// 等接口返回,不等时间
await page.waitForResponse(resp => resp.url().includes('/api/search'))动画时序:动画期间的元素状态不稳定,点了可能没效果。先 waitFor({ state: 'visible' }),再操作。
等「状态」,不等「时间」。凡是在测试里看到硬编码的毫秒数,就是一个潜在的 flaky 炸弹。
主动治理 Flaky,而不是忽略它
Flaky 测试是一种债。忽视它的代价是:团队开始对红色 CI 免疫,「重跑一下就好了」变成口头禅,真正的 regression 藏在噪音里被漏掉。
治理步骤分两阶:
先隔离。Playwright 提供 test.fixme() 和 test.skip() 做隔离标记,同时必须挂 Issue 引用。不挂 Issue 的隔离等于把问题扔进黑洞。
test('flaky: 搜索结果加载', async ({ page }) => {
test.fixme(true, 'Flaky — Issue #123,待查网络竞态')
// ...
})再复现。隔离之后用 --repeat-each=10 跑同一用例十次,稳定复现才能查根因。每次都过说明是环境问题;每次都挂说明是逻辑 bug;随机说明是时序问题。三种情况解法完全不同,不先分类就动手纯粹是碰运气。
产物管理:失败不是终点,证据才是
E2E 用例失败时,最宝贵的东西是失败瞬间的现场。Playwright 的 screenshot: 'only-on-failure'、video: 'retain-on-failure'、trace: 'on-first-retry' 三件套,CI 上必须全开,产物上传 artifacts 保留 30 天。
没有产物的 E2E 失败,只能靠猜。有产物,可以 npx playwright show-trace 一帧帧看,排查效率差了一个量级。
一句话心法
E2E 测试的价值不在于「绿了」,在于「绿了你信不信」——建立置信度,才是这套系统存在的理由。