国内很多团队把 E2E 挂进合并门禁之后,最先崩的不是覆盖率,而是「今天绿、明天红」的偶发失败。重跑两次就过了,于是有人把 retries 调到 3,有人直接 skip。结果门禁还在,信号却废了。
这篇按「能落地」来写。目标很具体:让偶发失败可回放、可分类、可隔离,并且隔离不是黑洞。材料主要来自 Playwright 官方 Trace / Best Practices / CI 文档,以及近年把 quarantine 做成可审计流程的工程实践。

偶发失败常见三类。
如果没有 Trace,这三类在日志里长得很像,都是 timeout。团队只能赌运气。Playwright Trace Viewer 的价值在于把时间轴、DOM 快照、网络请求叠在一起,失败瞬间可以回放,而不是只盯一行报错。
官方建议在 CI 上用 retries: 1 配 trace: 'on-first-retry'。不要默认 trace: 'on',全量开 Trace 很重。本地排查再用 --trace on 或 UI Mode。
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: process.env.CI ? 1 : 0,
use: {
trace: 'on-first-retry',
// 没有 retries 时可用 retain-on-failure
// trace: 'retain-on-failure',
},
forbidOnly: !!process.env.CI,
workers: process.env.CI ? 1 : undefined,
globalTimeout: 60 * 60 * 1000,
});
判断标准很简单:一次失败之后,你能不能在 10 分钟内指出「分叉点」。指不出,就先别加 retries,先把制品留住。

优先顺序建议这样排。
toBeVisible、toHaveText、toBeEnabled 比手写 waitForTimeout 稳。expect.poll 或 toPass 包起来,表达「最终一致」,而不是「睡 2 秒」。route.abort(),广告和统计尤其该拦。一个常见改法如下。
// 差:固定睡
await page.click('text=提交');
await page.waitForTimeout(3000);
await expect(page.getByText('提交成功')).toBeVisible();
// 好:等状态本身
await page.getByRole('button', { name: '提交' }).click();
await expect(page.getByRole('alert')).toContainText('提交成功');
第三方噪声可以这样收口。
await page.route(/google-analytics|hotjar|intercom|sentry/, (route) => route.abort());
注意:支付、登录这类业务依赖接口不要整域掐死,应 mock 具体路径。

偶发失败一旦进主门禁,会逼团队做坏选择:要么阻塞所有人,要么删测试。隔离(quarantine)是第三条路。关键不是「先跳过」,而是「继续跑、但不挡合并,并且有退出条件」。
推荐做法。
@flaky,旁边写 owner、issue、起始日期。不要匿名 skip。--grep-invert @flaky,失败阻断合并。--grep @flaky,continue-on-error: true,只产报告。// QUARANTINE owner:@checkout-team issue:#4821 since:2026-10-01
test('checkout completes under load @flaky', async ({ page }) => {
// ...
});
# 概念示例,按你们的 CI 改写
jobs:
trusted:
runs-on: ubuntu-latest
steps:
- run: npx playwright install chromium --with-deps
- run: npx playwright test --grep-invert @flaky
- uses: actions/upload-artifact@v4
if: failure()
with:
name: trusted-artifacts
path: |
playwright-report/
test-results/
quarantine:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- run: npx playwright test --grep @flaky --retries=2
- uses: actions/upload-artifact@v4
with:
name: quarantine-artifacts
path: |
playwright-report/
test-results/
CI 上建议只装真正用到的浏览器。例如只用 Chromium 就装 Chromium,能明显省时间和磁盘。Linux runner 通常更便宜也更稳。
假设「提交订单」在 CI 上每周翻车两三次。
trace.zip,对齐成功用例的 Trace。@flaky 视为流程失败。这套路径比「再重跑一遍」贵一点,但省下的是整条研发流水线的信任。
如果你们还处在「E2E 刚进 CI」阶段,不要一上来追求 100% 稳定。更务实的目标是。
waitForTimeout 合入主干。技术含量不在名词,而在约束。retries、quarantine、Trace 都只是工具。真正决定质量的是「失败后你能不能复盘,隔离后你能不能退出」。
(配图为本文示意图,便于评审与对内宣讲。落地时请以你们仓库真实配置为准。)