mcpField
S
selector-drift-recovery

selector-drift-recovery

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

关于 selector-drift-recovery

一项用于在计划内重构或改版后恢复 Playwright 选择器的技能。它对比新旧 DOM 快照,生成优先使用角色定位的候选项并评分,最终将经过验证的修改和证据整理到可审查的 PR 中。

主要功能

  1. 01对比新旧 DOM 快照
  2. 02盘点并核实失败尝试
  3. 03生成角色优先的选择器候选项
  4. 04为候选项评分并附上截图
  5. 05在可审查的 PR 中验证修改

适用场景

  1. 01网站改版后修复选择器
  2. 02组件重构后更新测试
  3. 03带证据审查多项选择器修改

安装

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

$npx skills add https://github.com/petrkindlmann/qa-skills --skill selector-drift-recovery

安装后在 Claude Code 中输入/selector-drift-recovery确认已加载。

附带文件references/recovery-scripts.mdreferences/score-candidates.mjs

详细介绍

一次重设计已发布,现在有 23 个测试因 `TimeoutError: locator.* exceeded` 失败。在修改测试之前,先确定原因是定位器漂移还是应用程序回归。这项技能用于完成这一维护闭环:对比新旧 DOM,针对新构建优先使用角色重新生成失效的选择器,验证整个测试套件,并将差异打包成一个便于审查的 PR,每项更改都附带置信度评分和截图。触发因素是一个事件(重构已合并),而不是偶发故障。输出是一个由人工审核批准的 PR,而非静默的运行时自动修复。

这是 test-reliability 的批量、离线处理对应方案。两者共用多属性定位器和置信度评分基础机制,但运行方向相反:test-reliability 在运行时通过受控阈值修复单个选择器;selector-drift-recovery 则针对新 DOM 离线重新生成 N 个选择器,并将它们打包成一个 PR。如果你正在 test-reliability 中执行第二种工作流,请切换到这里。

快速路径

情况前往
一个测试损坏,并非重构所致停止——改用 test-reliability
多个文件中有 200 个以上的选择器损坏先按区域拆分(每个页面/目录一个 PR),然后执行阶段 1
重构改变了流程/语义,而不只是结构停止——使用 playwright-automation 根据规范重写
切换框架(Selenium → Playwright)停止——使用 test-migration,而非漂移恢复
完全没有旧 DOM 参考获取一份(阶段 1),或缩小范围——没有它就相当于“重写测试”
已有新旧 DOM,准备开始映射依次执行阶段 1 → 6

发现问题

先检查 .agents/qa-project-context.md——如果存在,就使用其中的信息,并跳过已回答的问题。该文件会指出你的 E2E 框架、选择器策略和已知脆弱区域。然后:

  1. 是什么触发了漂移? 是计划中的重构(可通过 Storybook 在合并前查看新 DOM)、已发布的重设计(新 DOM 已进入 main)、依赖项升级,还是 Tailwind/CSS 迁移?触发因素决定你是提前运行恢复,还是响应 CI 失败。
  2. 影响范围有多大? 涉及单个组件、一个页面,还是整个应用?单个组件:将恢复范围限定为涉及该组件的测试文件。全局范围:预留半天到一天,并决定哪些测试应该重写,而不是重新选择定位器。
  3. 当前采用什么选择器策略? 如果选择器主要是 data-testid,且重构保留了这些测试标识,恢复会很简单。如果基于 CSS 类或 XPath,预计有 30–60% 的选择器需要采用新策略,而不只是换一个定位器。
  4. 是否有通过测试的基线? 你需要在某处找到旧 DOM:之前的 CI 跟踪产物、已部署的预发布构建、Storybook 故事,或组件的 Git 历史记录。没有旧 DOM 参考,就会退化为“重写测试”。
  5. 失效的定位器是内联的,还是封装在 Page Object 中? JSON 报告器的 error.location 会指向失败行。对于内联定位器,它指向定位器本身;对于由 POM 封装的定位器,它指向 POM 辅助函数,而非测试。在信任自动提取的行号之前,务必确认这一点(参见“失败模式”)。
  6. 由谁审查最终 PR? 带置信度评分的更新需要人工签字批准。提前确定 PR 应提交给测试作者、负责重构的工程师,还是 QA 负责人审查。

核心原则

  1. 恢复由事件驱动,而非由故障驱动。 在计划进行重构或刚完成重构时运行此流程,而不是在某个测试偶尔失败时运行。一个测试损坏 → test-reliability。同一事件导致十个或更多测试损坏 → 使用此技能。

  2. 旧 DOM、新 DOM,以及映射后的配对。 整个技能都依赖重构前和重构后的快照。其他所有内容都是围绕这对快照进行的记录工作。请将两者都采集为 aria 快照(await page.locator('body').ariaSnapshot()),而不是原始 HTML——角色树差异正是以角色为先的恢复所需的信号,也不会像原始 HTML 差异那样被外观变化(类名重命名、包装 div)淹没。如果无法生成这两份快照,请先解决这个问题。

  3. 每次都优先使用角色进行替换。 即使旧测试使用了 CSS,重新生成的选择器也应优先使用 getByRole + 可访问名称,其次是表单字段的 getByLabel,再其次是重构时新增了测试 ID 的 getByTestId。恢复 PR 是提升选择器平均稳定性评分的机会(使用下方的 0–5 评分标准,与 test-reliability 共用)。

  4. 通过限定区域消除歧义,不要使用布局选择器。 当角色 + 名称存在歧义时(例如两个“提交”按钮),使用 getByRole('region', { name }).getByRole('button', …) 或 getByRole(...).filter({ hasText }) 缩小范围。不要使用 :near() / :right-of()——参见“避免”说明。评分为 3 的候选项,是指已应用区域限定,且定位器现在恰好匹配一个元素。

  5. 一个 PR,按文件分组,并为每项更改提供证据。 审阅者无法逐一检查散布在 30 个提交中的 47 处选择器更改。请将它们整合到一个 PR 中,按测试文件分组代码块,并为每项更改附上置信度评分和 DOM 截图。

  6. 合并前必须通过测试套件,并删除失效测试。 无法运行的重新生成选择器比原来的问题更糟;技能应以 CI 全绿结束,而不是只生成差异。此外,如果重构移除了某个功能,应删掉对应测试——不要为已不存在的元素重新生成选择器。

避免: 使用 Playwright 布局选择器 :near()、:right-of()、:left-of()、:above()、:below() 来消除歧义——这些选择器已被官方弃用,并且“可能会被移除”,因为 1px 的布局偏移就会改变匹配结果(Playwright 文档,2026 年)。它们也与以角色为先的原则相悖。请改用区域限定 / getByRole().filter()。


工作流

分为六个阶段,每个阶段都必须通过检查后才能进入下一阶段。

阶段 1 — 为旧 DOM 采集快照

你需要为受影响测试涉及的每个页面或组件采集重构前状态的快照。来源按优先级排序如下:

  1. 最近一次通过的 CI 跟踪产物。 大多数团队会在失败时保存 Playwright 跟踪(trace: 'on-first-retry')。下载成功运行的产物,使用 npx playwright show-trace traces/checkout.zip 打开跟踪,选择某项操作,并查看每个界面对应的每项操作 DOM 快照面板。(查看器不再提供“Copy HTML at this step”菜单项;请阅读快照面板,或通过 page.content() / ariaSnapshot() 重放以编程方式导出。)
  2. 重构前提交中的 Storybook。 执行 git checkout <PRE_REFACTOR_SHA>,启动 Storybook,并使用简短的 page.content() / ariaSnapshot() 脚本导出每个 story 的快照。
  3. 仍在运行旧版本的预发布构建。 按相同流程操作并采集快照。
  4. 组件的 Git 历史。 可以重建,但成本最高——需要单独渲染组件。

输出:为每个受影响的单元生成 .drift-recovery/old/<page-or-component>.aria.yml(如果还需要原始标记,则同时生成 .html)。

检查点: 你可以根据快照文件回答“这个页面在测试上次通过时是什么样子?”,而不是凭记忆回答。

第 2 阶段 — 快照新的 DOM

在重构后的构建版本中运行相同的页面区域。最好使用 Vercel/Netlify 预览部署,也可以使用本地开发服务器或 CI 中的 PR 分支。快照前,等待应用实际的水合/就绪契约。网络空闲并不能证明客户端渲染已完成。

输出:与旧集合相匹配的 .drift-recovery/new/<page-or-component>.aria.yml。

门槛: 每个旧快照都必须有对应的新快照。404 是需要调查的问题,并不能证明相关内容是有意删除的。提议移除覆盖范围前,先确认产品变更。

第 3 阶段 — 清点失败尝试,然后验证来源和意图

针对新构建版本运行 JSON reporter,并保留测试进程的退出状态。保留每一次失败/超时的尝试、项目和重试,即使后续重试通过也不例外。保留被中断的尝试和运行级错误,以便单独分流处理。完整的失败清单还不等于损坏的选择器列表。

  • 根据证据分类。 定位器超时可能由应用、身份验证、网络或就绪状态问题导致。断言也可能因为定位器不存在而超时。TimeoutError 和 expect 都不能证明或排除漂移。
  • 明确保留未知字段。 reporter 提供的是测试/结果证据,而非经核实的编辑位置、定位器意图或页面路由。检查跟踪记录、实际测试/POM 源码以及新旧应用状态之前,将这些字段保留为 null。附近的测试标题或默认 / 路由无法补足缺失的上下文。
  • 区分尝试与编辑。 保留所有重试证据,然后针对每个源代码中的出现位置,选择一个经过审核的恢复目标。将未解决的尝试和其他原因导致的尝试保留在清单和恢复 PR 中,以便查看。

纯收集器和 identify-drift.ts 驱动程序见 references/recovery-scripts.md。它们会写入 .drift-recovery/failure-inventory.json,而不会臆测上下文。

门槛: 只有记录为 confirmed-selector-drift,且文件/行号、确切的旧定位器、意图、页面路由和分流证据均已核实的目标,才能进入第 4 阶段。先调用 requireVerifiedDrift(target);仅仅填入字段并不能证明编辑保留了测试的含义。

第 4 阶段 — 生成替换候选项

对于每一行,针对新的 DOM 快照生成候选项,并按 0–5 分的评分标准(与 test-reliability 共用)为每项评分。策略优先级如下,越靠前越好:

  1. 重构团队新增的 data-testid——这是他们做出的最稳定选择。评分 5 分。
  2. getByRole + 可访问名称,且在页面上唯一。 评分 4 分。
  3. 对表单字段使用 getByLabel,当意图是输入字段且存在标签时。评分 4 分(如果字段没有其他名称,则优先于单独使用 role)。
  4. role + name,并限定在仅匹配一个元素的区域内。 如果单独使用 role + name 返回的元素超过 1 个,则将其包装起来——使用 getByRole('region', { name }).getByRole(role, { name }) 或 .filter({ hasText })——并确认限定范围后的定位器恰好匹配一个元素。只有在限定范围后结果明确无歧义,才评分 3 分。
  5. 仅使用可见文本(getByText)。评分 2 分——容易因文案变动而失效。
  6. 变更结构上的 CSS 类。 评分 1 分——通常仍会失效。
  7. 没有安全的替换方案。 评分 0 分——标记为需要人工处理。
评分替换策略自动应用?
5存在新的 data-testid是
4getByRole + 可访问名称(或 getByLabel),在页面上唯一是
3getByRole + name,限定在恰好匹配一个元素的区域内是
2仅使用可见文本否
1变更结构上的 CSS 类否
0未找到安全的替换方案否 — 标记为需要人工处理

只有在限定范围后已确定唯一元素时,候选项才可评为 3 分。仍有多个匹配项(count > 1、“需要限定范围”)的候选项不能评为 3 分——它尚未完成,不得自动应用。

保留实时 Locator 对象用于唯一性检查和截图;拟议的源代码表达式不是 CSS 选择器,绝不能对其求值。为名称加引号,并转义正则表达式文本,包括 DOM 属性中的标点和引号。缺少截图证据时,候选项仍未解决。

输出:.drift-recovery/candidates.json,每项提案包含 { file, line, oldLocator, selector, score, rationale, screenshotPath, applied }。如果候选项生成或证据采集失败,或者没有唯一候选项达到最低分,则保留一条评分为 0 的未解决记录。候选项辅助函数参见 references/recovery-scripts.md。

门槛: 每一行都有评分 ≥ 3 且已确认唯一的候选项,或已标记为需要人工审核。评分为 0、1 或 2 的候选项绝不自动应用。

第 5 阶段 — 应用、验证、迭代
  1. 在功能分支中应用评分 ≥ 3 的替换项。按 (file, line) 替换,而不是进行全文字符串替换——报告器中的定位器字符串是渲染后的形式(locator('.summary > h2')),通常与源代码表达式并不完全一致;而简单的 String.replace 只会命中第一个匹配项,还可能与相同定位器冲突。使用 TypeScript AST 编辑或经过检查的精确小范围修改,并确认原始源代码仍完全匹配。保留接收对象、调用链以及同一行上的其他定位器。单靠行号,或以第一个右括号结尾的正则表达式,都不是安全的源代码锚点。仅当某项候选实际写入后,才将其设为 applied: true。
  2. 运行所有受影响的测试套件,而不只是之前失败的测试——新选择器可能匹配到非预期的元素,导致之前通过的测试失败。
  3. 对每项测试:通过 → 保留替换。失败 → 还原该行,并将测试标记为需要人工审核。
  4. 输出摘要:自动恢复 N 项,标记 M 项。

门槛: 已恢复的测试通过测试套件。已标记的测试须清晰标出,不得悄然纳入。

阶段 6 — 提交 PR

PR 就是交付成果。标题: chore(tests): selector recovery after <refactor description>。正文(根据 candidates.json 生成,并筛选 applied 项):

## Trigger
<Link to the refactor PR / describe the redesign>

## Summary
- N test files updated   - M selectors changed
- K tests deleted (feature removed)   - L tests flagged for manual review

## Per-file changes
<For each file: a table of line, old, new, score, screenshot URL>

## Flagged for review
<Every unresolved source location, including candidates awaiting review, with verified intent or explicit missing context>

## How to review
- Check each screenshot: does `new` point at the element `old` pointed at?
- For score-3 candidates, verify the region scope is meaningful in the new design.
- For flagged tests, decide: rewrite, delete, or accept a manual selector update.

按照团队的 CI 构建产物 URL 格式内嵌附上截图。有关源码编辑检查,请参阅 references/recovery-scripts.md 和 build-pr-body.ts。

门槛: PR 应能在一次评审中完成。内容过多 → 按区域拆分(每个页面/组件/测试目录一个 PR)。


反模式

  1. 自动应用评分为 0、-1 或 -2 的候选项。 评分为 2 意味着“我们找到了某个元素”。这不是恢复,而是在碰运气——也是恢复后整个测试套件稳定性评分下降的常见原因。只自动应用评分 ≥ 3 的候选项。

  2. 将存在多个匹配项的情况判为“评分 3”。 如果 getByRole(...) 返回多个元素,在通过区域限定将结果缩小到唯一元素之前,评分就不能算 3。将其评为 3 并自动应用,会提交一个指向错误元素的定位器。

  3. 跳过截图。 如果页面有两个区域具有相同的角色和名称,评分为 4 的候选项仍可能指向错误元素。请对照截图和原有业务断言,确认其与旧行为一致;仅凭截图或置信度评分无法证明语义等价。

  4. 使用针对整个内容的字符串替换,而不是按行锚定编辑。 content.replace(oldLocator, …) 只会命中首次出现的位置,会与重复定位器冲突;当报告器渲染的字符串与源码表达式不同时,还会悄无声息地不执行任何操作。请编辑指定的 (file, line)。

  5. 相信自动提取的 POM 封装定位器行号。 JSON 报告器的 error.location 指向失败行;对于 Page Object,这一行属于辅助函数,而不是测试。应用前,请从跟踪操作中重新读取定位器,或在 POM 源码中搜索。

  6. 恢复已删除功能的测试。 重构可能移除了一些流程。请在阶段 2 中映射已删除的路由,并删去这些测试——不要为已不存在的元素重新生成选择器。

  7. 在 CI 中自动合并恢复 PR。 PR 本身就是交付物;这么做的全部目的,是让人类逐项查看每项变更的证据。测试通过后自动合并会违背这一目的——即使选择器指向了一个存在但错误的元素,测试套件也会通过,从而损害信任。必须要求评审者审查。

  8. 把 PR 当作紧急事项。 测试套件失败令人焦急;但更重要的是正确地恢复测试。时间压力会导致用评分为 2 的替代项悄然降低测试套件的质量。


故障模式

症状可能原因修复或检查
尽管 CI 为红色,identify-drift.ts 却找不到任何失败项测试套件在生成 JSON 报告前出错,或者你解析了错误的文件执行 jq '.stats' .drift-recovery/results.json;确认 --reporter=json 已将结果重定向到该文件
提取的行号指向 POM 文件,而不是测试文件定位器封装在 Page Object 中从跟踪操作中读取定位器,或在 POM 源码中搜索渲染后的字符串
generate-candidates.ts 读取到的意图/路由为 undefined阶段 3 的输出缺少推断意图/页面路由字段在阶段 3 中填入这些字段——它们不在报告器的输出中;生成器无法推断
新 DOM 快照缺少客户端渲染的元素在水合前进行了快照在调用 ariaSnapshot() 前,等待应用自身的就绪约定
恢复后平均稳定性评分下降自动应用了评分为 2 的候选项/CSS 候选项在 candidates.json 中撤销评分低于 3 的候选项;最终只应保留角色/标签/testid 选择器(见下方评分器)

验证

在打开 PR 之前,先在恢复分支上按从小到大的顺序运行以下命令:

set -euo pipefail
# 1. Check the recorded applied scores and candidate schema.
node references/score-candidates.mjs .drift-recovery/candidates.json
# Exit 0: all recorded applied scores >= 3. Exit 1: a lower score.
# Exit 2: invalid input or no applied edits to score.

# 2. Preserve the test process exit status and the complete affected-suite report.
PLAYWRIGHT_TEST_BASE_URL=$PREVIEW_URL npx --no-install playwright test --reporter=json \
  > .drift-recovery/post-recovery.json
jq -e '.stats.expected > 0 and .stats.unexpected == 0 and .stats.flaky == 0' \
  .drift-recovery/post-recovery.json
# Account for skipped, blocked and unselected tests separately; do not hide them to get green.

# 3. The PR exists with the evidence body
gh pr view --json title,body -q '.title'   # contains "selector recovery"

评分器会检查记录的分数下限和架构,但不会验证定位器的含义、截图内容或测试执行情况。进程成功退出且受影响的测试套件结果非空,可作为记录的构建已执行的证据;在断定恢复成功之前,请检查新旧意图和排除项。 如果未应用任何编辑,请报告评分器无法评分的结果和未解决的工作,而不要声称恢复成功。


完成条件

  • 每个范围内的失败都必须有据可查:要么有证据表明已恢复,要么说明原因并标记为未解决。只有在确认产品确实移除了相关功能后,才能删除测试。
  • 如果应用了编辑,references/score-candidates.mjs candidates.json 应以 0 退出。必须明确报告缺少输入和未应用任何编辑的情况。
  • PR 已打开(gh pr view 可成功运行),并按文件分组,提供每项更改的证据:置信度分数和截图。
  • 记录受影响测试套件的完整进程退出情况、构建、计数、重试、跳过项和未解决的失败。只有运行结果支持该结论时,才能声称测试套件通过。
  • 在 .agents/qa-project-context.md 中添加简短说明,描述此次重构以及引入的任何新测试模式。

参考文件(位于 references/ 中)

  • recovery-scripts.md — aria-snapshot 捕获、失败分类、可执行的候选项和证据辅助工具、经检查的源代码编辑说明,以及保留未解决位置的 PR 摘要。包含 Cypress 适配限制。
  • score-candidates.mjs — 简易稳定性评分器;输出已应用分数的平均值,以及已应用且分数低于 3 的行数。用于“验证”和“完成条件”。

相关技能

  • test-reliability — 运行时逐项测试修复。适用于单个不稳定测试,而非由重构驱动的大规模更新。共用 0–5 稳定性评分标准。
  • playwright-automation — 从头编写新测试。当重构移除了足够多的功能,以至于测试应重写而非修补时使用。
  • test-migration — 切换框架(Selenium → Playwright)。迁移会重新录制测试;即使两者都会同时影响许多测试,这也不属于选择器漂移。
  • visual-testing — 如果每个 PR 都运行此项,重构的视觉差异就会在合并前被发现。当缺少此类覆盖时,恢复流程可作为后备方案。
  • ci-cd-integration — 将恢复 PR 的验证步骤接入 CI。

评论

支持 Markdown