site logo

Marico's space

使用案例:从前端到测试自动化的 Agentic 工作流构建

AI技术与应用 2026-10-01 14:50:15 7

最近折腾了一个从前端触发测试修复的 Agentic 工作流,踩了不少坑,这篇把整个过程说清楚。

我们前端每次改个 UI 组件,就引发一场小噩梦:测试挂了,测试自动化(TA)团队被叫过来,然后就是没完没了的来回拉锯。

我琢磨着能不能通过在工作流中引入一个无头 Agent来减少这种摩擦——让 Agent 自动接手失败的测试并尝试修复。

不过搞这种自动化确实是个硬骨头——要设计有意义的工作流、处理可能变成死胡同的跨依赖关系、还有一路上各种未知问题。这篇文章里,我会带你走过问题分析、设计决策和权衡,以及端到端的实现过程。

问题背景

在前端这边,我们配置了一些 CI 步骤,其中一项就是运行测试自动化。但这些测试经常因为前端改了 HTML 结构或者用户流程而挂掉。一旦发生,就得走下面流程图里的步骤。

流程图展示了前端和 UI 测试自动化之间的跨团队循环。当前端 PR 触发测试 A、B、C 失败时,UI 测试自动化响应跳过测试 A–C,随后创建并合并 PR 来修复它们。同时,前端的 CI 通过了因为测试被跳过,所以前端 PR 被合并到 main。之后 UI 测试自动化再次运行并创建修复 PR;在此期间存在一个警告状态,自动化“通过”了但测试在 main 上仍被跳过,直到修复被合并且测试在 main 上稳定。

简单总结:

  1. 前端:做了破坏性修改,测试挂了。
  2. TA:跳过相关测试。
  3. 前端:CI 管道通过,分支合并。
  4. TA:取消跳过测试、修复、提交 PR 并合并。

这个工作流在每个前端 feature 分支都会触发。步骤必须按顺序执行,整个流程涉及至少四个工程师(写代码的和 review 的)、上下文切换,还有跨团队依赖。而且中间有一段测试覆盖率会丢失:测试通过了,只是因为被跳过了。

做这个实验时,我们决定先聚焦在一个具体且容易修复的场景:前端改了 UI 组件,需要选择器变更的情况;只用冒烟测试作为卡口。下面给你更多背景信息。

UI 测试中的选择器是什么?

在前端应用中,我们实现用户交互的组件。这些组件可以通过文本、输入框 placeholder、role 或者屏幕阅读器的 aria-* 属性来访问,最后才会 fallback 到 data-testid。这些标识符——选择器(或者某些库叫的 locator)——被 Testing Library、Playwright 或 Cypress 这类测试库用来执行自动化测试,模拟用户操作。

只要用来标识测试组件的文本、label 或其他内容被改了,测试就会挂。你可能觉得这是小概率事件,但实际上在以下场景经常发生:

  • 如果前端正在使用设计系统并迁移到另一个,很可能破坏选择器。迁移期间尤其常见。
  • 如果文本变了,有时还有位置变化,也很可能出问题。
  • 任何HTML 变更都可能破坏选择器。

什么是冒烟测试?

冒烟测试是一小套端到端测试,只覆盖应用最关键的流程。核心思想不是测所有东西,而是快速回答一个问题:应用还能正常工作吗?因为数量更少、执行更快,它们比完整测试套件成本更低,适合在每个 feature 分支运行——对于这个实验来说,也意味着 Agent 要处理的失败更少(且更稳定)。如果想深入了解,这篇文章是个不错的起点。

约束条件

这个案例不是放之四海而皆准的方案,但能给你一些启发。我们有这些特定配置:

  • 两个独立代码库:有些项目把前端和测试自动化放在同一个代码库。我们的项目不是这样——前端一个仓库,测试自动化另一个仓库。
  • TA CI 无法本地构建前端:这意味着 TA 管道无法在和前端相同的环境下测试。前端在本地构建后触发冒烟测试。而 TA 不行——只能针对已部署的 QA 环境运行。因此,同样的测试可能在 FE 管道失败但在 TA 通过(反之亦然)。这就是为什么 Agent 的输出之一是Not reproduced(无法复现),后面会详细说。
  • 每个仓库用不同的 CI/CD 工具:前端 CI 管道跑在 Drone,CD(部署)跑在 GitHub Actions(GA)。TA 只有 Drone 的 CI 管道——不部署任何东西,只是一些报告,由 Drone 管理。

解决方案

流程图展示跨团队循环——在左侧前端,PR 创建后测试 A/B/C 失败,Drone 发送 raw repository_dispatch endraw 事件通知 UI 测试自动化。在右侧 UI 测试自动化,通知触发一个流程,其中自动化 Agent(在虚线框内标注为“自动化”)更新失败的测试,检查决策点“测试已修复?”,如果修复了就打开 PR 供审查,然后人工审查并合并 PR,反馈结果使整体管道继续。之后回到前端,CI 通过,PR 被合并。

整体思路是:当前端有变更时,CI 管道运行冒烟测试,如果失败,就通过 GitHub REST API 向 TA 仓库发送 repository_dispatch 事件来触发 TA 的工作流 Agent,Agent 然后尝试修复未通过的测试。如果成功,会创建一个 PR 供人工检查。如果这个 PR 被合并,前端 CI 管道就能通过。

几点需要考虑的:

  • 当 TA PR 合并时,不会自动触发前端 CI。需要用户手动重新触发 Drone 的任务。以后可以收紧这个流程,在 TA 的分支合并到 main 时自动触发。
  • 当 TA PR 合并到 main 时,会导致前端 main 和其他分支测试失败(因为他们还没跟上 feature 分支的最新代码)。这是已知的权衡,可以接受,因为我们加速了流程,同时控制了节奏(因为需要人工按钮触发合并 TA PR 到 main)。

经过大量来回讨论确定了高层设计之后,我开始琢磨细节。

自动化流程设计

要实现这个,需要回答几个问题:

  1. 时机:前端可以在每次测试失败时触发 Agent,但应该吗?
  2. 平台:Agentic 工作流应该跑在哪里——Drone 还是 GitHub Actions?
  3. 跨仓库/CI 通信:FE 和 TA 仓库之间的连接如何实现?
  4. 输出结果:如果 Agent 修不了测试怎么办?能修怎么办?测试通过了怎么办?

时机:Agent 什么时候应该运行?

考虑到前端的每个 feature 分支都会触发 CI 管道,加上成本效率和可控性(尤其是测试这个自动化期间),我选择在特定条件下触发 Agent。需要 feature 分支部署到特定环境(我们有超过 4 个环境,可以同时测试多个 feature 分支)。我们称这个环境为QA 环境。

有这个环境也是必须的,因为约束条件中 TA 只能针对已部署的环境运行冒烟测试。

平台:Agent 应该在哪儿跑?

自动化设计有多个步骤,其中之一就是决定在哪儿触发 Agentic 流程。如约束条件中所述,我们同时用了 Drone 和 GA。

我们的 Drone 已经够拥挤了——它在所有分支包括 main 上运行进程。我意识到用 GA 可以更好地控制触发时机和方式——它们的界面对测试和手动触发更友好。而且我可以为 Agent 创建独立的流程,不和其他进程混在一起。

基于这些原因,我决定用 GA 来运行 Agentic 工作流,因此连接点应该是GA 触发 Agent。

连接各个点:仓库之间怎么通信?

这个自动化的发起者是前端,需要满足两个条件:冒烟测试失败 且 已部署到 QA 环境。由于前端 CI 跑在 Drone、前端 CD 跑在 GA,这两个条件在不同地方检查。我有两个选项:

  1. 在 GA 中 QA 环境部署后运行冒烟测试,然后发送 dispatch 事件到 TA。
  2. 在 Drone 中,冒烟测试步骤之后,检查是否已部署到 QA 环境,然后发送 dispatch 事件到 TA。

第一个选项成本更高——需要把冒烟测试步骤引入 GA,会在 CI 和 CD 管道中重复。

第二个选项只是一个简单检查。因为我们用 GitHub labels 来追踪部署到哪个环境。已经有一个流程:给 PR 添加 label 就会触发部署到那个环境。

因此,我们可以在冒烟测试步骤之后检查 GitHub label。如果冒烟测试失败且部署 label 设置为 QA 环境,那么Drone 发送 dispatch 事件到 TA。

由于 CI 和 CD 在不同环境执行,这里有一个权衡:Drone 可能收到并识别到 label,但 GA 的部署流程可能失败,或者 Drone 检查时分支实际上还没部署完成。我接受这些权衡,因为最坏情况不过是测试跑在没有前端变更的环境上,导致误报失败。

关于竞态条件,label 一旦添加,通常 CD 在 5 分钟内完成。整个 CI 管道通常需要 15 分钟,检查作为 CI 管道最后一步,时间上足够缓冲。

输出结果:Agent 应该报告什么?

前端创建了一个破坏冒烟测试的新功能,已部署到 QA 环境,dispatch 事件已发送。接下来呢?Agent 应该做什么?

整个目标是修复被破坏的冒烟测试,方法是通过更新选择器。为此我做了一些决定:

  • 我们总共有超过 40 个冒烟测试。对于这个自动化,不需要运行整个套件。因此,payload 应该携带哪些冒烟测试在前端侧失败的信息。
  • 在尝试修复测试时,我看到一些可能的结果,总体来说:
    • Agent 能够修复测试。
    • Agent 无法修复测试。
    • 测试通过了。

三个主要结果,加上两个边缘情况:

  • 如果测试已修复,则在 TA 侧创建 PR,并向前端 PR 发送评论,内容为"已修复",带上TA PR 的链接。
  • 如果测试之前运行已修复,则向前端 PR 发送评论,内容为"已修复",不带 TA PR 链接。
  • 如果测试未修复,则不创建 PR,但向前端 PR 发送消息,内容为"可能的回归"。
  • 如果测试通过,则向前端 PR 发送消息,内容为"无法复现"。
  • 如果环境失败(例如 QA 环境不可达),Agent 中止,不向前端 PR 发送消息——错误只记录在 GA 运行日志中。

虽然可以在 GA 工作流页面看到所有日志,但我想尽可能透明,在前端 PR 上给出整个流程结果的消息。

然后,大部分定义都设定好了,接下来可以看看 Agent 具体怎么做的,以及围绕它的基础设施。

设置前置条件

如你所见,实现这个流程有多个步骤。

创建 Agent 工具链

工具链(harness)是围绕模型的一切,塑造 Agent 如何工作——指令文件、技能、命令、脚本和护栏——让它能可预测地运行,不需要人一步步引导。

代码库的初始状态完全没有为 AI 做准备。为了不扩大项目的影响范围,我决定只创建足够实现这个 POC 的工具链。创建了两个资产:

  • AGENTS.md:有 CLAUDE.md 指向它,使 Claude 和 OpenAI 模型都能读取。常规的指令文件,注入到每个 Agent 会话中(不深入讲这个,网上有很多文档)。
  • 选择器规范技能:定义了选择选择器的指导原则。有一个层级顺序,Agent 必须遵循。例如,第一选择应该是 getByRole,然后是 getByLabel,以此类推。

用这些,我在自己的 Agent(本地 Claude Code CLI)上测试了它的表现。

也创建了其他命令,但它们属于自动化本身,所以会在后续章节详述。

让测试环境无关化

另一个问题是测试无法在 QA 环境运行。数据耦合到特定环境,选择器也是。这和 AI 或自动化无关,但会影响开发能力。因此,我做了额外的准备工作:创建函数为每次测试运行 setup/create 和 teardown/delete 数据,以及让选择器环境无关化。

实现流程

整个流程的可视化图表如下:

此流程图展示了一个自动化测试选择器修复系统,有两个相互连接的工作流。在前端侧,PR 打开后冒烟测试失败,系统检查代码是否部署到 QA 环境。如果是,系统发送一个 dispatch 事件。

前端:发送事件

流程从前端开始,我自然先从那里开始,但工作量不大:我在 drone.yml 的冒烟测试步骤之后加了一个新步骤:

- name: Smoke test commands:
# Smoke tests instructions - name: Dispatch selector repair environment: GITHUB_APP_TOKEN: from_secret: GITHUB_APP_TOKEN REPAIR_ENVIRONMENTS: QA_Env commands: - sh ci/drone/dispatch-selector-repair.sh depends_on: - Smoke test when: status: - failure

(注:这是示例代码片段,不能直接使用。)

作为变量,需要 GitHub App token 来启用跨仓库通信,REPAIR_ENVIRONMENTS 是启用触发此自动化的环境镜像列表。目前只有 QA 环境启用。

我可以在 YAML 文件中添加所有指令,但提取到 ci/drone/dispatch-selector-repair.sh 中以分离关注点并提高可读性。在这个脚本中做了以下事情:

  • 检查部署环境是否有效
  • 检查 GitHub token 是否有效
  • 收集失败的测试。如果没有找到失败测试的 JSON 报告就退出。
  • 获取 commit SHA、PR 编号
  • 准备 payload 并发送 frontend-smoke-failed 事件

然后,TA 这边接收这个事件和 payload。

测试自动化:运行 Agent

为了接收事件,创建了一个 GA 工作流。当收到事件时触发 selector-repair.yml:

repository_dispatch: types: [frontend-smoke-failed]

也可以手动触发,虽然主要是为了测试目的,在评估 Agent 性能时用到。

这个工作流:

  1. 调用 Agent 之前运行确定性检查:用正则验证 payload(环境、前端 PR、SHA 和失败的 specs)防止注入,检查 API keys,重新检查部署环境并确认 portal 访问。早期失败可以避免不必要的 Agent 调用,并增加安全层。
  2. 如果一切正常,安装 OpenAI Codex CLI,用于无头 Agent。选择的模型是 GPT-5.4。目标是让工具链足够好,不需要更强大/更高推理(也更贵)的模型如 Opus 和 Astra。
  3. 触发 Agent 运行修复,使用 codex exec 命令,运行修复选择器命令(如下所述,正式名称 repair-selectors-ci)
  4. 输出修复摘要并上传证据(HTML 文件和图片)
  5. 在前端 PR 上评论反馈

如你所见,LLM 只涉及一个特定步骤。之前和之后都是确定性脚本,既降低成本也提高质量。Agent 只在能实际在启用环境中运行测试时才会触发;Agent 完成后,脚本会整理 PR 评论的消息体返回给前端。

修复选择器命令

除了设置章节的共享工具链,CI 运行还添加了两个命令。即:

  • 修复选择器命令:由自动化本身触发的命令。
  • 打开修复 PR 命令:在修复选择器命令内部触发的单独命令。

修复选择器命令是核心 Agent 逻辑所在。总结一下,它做了:

1. 读取环境变量输入:要测试的环境、触发运行的前端 PR,以及失败 specs 列表。

2. 管理持久分支:每个前端 PR 一个分支,在多次运行中复用和追加,而不是每次重建,因为同一分支的每个 commit 都会触发新运行。它永远不会 rebase 或 force-push,因为 commit 历史作为审计日志。另外,如果前端侧有新 commit,GA 会通过 concurrency: cancel-in-progress 取消当前运行,不会继续处理过时代码。如果 commit 已经推送了,就留在那里。

3. 确定测试什么:要么是特定的失败 specs,要么是手动运行整个冒烟套件。如果之前的修复运行已经在持久分支上提交了修复,就被归类为"已修复"。

4. 将每个失败分类到三个桶之一:

  • Rot(腐化) - 元素还在,只是用不同方式定位了(role/name/DOM 位置变了)→ 安全地自动修复。
  • Regression(回归) — 元素缺失或底层数据错误 → 升级给人工,报告为可能的回归。
  • Environment failure(环境失败) — portal 本身不可达/坏了,所以失败没有意义 → 完全中止分类,而不是误报每个 spec 都坏了。不会向前端发送任何反馈;错误只记录在 GA 运行日志中。

5. 修复 rot 分类的选择器,通过探测实时页面的可访问信号(role、label、text、placeholder 等),选择精确解析到唯一元素的最佳选择器,并验证修复实际让测试变绿后才保留。

6. 运行质量门 — lint/prettier 检查,清理任何临时/探测文件 — 然后才允许提交。

7. 打开或更新 draft PR(从不合并)—— 这是打开修复 PR 命令。

8. 写入结构化 JSON 摘要(repair-summary.json)作为返回调用工作流的唯一通信渠道 —— 列出什么被修复了、什么作为回归升级了、什么无法复现、之前的运行已修复了什么,以及任何环境错误 —— 有严格规则规定哪些场景哪些字段必须为空,确保面向人的报告永远不会误导。

之后,payload 被发送回 selector-repair.yml,由"评论反馈到前端 PR"最后一步处理。这是确定性代码,执行我们之前在"输出结果:Agent 应该报告什么?"章节讨论过的反馈给前端仓库。

总结

在这个实验之前,一个破坏性 UI 变更意味着跳过测试、在覆盖缺失的情况下合并,至少四个工程师跨两个团队协调。使用这个流程后,前端直接在 PR 中获得反馈,TA 审查一个 draft 修复而不是从头写,当 Agent 成功时,测试不再需要在 main 上挂着跳过。

对我来说最大的教训是:Agent 是工作中最小的部分。大部分精力都花在周围的东西上:决定它什么时候应该运行、连接两个仓库和两个 CI 工具、使测试能在共享环境运行,以及写确定性检查让 LLM 只在真正能帮上忙时才调用。

还有改进空间,我相信这可以作为超越本地开发使用 AI 的自主系统的案例研究。未来是 Agent 在云端工作,不需要人驱动每一步——为此,我们需要扎实的工具链和护栏,持续观察输出以提高质量、降低成本并加快速度。