mcpField
T
test-reliability

test-reliability

github.com/petrkindlmann测试与质量2221.2k 次安装6 天前更新
代码仓库

关于 test-reliability

test-reliability 面向排查不稳定测试并让自动修复可审查的团队。内容涵盖根因分类、稳健定位器、环境与测试数据修复、隔离管理,以及带置信度和证据的修复流程。

主要功能

  1. 01测试波动根因分类
  2. 02多属性稳健定位器
  3. 03感知环境的故障诊断
  4. 04过期测试数据恢复
  5. 05基于置信度的修复流程
  6. 06测试隔离管理指导

适用场景

  1. 01排查间歇性 CI 失败
  2. 02用经过审查的候选项替换失效选择器
  3. 03区分后端或运行器问题与 UI 故障
  4. 04从令牌过期或记录缺失中恢复测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/petrkindlmann/qa-skills --skill test-reliability

安装后在 Claude Code 中输入/test-reliability确认已加载。

附带文件references/flaky-test-runbook.md

详细介绍

重试后仍然不稳定的测试,最终可能会在最关键的发布期间连续 3 次失败;而静默自动修复的测试,现在也可能验证的是完全不同的元素。这项技能旨在构建值得团队信赖的测试套件:可靠的定位器、分类后的不稳定测试、感知环境的修复、数据修复,以及带置信度评分的可观测修复——每项自动修复都会生成证据,供人工审核。

快速导航

情况前往
测试在 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. 预防胜于补救。 编写可靠的测试成本为 1 倍。排查不稳定测试成本为 10 倍。失去团队对测试套件的信任,代价则是 100 倍。

  2. 修复必须可观测、可审核。 每项自动修复都会生成证据:哪里出了问题、尝试了什么、什么方法有效,以及置信度评分。静默修复和静默失败一样,都会迅速侵蚀信任。

  3. 先分类,再修复。 时序问题的修复方法与数据依赖问题完全不同。错误诊断会浪费精力,还可能让问题恶化。

  4. 不稳定测试就是缺陷。 不能把它们当作小麻烦而容忍。不稳定测试要么存在测试缺陷(修复测试),要么揭示了应用缺陷(修复应用),要么暴露了环境问题(修复环境)。

  5. 将可靠性作为指标跟踪,而非凭感觉判断。 衡量不稳定率、平均修复时间、隔离时长和选择器稳定性。只有衡量,才能推动修复。

  6. 自愈是一个渐进的过程。 从可靠的定位器开始(第 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 上下文来查找元素:

  1. 父级 + 标签 + 类型: 通过 testId 查找容器,然后在容器内按标签和类型定位。
  2. 前置标签: 查找同级文本(标签),然后定位相邻的输入框 / 按钮。
  3. 附近文本上下文: 查找目标附近的可见文本,然后在同一父级中定位相应类型的元素。

这些是由下方置信度系统评分的修复候选项,而不是运行时回退方案。

选择器稳定性评分

按 0-5 分为每个选择器评分,以便确定重构优先级。

分数策略可适应的变化
5getByTestId('submit-order')CSS、文本和结构变化
4getByRole('button', { name: 'Submit' })CSS 和结构变化
3getByLabel('Email')CSS 变化;标签改写后会失效
2getByText('Submit Order')文案有任何变化都会失效
1locator('.btn-primary.submit')CSS 或结构变化后会失效
0locator('//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 失败

操作失败时,先检查后端健康状况,再归咎于测试:

  1. 操作失败 → 请求 /api/health。
  2. 后端不健康(5xx 或超时) → 使用指数退避重试。诊断为 backend_down。这不是 UI 错误。
  3. 后端健康(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.30testId=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.150.0-1.0(可访问名称的 Levenshtein 比率)
属性重合度0.100.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 — 如果不稳定问题调查发现了真实的应用程序缺陷,将失败交给分流流水线进行分类和报告。

评论

支持 Markdown