研发协作
UI 重构后批量修复失效测试选择器:用 aria…
当一次 UI 重构或改版导致大量测试因选择器失效而超时失败时,离线批量重新生成选择器并打包成一个可人工审核的 PR。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
UI 重构后批量修复失效测试选择器:用 通用
解决思路
- 先判断是否适用:单个测试失败用 test-reliability;重构改变了流程/语义则用 playwright-automation 重写;框架迁移(Selenium→Playwright)用 test-migration;只有同一事件导致 10 个以上测试失败才用本技能。
- 先查 .agents/qa-project-context.md,若存在则直接采用其中已确认的 E2E 框架、选择器策略和已知脆弱区域,跳过已回答的问题。
- 明确五个前提:触发原因(计划中重构/已发布改版/依赖升级/CSS 迁移)、影响范围(单组件/单页/全应用)、当前选择器策略(data-testid 则简单,CSS/XPath 则 30–60% 需要换策略)、是否存在旧 DOM 参照(CI trace 产物、staging 构建、Storybook、git 历史)、失效定位器是内联还是包在 Page Object 中、以及 PR 由谁审核。
- Phase 1 快照旧 DOM:按优先级从最近一次绿色 CI 的 trace 产物(npx playwright show-trace traces/checkout.zip,读每个 action 的 DOM snapshot 面板)、重构前 commit 的 Storybook(git checkout <PRE_REFACTOR_SHA> 后用 page.content()/ariaSnapshot() 导出)、仍运行旧版本的 staging 构建、或组件 git 历史中获取;用 aria 快照(await page.locator('body').ariaSnapshot())而非原始 HTML;输出到 .drift-recovery/old/<page-or-component>.aria.yml。
- Phase 2 快照新 DOM:在重构后的构建(Vercel/Netlify 预览部署、本地 dev server 或 PR 分支 CI)上运行相同页面,快照前先 await page.waitForLoadState('networkidle') 等待 hydration,否则 SSR 页面会漏掉客户端渲染元素;输出到 .drift-recovery/new/<page-or-component>.aria.yml,并确保每个旧快照都有对应的新快照;若某路由在新构建中 404,说明该流程已删除,把其测试标记进 Phase 6 的删除清单。
- Phase 3 识别失效选择器并推断意图:用 JSON reporter 对新构建运行每个测试文件,逐条捕获 file、line、旧定位器字符串、错误类型(timeout vs assertion)、推断意图和页面路由;drift 失败是 TimeoutError: locator.* exceeded,要与断言失败区分;推断意图必须阅读测试上下文(对定位器做了什么操作、随后是什么断言)并写成简短字符串(如“提交订单”“读取订单总额”),这是候选生成的关键输入;页面路由也不在 reporter 中,需手动映射到对应的新快照文件;输出为按文件分组的表格。
- Phase 4 生成替换候选并按 0–5 分打分:按策略阶梯从优到劣生成——(1) 重构新增的 data-testid,5 分;(2) 页面上唯一的 getByRole + 可访问名称,4 分;(3) 表单字段的 getByLabel,4 分;(4) 角色+名称有多个匹配时用区域限定(getByRole('region', { name }).getByRole(role, { name }) 或 .filter({ hasText }))并确认唯一后才给 3 分;(5) 仅可见文本 getByText,2 分;(6) 变更结构上的 CSS class,1 分;(7) 无安全替换,0 分并标记人工处理;只有 5/4/3 分可自动应用,2/1/0 分绝不自动应用;输出 .drift-recovery/candidates.json,每条含 { file, line, oldLocator, selector, score, rationale, screenshotPath }。
- Phase 5 应用并验证:在 feature 分支上按 (file, line) 逐行替换(不要用 content-wide 字符串替换,reporter 的渲染字符串通常与源码表达式不一致,且会只替换首个匹配或在重复定位器上冲突),对实际写入的候选设置 applied: true;运行整个受影响测试套件而非仅之前失败的测试;通过的保留,失败的还原该行并标记人工审核;输出 N 个自动恢复、M 个标记的汇总。
- Phase 6 提交 PR:标题为 chore(tests): selector recovery after <refactor description>;正文由 candidates.json 中 applied 为 true 的条目生成,包含触发原因链接、汇总(更新文件数/修改选择器数/删除测试数/标记人工审核数)、按文件的变更表(行号、旧、新、分数、截图 URL)、标记审核清单(无 ≥3 分候选的测试及其推断意图)、审核指引(对照截图确认新选择器指向旧选择器原本指向的元素;验证 3 分候选的区域限定在新设计中是否有意义;对标记测试决定重写、删除还是手动更新);截图通过团队 CI 产物 URL 内联附加;PR 过大时按页面/组件/测试目录拆分成多个 PR。
- 避免使用 Playwright 布局选择器 :near()、:right-of()、:left-of()、:above()、:below() 做消歧——官方已弃用且可能被移除,1px 布局偏移就会改变匹配,应改用区域限定或 getByRole().filter()。
- 不要自动应用 0/1/2 分候选:2 分只是“找到了某个元素”,是赌博而非恢复,通常会导致恢复后套件稳定性评分反而下降。
- 不要把有多个匹配的歧义定位器标为 3 分:getByRole(...) 返回 >1 个元素时,必须先用区域限定收窄到恰好一个才算 3 分,否则会指向错误元素。
- 不要跳过截图:同一页面上有两个角色+名称相同的区域时,4 分候选也可能指向错误元素,逐条截图是唯一能发现语义漂移的检查。
- 不要信任 POM 包装定位器的自动提取行号:JSON reporter 的 error.location 指向失败行,对 Page Object 来说是 helper 而非测试;应从 trace action 读取定位器或 grep POM 源码。
- 不要为已删除的功能恢复测试:Phase 2 中映射出被删除的路由后,应删除对应测试,而不是为不存在的元素重新生成选择器。
- 不要在 CI 中自动合并恢复 PR:PR 本身就是交付物,重点是人工查看逐条证据;测试通过就自动合并会让指向错误但存在元素的绿色套件侵蚀信任,必须要求审核人。
- 不要把该 PR 当作紧急任务:失败的套件感觉紧急,但正确恢复才重要,时间压力会催生悄悄降低套件质量的 2 分替换。
- 提交 PR 前按从小到大顺序验证:运行 node references/score-candidates.mjs .drift-recovery/candidates.json 确认 applied 且 score < 3 的行数为 0;运行 PLAYWRIGHT_TEST_BASE_URL=$PREVIEW_URL npx playwright test --reporter=json | jq '.stats.unexpected' 必须为 0;运行 gh pr view --json title,body -q '.title' 确认标题含 selector recovery。
- 完成标准:每个范围内测试在新构建上通过、被明确标记人工审核并附原因、或因功能删除而被删除;score-candidates.mjs 报告 0 个 applied 且 score < 3 的候选;PR 已开启且含逐条证据(每条变更的置信度分数和截图,按文件分组);恢复分支上 jq '.stats.unexpected' 返回 0;在 .agents/qa-project-context.md 中补充一段说明本次重构及引入的新测试模式。
适用边界 · 注意事项
- 单个测试偶发失败或 flaky,而非重构导致的大批失败——应使用 test-reliability 做运行时单测试修复。
- 重构改变了流程或语义,而不只是 DOM 结构——应按规格用 playwright-automation 重写测试。
- 框架迁移(如 Selenium 迁移到 Playwright)——应使用 test-migration 重新录制测试,这不是选择器漂移。
- 任何地方都不存在旧 DOM 参照——必须先捕获一份(Phase 1)或缩小范围,否则这退化为“重写测试”。