关于 test-reliability
test-reliability 面向排查不稳定测试并让自动修复可审查的团队。内容涵盖根因分类、稳健定位器、环境与测试数据修复、隔离管理,以及带置信度和证据的修复流程。
主要功能
- 01测试波动根因分类
- 02多属性稳健定位器
- 03感知环境的故障诊断
- 04过期测试数据恢复
- 05基于置信度的修复流程
- 06测试隔离管理指导
适用场景
- 01排查间歇性 CI 失败
- 02用经过审查的候选项替换失效选择器
- 03区分后端或运行器问题与 UI 故障
- 04从令牌过期或记录缺失中恢复测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill test-reliability安装后在 Claude Code 中输入/test-reliability确认已加载。
附带文件references/flaky-test-runbook.md
详细介绍
快速导航
| 情况 | 前往 |
|---|---|
| 测试在 CI 中不稳定,根本原因未知 | 不稳定测试分类 → 决策树 |
| 定位器失效,希望换成可靠的定位器 | 定位器可靠性 |
| 操作失败,怀疑是后端响应慢,而非 UI 错误 | 感知环境的修复 |
| 测试过程中因数据过期而出现 401/404/429 | 数据修复 |
| 希望自动修复并设置审核关卡 | 可观测修复工作流 |
| 隔离不稳定测试,但不阻塞 CI | 隔离管理 |
| 逐步排查一个不稳定测试 | references/flaky-test-runbook.md |
发现问题
先检查 .agents/qa-project-context.md——其中记录了已知的不稳定区域、选择器策略和 CI 环境详情。已有答案的问题无需再问。
- 当前不稳定测试率是多少? 查看过去 30 天的 CI 失败统计。低于 2% 表示健康;2-5% 需要关注;高于 5% 会削弱团队对测试套件的信任。
- 问题主要集中在哪里? 定位器失效、时序、测试数据,还是环境?如果尚不清楚,先添加监测(参见“不稳定测试分类”)。
- 当前采用什么选择器策略? 全面使用 data-testid、混用 CSS 和基于角色的选择器,还是没有策略(能用就行)?这决定了稳定性评分的基线。
- 目前如何处理不稳定测试? 重试并寄希望于运气、跳过后不再处理,还是采用结构化的方法?这决定了需要增加多少流程。
- 测试运行在哪种 CI 环境中? 每次都使用同一台机器,还是使用不同的运行器?资源是否稳定?这些因素决定了如何区分环境问题和测试问题。
- 测试数据策略是什么? 共用数据库、每个测试单独配置数据、通过工厂填充数据,还是使用外部服务?这决定了是否适用数据修复。
核心原则
-
预防胜于补救。 编写可靠的测试成本为 1 倍。排查不稳定测试成本为 10 倍。失去团队对测试套件的信任,代价则是 100 倍。
-
修复必须可观测、可审核。 每项自动修复都会生成证据:哪里出了问题、尝试了什么、什么方法有效,以及置信度评分。静默修复和静默失败一样,都会迅速侵蚀信任。
-
先分类,再修复。 时序问题的修复方法与数据依赖问题完全不同。错误诊断会浪费精力,还可能让问题恶化。
-
不稳定测试就是缺陷。 不能把它们当作小麻烦而容忍。不稳定测试要么存在测试缺陷(修复测试),要么揭示了应用缺陷(修复应用),要么暴露了环境问题(修复环境)。
-
将可靠性作为指标跟踪,而非凭感觉判断。 衡量不稳定率、平均修复时间、隔离时长和选择器稳定性。只有衡量,才能推动修复。
-
自愈是一个渐进的过程。 从可靠的定位器开始(第 1 级),再添加备用策略(第 2 级),然后引入感知环境的修复(第 3 级),最后实现带置信度评分的自动修复(第 4 级)。在掌握第 1 级之前,不要直接跳到第 4 级。
定位器可靠性
多属性选择器(超越备用链)
单一的定位器策略就是单点故障。多属性选择器会结合多个信号来查找同一元素,无需备用链的复杂性,也能提升可靠性。
核心思路: 不要采用“先试 A,再试 B,最后试 C”,而是“查找同时匹配 A、B 和 C 的元素,并允许其中一个信号缺失”。
// Multi-attribute locator: tries combinations from most specific to least
const submitBtn = await multiAttributeLocator(page, {
testId: 'checkout-submit', // most stable signal
role: 'button', // semantic signal
name: /place order/i, // accessible name
nearText: 'Order Summary', // visual context
});
// Internally: tries testId+role+name first, then testId alone, then role+name,
// then text, then nearText+role. Returns first visible match.
// Unlike fallback chains, it combines signals for higher confidence.
DOM 相似性 / 邻近上下文匹配
定位器失效时,元素可能仍然存在,只是属性发生了变化。使用周围的 DOM 上下文来查找元素:
- 父级 + 标签 + 类型: 通过 testId 查找容器,然后在容器内按标签和类型定位。
- 前置标签: 查找同级文本(标签),然后定位相邻的输入框 / 按钮。
- 附近文本上下文: 查找目标附近的可见文本,然后在同一父级中定位相应类型的元素。
这些是由下方置信度系统评分的修复候选项,而不是运行时回退方案。
选择器稳定性评分
按 0-5 分为每个选择器评分,以便确定重构优先级。
| 分数 | 策略 | 可适应的变化 |
|---|---|---|
| 5 | getByTestId('submit-order') | CSS、文本和结构变化 |
| 4 | getByRole('button', { name: 'Submit' }) | CSS 和结构变化 |
| 3 | getByLabel('Email') | CSS 变化;标签改写后会失效 |
| 2 | getByText('Submit Order') | 文案有任何变化都会失效 |
| 1 | locator('.btn-primary.submit') | CSS 或结构变化后会失效 |
| 0 | locator('//div[3]/button[1]') | DOM 有任何变化都会失效 |
目标: 整个测试套件的平均分达到 3.5+。每月审计。优先修复评分为 0 和 1 的选择器。将每个定位器的评分写入 selector-stability.md(或 CI 步骤),并报告测试套件的平均分,确保 3.5 的目标可验证,而非仅仅声称达标。
不稳定测试分类框架
每个不稳定测试都有一个根因类别。正确分类才能确定修复方式。
类别
| 类别 | 信号 | 根本原因 | 修复方向 |
|---|---|---|---|
| 时序 | 超时错误、重试后通过、在 CI 中表现更差 | 竞争条件、动画、异步操作 | 等待条件,而不是等待时间 |
| 数据依赖 | 与其他测试一起运行时失败,单独运行时通过 | 共享状态、缺少清理 | 按测试隔离,清理 fixture |
| 环境 | 在特定运行器上失败,与负载相关 | 资源争用、网络延迟 | 模拟外部服务,增加资源 |
| 顺序依赖 | 使用 --shard 或 fullyParallel 时失败 | 依赖另一项测试的副作用 | 使用自包含的设置 |
| 时间敏感 | 在特定时刻失败(午夜、月末) | 使用真实时钟、日期边界 | 模拟时钟,使用相对比较 |
| 视觉渲染 | 截图差异闪烁、存在子像素差异 | 字体渲染、抗锯齿、动画帧 | 提高阈值,遮罩动态区域 |
| 外部服务 | 与第三方服务状态相关 | 测试中发起真实 HTTP 调用 | 模拟外部 API |
分类决策树
Test is flaky
│
├── Does it pass when run alone?
│ ├── YES → ORDER DEPENDENCY or DATA DEPENDENCY
│ │ ├── Does another test create/modify data it needs? → ORDER DEPENDENCY
│ │ └── Does it share a database/file/cache? → DATA DEPENDENCY
│ │
│ └── NO → Not order/data dependent. Continue below.
│
├── Does it fail more often in CI than locally?
│ ├── YES → TIMING or ENVIRONMENT
│ │ ├── Timeout errors? → TIMING (CI is slower)
│ │ ├── Connection errors? → ENVIRONMENT (network latency / service)
│ │ └── Resource errors (OOM, disk)? → ENVIRONMENT (resource contention)
│ │
│ └── NO → Same rate locally and CI. Continue below.
│
├── Does it fail at specific times?
│ ├── YES → TIME SENSITIVITY
│ │ ├── Near midnight? → Date boundary issue
│ │ ├── Near month/year end? → Calendar calculation
│ │ └── Specific hour? → Timezone issue
│ │
│ └── NO → Continue below.
│
├── Does it involve screenshots or visual comparison?
│ ├── YES → VISUAL RENDERING
│ │
│ └── NO → Continue below.
│
├── Does it call external HTTP APIs?
│ ├── YES → EXTERNAL SERVICE
│ │
│ └── NO → TIMING (most likely — default classification)
│ └── Investigate: what async operation is not being awaited?
各类别对应的修复方案位于 references/flaky-test-runbook.md(步骤 4),其中包含完整的代码模式。
感知环境的修复
并非所有测试失败都是测试本身的问题;有些是环境问题。感知环境的修复能够区分两者并做出相应调整。
后端缓慢与真实 UI 失败
操作失败时,先检查后端健康状况,再归咎于测试:
- 操作失败 → 请求
/api/health。 - 后端不健康(5xx 或超时) → 使用指数退避重试。诊断为
backend_down。这不是 UI 错误。 - 后端健康(2xx) → 这是真实的 UI / 测试失败。不要重试。
返回结构化诊断:{ success: boolean; diagnosis: 'backend_down' | 'ui_failure' | 'backend_slow_recovered' }。这会用于不稳定测试分类:后端问题属于环境问题,而非测试错误。
资源争用检测
在 CI 中判定测试失败之前,先检查是否存在资源争用:
- 浏览器健康状况: 加载
about:blank。如果耗时 > 2s(基线 < 500ms),说明运行器负载过高。 - API 健康状况: 请求
/api/health。如果耗时 > 5s(基线 < 1s),说明后端压力过大。 - 诊断: 如果任一检查失败,则归类为环境问题,并在测试结果中添加注释。资源争用导致的失败不计入不稳定测试率。
数据修复
测试数据会过期、被清理或变得无效。数据修复会检测并重新生成过时的测试数据。
常见的数据失效模式
| 模式 | 信号 | 修复方法 |
|---|---|---|
| 身份验证令牌过期 | 测试期间收到 401 响应 | 在 fixture 中重新生成令牌 |
| 测试记录已删除 | 访问预置数据时收到 404 | 在测试前重新播种数据 |
| 唯一性冲突 | 收到 409 或约束错误 | 为每次运行生成唯一标识符 |
| 缓存过时 | 返回的数据有误 | 在设置阶段清除缓存 |
| 超出配额 | 收到 429 或限流错误 | 重置配额或使用专用测试账户 |
自动修复测试数据的 fixture 模式
构建能够验证数据是否存在,并在数据过时时重新生成的 fixture。
// Pattern: verify → heal → use → cleanup
testUser: async ({ request }, use, testInfo) => {
// 1. Try to find existing test user by deterministic email
// 2. Verify auth token is still valid (GET /api/me)
// 3. If token expired → refresh it (POST /refresh-token), mark as healed
// 4. If user missing → create new one, mark as healed
// 5. If healed → annotate testInfo for observability
// 6. use(user) → run the test
// 7. Cleanup: delete test user (guaranteed by fixture, even on failure)
}
关键模式:
- 在电子邮件地址/标识符中使用
testInfo.testId,确保每个测试各自唯一。 - 发生修复时,添加
testInfo.annotations注释,便于观察。 - 始终在 fixture 的使用后代码块中清理,而不要在
afterEach中清理——fixture 能确保在失败时也执行清理。
可观测的修复工作流
核心保障措施: 修复必须可观察、可审查。每次修复都遵循以下流程:
Failure Detected
│
▼
Candidate Repair Generated
│
▼
Confidence Score Computed (0.0 - 1.0)
│
▼
Evidence Diff Produced (what changed, what was tried)
│
▼
Approval Policy Applied
│ ├── Score >= 0.9 → Auto-apply, log for batch review
│ ├── Score 0.7-0.89 → Apply in quarantine, flag for individual review
│ ├── Score 0.5-0.69 → Do NOT apply, open PR with evidence for review
│ └── Score < 0.5 → Discard, manual investigation required
│
▼
Intent Fidelity Check (does repaired test still test the same thing?)
│
▼
Rollback if intent fidelity drops
置信度评分
从六个维度为每个修复候选项评分(加权总和,0.0-1.0):
| 维度 | 权重 | 评分标准 |
|---|---|---|
| 匹配精确度 | 0.30 | testId=1.0,role=0.9,text=0.7,context=0.5,CSS=0.3 |
| 元素可见 | 0.15 | 可见则为 1.0,不可见则为 0.0 |
| 相同父容器 | 0.15 | 容器相同则为 1.0,不同则为 0.0 |
| 相同元素类型 | 0.15 | 标签和 role 均相同则为 1.0,不同则为 0.0 |
| 文本相似度 | 0.15 | 0.0-1.0(可访问名称的 Levenshtein 比率) |
| 属性重合度 | 0.10 | 0.0-1.0(共有属性的 Jaccard 系数) |
评分阈值(左闭右开区间——0.9 仅属于自动应用层级):
- >= 0.9 — 自动应用,记录日志以供批量审查。
- 0.7-0.89 — 在隔离环境中应用,标记为待单独审查。
- 0.5-0.69 — 不应用;创建 PR 并附上证据以供审查。
- < 0.5 — 丢弃,需人工调查。
references/flaky-test-runbook.md(分数解读)采用完全相同的这四个区间——如果编辑这些区间,请确保三处内容完全一致。
修复证据
每次修复都会生成一份证据记录,其中包含:测试文件、测试名称、失败类型、原始定位器、候选替代项(每项均包含置信度、证据字符串和意图保留标记)、选中的候选项、时间戳、审批路径、回滚触发条件,以及一段修复运行的 screencast.webm 录制文件(Playwright 1.59+ 的 page.screencast,带有 showActions 注释——即“Agentic 视频凭证”)。没有录屏时,Level-3/4 的修复测试会要求审查者相信一份 JSON 记录;有了录屏,差异和运行时表现都可以检查。
购买还是自建
如果你不想负责维护带置信度评分的修复器,就把第 3–4 级交给供应商;如果你需要本地部署/隔离网络部署、明确的 AI 提示词审计轨迹,或跟踪器无法表达的隔离逻辑,就在内部自建。一旦托管工具已能为你完成指纹识别、聚类和自动隔离,自建就不再值得。
- 对于 Selenium / Selenide / Robot Framework 测试套件,Healenium 仍是专用的自愈层(现已登陆 AWS Marketplace)。
- 对于 Playwright 中由 Agent 驱动的自愈,Playwright MCP 是标准方案——它为 Agent 提供实时浏览器控制能力,让其在仅靠 CLI + skills 不够时发现替代定位器。
- 提供此工作流的托管平台:Trunk Flaky Tests(自动隔离 + AI 故障聚类;其 2026 Quarantined Tests API 可通过程序返回当前隔离列表——与此技能手动构建的质量管理工作流相同)、CloudBees Smart Tests(前身为 Launchable;搜索旧文档的 Agent 可能会看到旧名称),以及 Datadog Test Optimization(于 2024 年 12 月从 “Datadog Test Visibility” 更名)。三者都提供指纹识别 + 聚类 + 自动隔离,对应第 3–4 级。
意图一致性检查
应用修复后,验证测试仍在检验相同的用户意图:
- 元素类型已更改(例如,
button→a)——意图未保留,回滚。 - ARIA 角色已更改(例如,
button→link)——意图未保留,回滚。 - 表单操作已更改(指向不同的端点)——意图未保留,回滚。
- 标签、角色和表单操作相同——意图已保留,保留修复。
如果修复改变了测试验证的内容(而不只是查找元素的方式),就必须回滚。
隔离管理
隔离会将不稳定测试单独处理,使其仍能运行,但不会阻塞 CI。完整配置和 CI 接线请参阅 references/flaky-test-runbook.md(步骤 6);要点如下:
// Tag the flaky test
test('intermittent WebSocket reconnect', {
tag: ['@quarantine'],
annotation: {
type: 'quarantine',
description: 'Flaky since 2026-03-15. Race condition in WebSocket handler. Ticket: BUG-1234.',
},
}, async ({ page }) => { /* ... */ });
// playwright.config.ts — separate projects
projects: [
{ name: 'stable', testMatch: /.*\.spec\.ts/, grep: /^(?!.*@quarantine)/ }, // exclude quarantine
{ name: 'quarantine', grep: /@quarantine/, retries: 3 },
],
在 CI 中,将 --project=stable 作为阻塞步骤运行,并将 --project=quarantine 与 continue-on-error: true 一起运行,这样 quarantine 项目就不会阻塞流水线。
隔离生命周期
1. DETECT — Test identified as flaky (CI reporter or manual triage)
2. TAG — Add @quarantine annotation with ticket link and date
3. ISOLATE — Quarantine project runs separately, does not block
4. DIAGNOSE — Follow the flaky test runbook (references/flaky-test-runbook.md)
5. FIX — Apply the fix pattern for the classified category
6. VERIFY — Run 50x with --repeat-each, zero failures required
7. RELEASE — Remove @quarantine tag, add annotation documenting the fix
隔离维护规则
- 隔离期限最长为 14 天。 14 天后,修复或删除。永久隔离只会造成永久积弊。
- 每个隔离条目都要有工单链接。 不允许匿名隔离。
- 每周审查。 每个迭代都要检查隔离列表。临近到期的隔离项要升级处理。
- 跟踪隔离规模。 超过 5% 的测试处于隔离状态,说明存在需要改进流程的系统性问题,而不只是测试问题。
反模式
1. 静默替换选择器
替换失效的选择器,却不记录日志、不进行审查,也不评估置信度。修复后的测试可能已经在验证完全不同的元素。每次修复都必须提供证据。
2. 把“重试一下”当作修复
重试是一种检测机制,不是修复。一个需要重试 2 次(共 3 次)的测试,最终可能在最关键的发布期间连续失败 3 次。
3. 永久禁用不稳定测试
test.skip('flaky, will fix later')——“稍后”永远不会到来。要么隔离并跟踪,要么彻底删除。没有工单的跳过测试就是死代码。
4. 将所有不稳定情况一视同仁
时序问题和数据依赖需要完全不同的修复方式。在数据依赖问题上添加 waitForTimeout(5000),只会让测试变慢,而且仍然不稳定。
5. 将 waitForTimeout 当作稳定性修复
// NEVER the right fix
await page.waitForTimeout(5000);
// Wait for the actual condition
await expect(page.getByRole('table')).toBeVisible();
await page.waitForResponse(resp => resp.url().includes('/api/data') && resp.status() === 200);
6. 治愈过程缺乏可观测性
自动修复不生成日志、证据或置信度评分。无法衡量就无法改进,无法审查就无法信任。
7. 尚未编写稳定测试就过度设计治愈机制
还没采用基本的弹性定位器模式,就先构建复杂的自愈框架。先从多属性选择器和恰当的等待开始。只有数据表明问题发生在哪里时,才添加治愈基础设施。
8. 隔离期限不得过期
测试在隔离区中一放就是几个月。隔离是临时状态,不是永久归宿。最长隔离时间应设为 14 天。
故障模式
| 症状 | 可能原因 | 修复或检查 |
|---|---|---|
后端健康检查本身不稳定 → 误判为 backend_down | 健康检查端点本身不稳定或响应缓慢 | 单独跟踪该端点自身的 p99;不要只凭一次探测就做出诊断 |
| 启用修复视频后,产物存储量暴增 | 每次运行都录制 page.screencast,而非仅在修复时录制 | 仅在修复路径录制,不要在正常路径录制 |
| 自动修复准确率降至 80% 以下 | 置信度阈值过低,或跳过了意图保真度检查 | 提高自动应用的最低置信度;绝不要跳过意图检查 |
| 隔离项目阻塞流水线 | 隔离 CI 步骤缺少 continue-on-error | 添加此项;隔离项目绝不能阻塞合并 |
验证
- 重现不稳定问题:
npx playwright test <spec> --repeat-each=20 --workers=4 --trace=on— 在相信任何修复之前,必须至少失败一次。 - 修复后证明稳定:
npx playwright test <spec> --repeat-each=50 --workers=4— 要求 50 次全部通过,在 CI 环境中也同样如此。 - 确认隔离路由:
npx playwright test --project=stable会排除@quarantine测试,而--project=quarantine只运行这些测试。 - 确认选择器审计会输出数值:稳定性报告会列出每个定位器的分数和套件平均分。
完成标准
- 每个不稳定测试都已识别,并按根本原因(时序、数据依赖、环境等)分类。
- 每个不稳定测试都已隔离或修复 — 没有任何测试在未经记录计划和工单引用的情况下悄悄重试。
selector-stability.md(或 CI 报告)会列出每个定位器的稳定性分数,并报告套件平均分 >= 3.5。- 不稳定测试率指标(重试后通过的测试百分比)已发布到 CI 仪表板,并对团队可见。
- 每条隔离记录都有工单引用,且到期日不超过 14 天后。
相关技能
- selector-drift-recovery — 计划进行 UI 重构后,需要离线批量重新生成许多选择器时使用;此技能会在运行时修复单个测试。
- playwright-automation — 完整的 Playwright 配置、Page Object Model、固件和 CI 集成;本文所述模式以此为基础。
- ci-cd-integration — 流水线配置、并行执行,以及上文提到的隔离任务接线配置。
- qa-metrics — 跟踪不稳定率、平均修复时间、隔离规模和选择器稳定性随时间的变化。
- ai-bug-triage — 如果不稳定问题调查发现了真实的应用程序缺陷,将失败交给分流流水线进行分类和报告。