自动化工具 把「用例生成技能」交给写代码的 Agent:一次开发与测试闭环的实践

w471176877 · 2026年10月15日 · 最后由 figer 回复于 2026年10月15日 · 222 次阅读

先说个感受:E2E 用例追不上代码,是我们团队长期的状态。

代码合并了,用例还在写;用例写完了,页面又改了一版。测试同学花在「理解这次改动到底动了哪可测面」上的时间,比写脚本本身还多。我们最近试了一条不太一样的路:把怎么写用例这件事,做成技能交给写代码的那个 agent,让它顺手把用例一起产出,并且在提交之前先证明定位器能解析。这篇记录过程和踩到的东西。

一、为什么以前接不上

回头看,「接不上」其实是三个断点:

  1. 锚点没人给。 页面里全是 div + 文案,脚本能用的定位就只剩文本和层级。
  2. 用例靠猜。 写用例的人重新理解一遍页面,选择器凭肉眼找 —— 猜错不会当场失败,而是两个月后某次改版突然爆红。
  3. 入库即脱钩。 用例进库之后和代码没关系了,红了也说不清是缺陷还是脚本该维护。

第 1 个断点,我们是靠一份「测试友好 rule」解决的(写进 CLAUDE.md,让 agent 写代码时顺手给 data-testid 和语义标签)。这篇说后两个。

二、装进去的是一个技能包

TestDog 项目列表页右上角有两个下载入口,一个是那套测试友好 rule,另一个就是「用例生成技能」:

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

下载下来是个 ZIP,里面 9 个文件:SKILL.md(293 行,工作流 + 交付方式 + 关键规则)、testcase-schema.md(273 行,字段表、执行语义、易错点清单)、5 个示例用例(UI 断言 / 接口 JSON 断言 / WebSocket 断言 / 上传滚动作用域 / 组件语义动作)、1 份上传滚动的专项参考,加一份说明装到哪里的 README。

装的地方有两个选择:项目级 <被测应用>/.claude/skills/(推荐,跟着代码走),或全局 ~/.claude/skills/。装好之后,Claude Code 这类编程 agent 听到「写 E2E 用例」就会自动加载它。

有个细节我挺喜欢:仓库里还有一份本地安装副本 .claude/skills/generate-testcase,是指向技能源目录的符号链接 —— 写技能的人和用技能的人看的是同一份文件,不会出现「文档里这么说、包里那么写」的漂移。

三、它凭什么能让写代码的 Agent 写对用例

技能工作流里第 2 步是整个设计的命门,原文是:

勘察被测应用(关键):阅读前端代码 / 路由 / 组件,确认页面路由与 URL 结构、元素的可用定位锚点、涉及的接口路径片段、涉及的 WebSocket URL 片段。
不要凭空猜测选择器——基于代码里真实存在的 testid / role / label / 文本来写。

因为写用例的 agent 和写业务代码的 agent 在同一个仓库里,它能读到代码。这就是"闭环"能成立的物理前提:它不需要猜页面长什么样。

剩下几条也是冲着「回放要可靠」去的:每步都写 instruction(自然语言,定位器失效时 AI 自愈靠它重新定位)、断言尽量用强断言(能 text_exact/value/count 就别退化成 visible)、组件库的非原生控件用 action: "plugin" 语义动作(原生 click 对弹层和虚拟列表很脆)。

还有一张定位优先级表,和代码侧那份 rule 是同一套顺序:data-testid → role → label → placeholder → text → alt → title → css → xpath,技能原话是「建议不使用 XPath」。

这点挺关键:两边共用一张表,闭环才闭合得上 —— 规则负责让代码给出锚点,技能负责让用例按同一顺序取锚点。

另外 testcase-schema.md 是一份硬契约,枚举拼错导入直接 400。所以技能明确要求「先读 schema 再写文件」。血泪经验:让模型凭印象写字段名,textEqual、responseStatus、not_checked 这种拼法一定冒出来。

四、提交前先跑一遍(我最喜欢的一步)

工作流第 6 步:

本地 Playwright 预校验(强烈推荐):写完文件不立刻交付,先用本机 Playwright 跑一遍目标 URL 上的每一步 locator,确认能解析到元素;不通过则根据证据修正 locator / 拆步并复验;无法验证或仍失败时如实报告,不无限重试。

为了看看这一步到底顶不顶用,我把预校验真跑了一遍:照 schema 写一条 4 步用例(打开页面 → placeholder 定位输入框填值 → 回车 → 文本断言),目标用 Playwright 官方的 TodoMVC 演示站,按技能给的模板写了个 smoke 脚本。真实输出:

用例: 新增待办并校验可见 | 步骤: 4
  [0] goto -> PASSED  (url=https://demo.playwright.dev/todomvc/#/)
  [1] fill -> PASSED  (placeholder "What needs to be done?" matched 1 / filled="买牛奶")
  [2] press -> PASSED  (placeholder "What needs to be done?" matched 1 / key=Enter)
  [3] assert -> PASSED  (text "买牛奶" matched 1 / assertion visible)

结果: 全部定位器可解析,用例可交付

事情本身很朴素 —— 把每步 locator 在真实浏览器里执行一遍,报「解析到几个元素」。但意义在于:这条用例在进测试工具之前,就已经被证明定位器可用。有问题留在开发机上,而不是变成 CI 里一条说不清的红色。

技能对这一步的边界也写明了:模板只是基础动作示例、不完整覆盖回放引擎;含 upload/scroll 的必须用实际回放或等价 smoke,不能跳过后声称通过;跑不了就说明阻塞项和未验证项,别弱化预期。这种"允许说做不到"的约束,对 agent 挺重要。

五、导入、跑起来、看证据

导入有两条路:桌面端「用例列表 → 导入」选文件(支持多选批量),或者工具运行时直接推本地 API:

curl -X POST http://127.0.0.1:4123/api/projects/<projectId>/test-cases/import \
  -H 'Content-Type: application/json' --data @login-flow.testcase

技能里特意补了一句:推送前确认工具已启动,若不确定就走 UI 导入,不要因推送失败阻塞交付。

导入只是入库,验证要主动点「运行」:

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

用例详情:13 步,策略列 placeholder / role / text / xpath

注意这张图第 10 步的 xpath //tr[contains(@class,'ant-table-row ... —— 这条用例的代码侧没给锚点,脚本只能这么写。所以说回来,技能再强也只能在代码给出的锚点里挑,两个下载件是一对。

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

运行记录:21 条运行,状态通过/失败,Token 消耗 0,自愈列,逐步 PASSED 日志

回流给开发的东西比"红了"具体得多:失败步骤自动截图 + 采集 console 与 network(最近 20KB)落库,点开就能看。定位器失效且动作步写了 instruction 时会 AI 自愈(经 CDP 附加 Stagehand 重新定位),但 upload/scroll 和断言失败不走通用自愈 —— 给动作换定位是修工具,给断言换目标就是把缺陷改成通过。

成本上分两半:回放那一半 Token 消耗是 0(上图那列),生成的成本都记在生成记录里:

生成记录:状态 / 步骤数 / Token 消耗(含缓存)/ 起止时间

顺便说个真实观察:生成记录里 出错 和 已取消 的那几条,Token 消耗是 0 —— 说明成本是花在"真的跑完的那次生成"上的,失败的尝试并不计费。确定性回放 + 成本集中在生成,这两点加起来,才让「每天跑一遍」在经济上说得通。

六、三个体会

  1. 技能里那句「不要凭空猜测选择器」是这事能不能成的命门。 Agent 必须真去读代码;靠印象写定位器,产出的就是"格式正确但定位靠猜"的用例。
  2. 提交前的那一遍预校验,比 CI 里发现划算太多。 它不检查业务对错,只回答一个问题:这些定位器在这版页面上能不能解析。定位器级的问题就该在这层挡掉。
  3. 确定性回放让"高频跑"变成可能。 回放不调模型、Token 为 0,动作失败才自愈,断言失败保留证据 —— 这样回归才有资格当门禁,而不是"跑起来太贵所以一周一次"。

七、我们踩到的坑

  • 枚举拼错直接 400。 response_status 不是 responseStatus,text_exact 不是 textEqual,unchecked 不是 not_checked。技能要求先读 schema 是对的。
  • url / url_exact 断言不等跳转语义。 点击跳转后紧跟 url 断言,会因为跳转没完成而失败 —— 前面加一个 visible 断言或 wait。这个坑我们复现过好几次。
  • role 定位器要同时填 value 和 role(同值),漏了就变成 getByRole(undefined)。
  • 一个 .testcase 只有一条用例,多场景拆文件。别想着一个文件塞五个场景。
  • 创建类操作要清理,但不要 mock 接口。 正解是「新建 → 断言出现 → 删除 → 断言已删除」,数据用 {{systemTime}} / {{randomNumber}} 这类系统变量造唯一值;拦截请求就没意义了,接口断言本来就是靠真实请求响应的。
  • intent 字段是给工具内置 AI 生成流程用的(约束断言、验收覆盖),回放期不校验,手写用例可以不填。

八、留个问题

你们的 E2E 用例是测试写还是开发写?有没有人试过让 agent 在提交 PR 的时候顺手带上用例?以及那道门(lint / CI / review 清单)是怎么设的 —— 是卡"定位器能不能解析",还是卡"断言对不对"?评论区聊聊。

参考:

共收到 1 条回复 时间 点赞

登录遇到验证码怎么办

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