自动化工具 用一条 rule 让编程 Agent 顺手写下 testid:聊聊「测试友好」这件事怎么落地

w471176877 · 2026年10月16日 · 57 次阅读

前几天遇到一个特别典型的场景:前端同事重构了一版列表页,只动了结构和文案(表格多了一列,按钮文字从「历史」改成「阶段历史」)。当天下午回归,红了一片。

查下来不是环境问题,也不是用例写得马虎 —— 是定位器掉档了。

一、先看清「掉档」长什么样

这是那条用例的步骤表(13 步,从登录到打开阶段历史弹窗):

用例详情:选择策略列里 placeholder / role / text / xpath 混用

选择策略 这一列从上到下依次出现 placeholder、role、接口响应、text,到第 10 步变成了:

xpath  //tr[contains(@class,'ant-table-row ...

要点的那个「首行里的历史按钮」没有稳定锚点,脚本只能写成「第 N 个带某个 class 的 tr 里的按钮」。重构一动结构,它就断了。

再看另一条能跑的(Playwright 官方 TodoMVC 演示):

脚本编辑页:打开页面 / 填写 placeholder / 按键 Enter / 断言 text 买牛奶

输入框靠 placeholder 定位,断言靠可见文案「买牛奶」。能跑,但锚点都压在文案上 —— 加 i18n、改措辞,脚本一起改。

把两条放在一起看,结论很直白:脚本能用什么定位,取决于代码给了什么锚点。 测试侧再怎么调等待、调超时,也调不出一个页面里不存在的 data-testid。

二、为什么定位会自动「掉档」

我们用的工具(TestDog)里,定位是按优先级阶梯去找的,顺序大致是:

data-testid → 无障碍 role+name → 表单 label → placeholder → 可见文本 → 图片 alt → title → css → xpath

从上往下,越靠上越稳、越靠下越脆。脚本会挑「第一个能命中的」——所以只要代码给了 testid,它根本不会去用文案;反过来,代码什么都没给,它就只能一路掉到 css / xpath。

也就是说,定位稳不稳这件事,其实是在写业务代码的时候决定的,跟测试用例写得好不好关系不大。

三、换个思路:把要求写进 agent 的规则文件

既然决定权在代码侧,那能不能让写代码的那个「人」自己带上这个习惯?如果这个「人」是编程 agent(Claude Code、Codex 之类),答案是可以 —— 只要把要求写进它会读的规则文件里。

TestDog 的项目列表页右上角有两个下载入口,其中一个就是干这个的:

项目列表页:新建项目 / 用例生成技能 / 测试友好rule

「测试友好 rule」下载下来是一个 2.6KB 的单文件 .md(test-friendly-code.md),用法在文件开头:

将本文件内容(或其中适用章节)追加到项目根目录的 CLAUDE.md 或 AGENTS.md。主流编程 agent 会自动加载该文件,编写代码时遵循其中的约定。

之所以要放进 CLAUDE.md / AGENTS.md,是因为这两个文件是 agent 在写这段代码的那一刻会读进上下文的。写成测试规范文档放在 wiki 里,人不会每次都翻,agent 更不会。

规则一共三节 12 条,摘一下要点:

  • 稳定定位器:关键交互元素给 data-testid(其次稳定唯一的 id/name);值要语义化且长期稳定(submit-order-btn、product-item-{id}),禁止自增序号 / 时间戳;不要依赖纯文案、纯 CSS 层级与索引;重构时同步更新定位属性。
  • 可断言状态:异步过程要有可等待信号(loading 消失、列表渲染、按钮恢复);成功 / 失败要有稳定提示元素;列表就绪要有可断言的标志;错误文案不要拼随机内容。
  • 语义化与可访问性:用 button/a/dialog 这些标签,不要 div + onClick;图标按钮给 aria-label;浮层用正确的 ARIA 角色;纯装饰图标 aria-hidden。

四、真正落地时,有用的三条和容易忽略的一条

这类规则文件,抄进项目的成本是零,难的是它到底解不解决问题。我们实际试下来,有几点体会:

1)data-testid 不是唯一手段,「零成本锚点」优先级更高。
<button>提交</button> 本身就能让脚本用 role=button + 名字命中 —— 这就是上面截图里第 4 步 role 策略的来源,不需要额外加属性。图标按钮加个 aria-label,比写 :nth-child(3) 强太多。data-testid 该留给语义上确实分不开的元素:列表项、动态区域、长得一样的卡片。

2)「稳定」的判定标准是「值不随数据和结构变」,不是「有没有这个属性」。
data-testid="btn-1712345678"、id="row-1" 这种,加了等于没加 —— 用它定位,第二条数据进来就乱。product-item-{id} 这种带业务主键的形态最实用:数据变了它还能唯一定位,改版不动它。

3)规则第 4 条(重构时同步更新定位属性)最容易被忽略,也最贵。
上面那次一片红,根因就是这条。它没法靠工具自动保证 —— 我们后来在 code review 清单里加了一条:动了关键交互元素的文案 / 结构 / 类名,检查一下有没有测试在依赖它。规则能让 agent 记得,人 review 才是最后一道。

4)比定位更容易被忽略的是「可等待信号」。
E2E 的随机失败大多不是浏览器不稳,而是页面没有可等待的完成信号,测试只能靠固定等待去赌,赌早赌晚都是红。规则第二节要的就是让代码交付这个信号:loading 消失、空态出现、列表渲染、toast 弹出,随便哪个稳定可见都行。

顺带说个设计取舍:我们用的工具里,动作定位失败允许 AI 自愈(元素挪了位置照样点得到),但断言失败不做自愈。理由很朴素 —— 给动作换定位是修工具,给断言换目标就是把失败改成通过,那是把缺陷改没了。所以「状态可断言」这件事必须由代码来保证,测试不敢替它兜。

五、闭环:规则给锚点,技能写用例

规则只管「代码长什么样」,另一半是「用例长什么样」。同一排的另一个下载入口「用例生成技能」(generate-testcase 技能包)就是这一半:解压放进被测项目的 .claude/skills/,编程 agent 就会读技能和用例 schema,直接产出可导入的 .testcase 文件,而且它会按同一套优先级去代码里找锚点。

产出的用例导回来就是回归集:

用例列表:导入用例 / 批量导入入口,19 条用例,版本列标记「最新」

跑起来是这样,注意 Token 消耗那一列:

运行记录:21 条运行,状态通过 / 失败,Token 消耗 0,步骤表里自愈一列为空

回放是确定性的,不调模型(所以 Token 消耗是 0),4 个步骤依次 PASSED;一旦某一步定位失败,自愈那一列才会有内容 —— 它同时也是「页面结构是不是又变了」的一个信号。

整条链条其实就是三句话:

  1. 写代码时,规则让 agent 顺手留下锚点;
  2. 写用例时,技能让 agent 按同一套优先级找锚点;
  3. 跑回归时,确定性回放,动作失败才自愈,断言失败保留证据。

六、还没解决的部分(求经验)

这套东西目前能解决的,是「新写的代码」。几个还没搞定的地方,想听听大家的做法:

  • 规则没有强制力。 它只是写进 CLAUDE.md 的约定,agent 仍然可能写出 div + onClick,人 review 也会漏。我们在考虑用 lint 兜底(禁止裸 div 绑 click、要求图标按钮必须有 aria-label、约束 testid 命名),但还没落地,也没想清楚怎么避免误报。
  • 存量代码要一次性补齐。 老项目里到处是纯文案 / 层级定位,得让 agent 按规则批量补一遍锚点,补完还要跑回归确认没改坏行为 —— 纯人力活。你们是怎么分批做的?按页面还是按模块?
  • 接口契约不在规则里。 现在这份规则只管前端 DOM 和页面状态,接口路径、响应字段的稳定性没写进去。做接口断言的团队,会把它写进同一份规则文件吗?

抛个问题:你们有没有把「测试友好」变成可检查的约束(CI、lint、review 清单)?还是主要靠人盯?评论区聊聊。

参考:

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册。