开发与测试怎么闭环?聊聊把「用例生成技能」交给写代码的那个 Agent

开发与测试的「闭环」经常被理解成流水线接上就算闭环。但如果用例本身是靠猜页面结构写出来的,那条循环里流的其实是噪声。这篇拆一个具体的做法:把「怎么写一条能回放的用例」这件事,做成技能交给写代码的那个 agent。

一、闭环通常断在三个地方

  1. 代码 → 锚点 :开发不知道测试需要什么锚点,页面里全是 div + onClick 和可见文案;测试侧再努力也只能退到文案和层级定位。
  2. 代码 → 用例 :写用例的人要重新理解一遍页面和交互,定位器靠猜。猜错的表现不是当场失败,而是两个月后某次改版突然爆红。
  3. 用例 → 回归:用例入库之后就和代码脱钩了。改版后红了,说不清是产品缺陷还是脚本该维护了,于是没人敢把回归当门禁。

第 1 个断点的解法是把要求写进 agent 的规则文件(CLAUDE.md / AGENTS.md,另一篇单独拆过)。这篇讲的是后两个:让写代码的 agent,顺手把用例也写出来,并且在提交前就证明定位器能解析。

二、交给 Agent 的东西,是一个 9 个文件的技能包

TestDog 项目列表页右上角有两个下载入口,「用例生成技能」就是它:

点下去拿到的是一个 ZIP(generate-testcase.zip),内容是 9 个文件:

bash 复制代码
generate-testcase/SKILL.md                      # 293 行:做什么、何时用、工作流、交付方式、关键规则
generate-testcase/testcase-schema.md            # 273 行:字段表、执行语义、易错点清单
generate-testcase/examples/login-flow.testcase              # UI 断言 + 接口状态码 + URL 断言
generate-testcase/examples/api-json-assert.testcase         # 接口 JSON 字段断言
generate-testcase/examples/websocket-notify.testcase        # WebSocket 发送/接收断言
generate-testcase/examples/upload-scroll-scope.testcase     # 上传 + 容器滚动 + 父作用域
generate-testcase/examples/component-actions.testcase       # 组件语义动作 + 强断言
generate-testcase/references/upload-scroll-scope.md         # 上传/滚动/作用域的专项说明
README.txt                                                  # 装到哪里

打包方式很直白(src/utils/skillBundle.ts):技能源文件在仓库 downloads/skills/generate-testcase/,经 Vite ?raw 内联进前端包,ZIP 用前端里的 stored 格式写入器现拼,下载到的和仓库里那份完全一致 。仓库里还有一份本地安装副本 .claude/skills/generate-testcase,是指向 downloads/skills/generate-testcase 的符号链接 ------ 写技能的人和用技能的人,看的是同一份文件,不存在「文档版」和「实际版」两份。

安装位置 README.txt 里写了两个:

  • 项目级:<被测应用>/.claude/skills/
  • 全局:~/.claude/skills/

装好之后,Claude Code 这类编程 agent 听到「写 E2E 用例 / 接口断言 / WebSocket 断言」就会自动加载它,产出 .testcase 文件。

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

技能正文的第二节是「工作流」,7 步,其中第 2 步是整个设计的核心:

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

写用例的 agent 和写业务代码的 agent 是同一个(或至少是同一台机器上的同一个代码库),它能读到代码。这就是闭环能成立的物理前提:它不需要"猜"页面长什么样。

第 3 步「设计步骤序列」里的几条要求,也是为了让用例在回放时是可靠的:

  • 每步都写 instruction(自然语言)------定位器失效时,自愈模块靠它用 AI 重新定位 ;同时写 description(界面「说明」列);
  • 断言尽量用强断言:能 text_exact / value / count / checked 就不要退化成 visible / text 包含 ------ 精确断言的回归价值更高;
  • 组件库的非原生控件(Ant Design / Element Plus / Vant / MUI 的 Select / DatePicker / Cascader...)用 action: "plugin" 语义动作,原生 click 对弹层和虚拟列表极脆。

定位器优先级也写在技能里,和代码侧的规则是同一套顺序:

定位方式 strategy 稳定性 推荐
getByTestId testid ★★★★★ 首选
getByRole role ★★★★★ 首选
getByLabel label ★★★★★ 强烈推荐
getByPlaceholder placeholder ★★★★ 推荐
getByText text ★★★★ 推荐
getByAltText alt ★★★★ 推荐
getByTitle title ★★★★ 推荐
CSS css ★★★ 兜底
XPath xpath ★★ 最后考虑(技能原话:建议不使用 XPath)

两篇东西共用一张表,闭环才闭合得上:规则负责让代码给出锚点,技能负责让用例按同一顺序取锚点。

还有一层是硬契约:testcase-schema.md 里把枚举写死了,拼错就是导入 400 ------

scss 复制代码
action        ∈ goto/click/fill/press/check/select/assert/wait/raw/plugin/upload/scroll
locator.strategy ∈ role/label/text/placeholder/testid/alt/title/css/xpath/response/websocket
assertion.type ∈ visible/hidden/text/text_exact/value/checked/unchecked/enabled/disabled/
                 count/url/url_exact/response_status/response_body/response_json/ws_sent/ws_received

技能因此明确要求「务必先读 schema 再写文件,照表填写」。经验之谈:让模型凭印象写字段名,textEqual、responseStatus、not_checked 这类拼法一定会出现。

四、闭环里最值钱的一步:提交前先跑一遍

工作流第 6 步,技能用加粗写的:

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

这一步的价值在于把「红」留在开发机上。我把这个预校验真跑了一遍:照 schema 写一条 4 步用例(打开页面 → 用 placeholder 定位输入框填值 → 回车 → 文本断言),目标用 Playwright 官方的 TodoMVC 演示站,按技能的模板写了个 smoke 脚本:

json 复制代码
{
  "format": "testcase", "version": 1,
  "title": "新增待办并校验可见",
  "description": "在 TodoMVC 演示站新增一条待办,校验输入框 placeholder 定位与列表文本断言。\n需在项目设置定义变量:baseUrl。",
  "steps": [
    { "instruction": "打开待办列表页", "kind": "navigate", "action": "goto", "url": "{{baseUrl}}" },
    { "instruction": "在新建待办输入框里填写「买牛奶」", "kind": "action", "action": "fill",
      "locator": { "strategy": "placeholder", "value": "What needs to be done?" }, "value": "买牛奶" },
    { "instruction": "按 Enter 提交这条待办", "kind": "action", "action": "press",
      "locator": { "strategy": "placeholder", "value": "What needs to be done?" }, "key": "Enter" },
    { "instruction": "断言列表中出现「买牛奶」", "kind": "assert", "action": "assert",
      "locator": { "strategy": "text", "value": "买牛奶" }, "assertion": { "type": "visible" } }
  ]
}

真实输出:

ini 复制代码
用例: 新增待办并校验可见 | 步骤: 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,不能跳过后声称通过;跑不了就说明阻塞和未验证项,不要弱化预期、不要无限重试。

五、交付:两条路进来,再跑起来

方式 A(默认推荐) :桌面端 → 目标项目 → 用例列表 →「导入」→ 选 .testcase,支持多选批量:

方式 B(工具运行时) :本地 API 就在 http://127.0.0.1:4123,可以直接推:

bash 复制代码
# 1. 列出项目,拿 projectId
curl http://127.0.0.1:4123/api/projects

# 2. 推送用例(body 即 .testcase 文件内容)
curl -X POST http://127.0.0.1:4123/api/projects/<projectId>/test-cases/import \
  -H 'Content-Type: application/json' \
  --data @login-flow.testcase

要触发回放,桌面端点「运行」,或调 POST /api/runs(testCaseId 必填,可直传 steps 数组、selfHeal 默认 true、headless 默认 false,立即返回 jobId,进度走 WebSocket);批量用 POST /api/runs/batch(固定无头)。技能在这里特意写了一句:推送前确认工具已启动,若不确定就走方式 A,不要因推送失败阻塞交付 ------ 这类「不要为了炫技把交付卡住」的约束,是给 agent 写的纪律。

导入之后必须点一次「运行」。导入只是入库,回放才是验证:

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

六、闭环上真正回流的是什么

跑起来长这样:

三个细节值得说:

  1. 失败步骤自动截图 + 采集 console 与 network(最近 20KB)落库。回流给开发的不是「脚本红了」,而是可以点开看的证据。
  2. 自愈只救动作,不救断言 。定位器失效且动作步写了 instruction 时,会经 CDP 附加 Stagehand 用 AI 重新定位,成功后记 PASSED 并标注「已自愈」;但 upload、scroll 和断言失败不走通用自愈。理由很硬:给动作换定位是修工具,给断言换目标就是把缺陷改成通过。
  3. 生成花模型,回放不花。每步的 Token 消耗在回放里是 0(上图那一列),确定性回放的边际成本几乎为零 ------ 这才是「闭环能每天跑」的前提。生成侧的成本集中在生成记录里,能逐条看到 token 与缓存明细。

七、这套东西的边界

  • 技能依赖 agent 真去读代码。 如果它偷懒凭印象写选择器,产出就是「格式正确但定位靠猜」的用例 ------ 预校验能拦住一部分,拦不住全部。
  • schema 只保证格式合法。 枚举拼错会 400,但断言写得不对(该断列表却断了个装饰性文本)schema 管不了,那是测试设计的事。
  • 不能 mock 接口。 技能和 schema 都明说:接口必须真实调用,接口 / WebSocket 断言正是基于真实请求与响应;为了「不产生数据」去拦截请求,断言就失去意义了。创建类操作的正解是「新建 → 断言出现 → 删除 → 断言已删除」,并用 {{systemTime}} / {{randomNumber}} 这类系统变量造唯一数据。
  • intent 字段(可选)只在工具内置的 AI 生成流程里用(约束断言、验收覆盖率检查),回放期不看它 ------ 手写用例可以不填,别指望它当门禁。
  • 无法验证的场景要如实说。 技能里那句「无法验证或仍失败时如实报告,不无限重试」,是这类工具最容易自欺的地方:把没验证过的用例说成通过,比没有用例更糟。

八、一句话

闭环不是「把测试接进流水线」,而是让写代码的那一侧顺手产出可回放的证据:

  • 规则(CLAUDE.md 里的测试友好 rule)让代码给出锚点;
  • 技能(generate-testcase)让同一个 agent 按同一套优先级写出 .testcase;
  • 预校验在提交前用真实浏览器证明定位器能解析;
  • 回放把失败证据(截图 / console / network)回流给开发,断言永不被自愈掩盖。

四个环节都落在「开发手上」,测试侧接到的才是可维护的基线,而不是一堆需要人肉抢救的脚本。

项目地址:github.com/xianyongwen...,文档:softwing.top/testdog-doc...。

相关推荐
闻缺陷则喜何志丹3 小时前
详细设计说明书的内容要求
算法·测试·存储
冬奇Lab16 小时前
LLM 驱动的自动化测试系列(12):像素 diff 之外,谁真的在让 LLM「看懂」UI 变化
人工智能·测试
ClouGence21 小时前
Katalon Studio、Testim、mabl、CueCast 4 款自动化测试工具怎么选?
测试
stillstream_ink1 天前
一个缺失的状态校验,让 Mall4j 的订单系统开了 4 个后门
mysql·测试
货拉拉技术1 天前
货拉拉业务 Agent 评测平台的工程实践与深度思考
测试
冬奇Lab2 天前
LLM 驱动的自动化测试系列(11):API 测试为什么反而是 LLM 最保守的战场
人工智能·测试
狂师2 天前
爆火!新一代e2e AI测试框架,单日狂揽1500 star
前端·人工智能·测试
冬奇Lab3 天前
LLM 驱动的自动化测试系列(10):自愈定位——当启发式打分撞上语义理解的天花板
人工智能·测试
狂师3 天前
2026年APP自动化测试工具大全,12款主流工具一次看懂
前端·开源·测试