Agent 说“代码已完成”,我为什么还不敢合并:一套可复现的自动验收流程
Agent 说“代码已完成”,我为什么还不敢合并:一套可复现的自动验收流程
Agent 用几分钟改完代码,测试结果一片绿色,最后还附上一句:“任务已完成,可以合并。”
我差点就信了。
直到打开页面才发现:登录框旁边确实多了一个“显示密码”按钮,但点击后毫无反应;切到移动端视口,按钮又被挤出了输入框。
代码改了,测试也过了,任务却没有真正完成。
这暴露了 AI 编程里一个容易被忽略的问题:
当 Agent 既负责写代码,又负责告诉你“代码没问题”,这真的算验收吗?
传统开发至少包含开发、测试和验收。很多 AI 编程工作流却只自动化了“开发”,然后顺手把“是否完成”的判断权也交给开发 Agent。
问题不在于 Claude Code、Cursor、Codex 或其他工具谁更强,而在于我们缺少一套工具无关、证据优先的验收机制。
本文用一个可以复现的任务,拆解如何让编程 Agent 在交付代码的同时,提交测试结果、界面截图和结构化变更说明。
编程 Agent 自动验收:假完成、自动验收产物与最终验收包对比示意图
配图说明:上图为脱敏复现示意图,不代表生产数据。左侧是 Agent 声称完成但实际存在问题的页面,中间是自动验收产物,右侧是最终验收包。三个面板分别对应本文要求保留的三组关键截图。
Agent 的“假完成”到底有多少种
先看本文贯穿始终的任务:
给登录页增加“显示/隐藏密码”按钮,同时保证键盘操作、移动端布局和原有登录功能正常。
这是一个很小的需求,却同时涉及状态管理、按钮行为、无障碍属性、响应式布局和旧功能回归,非常适合暴露 Agent 的“假完成”。
常见问题至少有以下几类。
代码改了,但需求没有真正实现
Agent 加上了眼睛图标,却没有正确更新密码输入框的 type 属性。页面看起来有变化,按钮实际上只是装饰。
还有一种情况是修改了错误组件:项目里同时存在旧版登录页和新版登录页,Agent 改了未被路由引用的文件。Git diff 很漂亮,运行中的页面却没有任何变化。
测试通过,但用户看到的结果不正确
单元测试可能证明“点击后状态由 false 变成 true”,却无法证明按钮有没有被遮挡、图标是否溢出、文案有没有错位。
这正是第一个失败样例:
- 组件测试通过;
- 桌面端按钮可以点击;
- 移动端截图显示按钮超出输入框边界;
- 最终状态不能是
PASS,而应是NEEDS_REVIEW或FAIL。
新功能正常,旧功能却被破坏
例如,Agent 为眼睛图标增加了一个 ,却忘记设置:
type="button"
在表单里,按钮默认可能触发表单提交。用户只是想查看密码,页面却提前发起登录请求。
另一个常见回归是键盘操作失效:原来在密码框按回车可以登录,修改后由于事件处理不当,这条路径断了。
这就是第二个失败样例:
- 截图看起来完全正常;
- 显示和隐藏密码也可以操作;
- 旧有“按回车提交登录”的回归测试失败;
- 视觉证据无法替代功能证据。
修改范围失控
为了增加一个按钮,Agent 顺手重构表单、替换图标库、调整全局样式,甚至升级依赖。
这些改动不一定错误,但会显著增加评审范围和回归风险。此时即使测试通过,也不能自动合并。
因此,“代码已修改”只能算交付声明,不能算任务完成。
我把“任务完成”重新定义成三份证据
我的做法是要求 Agent 提交一个可被机器检查、也方便人快速复核的“验收包”。
统一完成标准可以写成:
任务完成 =
自动化测试通过
+ 关键页面截图生成
+ 结构化变更说明完整
+ 无阻断级异常
三种证据各自解决不同问题。
| 验收证据 | 主要解决的问题 | 不能替代什么 | | 测试结果 | 功能、逻辑和旧功能是否按预期运行 | 不能证明视觉效果正确 | | 页面截图 | 页面状态、布局、文案和响应式表现是否正确 | 不能证明内部逻辑完全可靠 | | 变更说明 | 改了什么、为什么改、风险在哪里 | 不能替代代码的实际执行结果 |这三项不是为了把流程变复杂,而是在减少低价值的人工检查。
没有验收包时,人需要逐文件看代码、启动项目、手动点击页面,再猜哪些功能可能受影响。有了验收包后,机器先筛掉明显失败的任务,人只需要重点检查:
- 关键状态截图是否符合需求;
- 高风险
diff是否合理; - Agent 声明的未覆盖项能否接受;
- 是否存在安全、架构或产品层面的风险。
测试告诉你“它能不能工作”,截图告诉你“用户看到什么”,变更说明告诉你“代价和风险是什么”。
从 Agent 改代码到自动验收,流程怎么跑起来
完整工作流不依赖某一款编程 Agent。无论代码由哪种工具生成,后面的验收步骤都可以保持一致。
flowchart TD
A[需求输入] --> B[Agent 修改代码]
B --> C[运行 lint、类型检查、测试与构建]
C -->|失败| D[携带日志返回 Agent 修复]
D --> B
C -->|通过| E[启动应用并执行浏览器操作]
E -->|失败| F[携带截图、控制台日志返回]
F --> B
E -->|通过| G[生成截图、Git Diff、变更说明]
G --> H{规则判定}
H -->|PASS| I[人工复核或进入合并流程]
H -->|FAIL| D
H -->|NEEDS_REVIEW| J[人工判断风险]
对应到登录页案例,执行顺序如下:
1. Agent 修改登录表单组件;
2. 执行 lint、类型检查、单元测试、回归测试和构建;
3. 启动本地服务;
4. Playwright 打开登录页,填写密码并点击按钮;
5. 分别在桌面端和移动端执行关键操作并截图;
6. 收集 Git diff 和文件变更统计;
7. 生成 acceptance-summary.md;
8. 根据证据返回 PASS、FAIL 或 NEEDS_REVIEW;
9. 失败时,把日志和截图交还给 Agent,而不是允许它直接宣布完成。
一段可复制的统一验收脚本
下面是一个最小版本。项目命令可以根据实际技术栈调整:
#!/usr/bin/env bash
set -euo pipefail
ARTIFACTS_DIR="artifacts"
SERVER_PID=""
mkdir -p "$ARTIFACTS_DIR"
cleanup() {
if [ -n "$SERVER_PID" ]; then
kill "$SERVER_PID" 2>/dev/null || true
fi
}
trap cleanup EXIT
npm run lint
npm run typecheck
npm run test -- --run
npm run build
npm run dev > "$ARTIFACTS_DIR/dev-server.log" 2>&1 &
SERVER_PID=$!
npx wait-on http://localhost:3000/login
npx playwright test tests/acceptance/login.spec.ts \
--reporter=html
git diff --stat > "$ARTIFACTS_DIR/diff-stat.txt"
git diff > "$ARTIFACTS_DIR/changes.diff"
echo "Acceptance checks completed."
这里有一个很重要的细节:不要只检查测试进程是否返回成功,还要保留日志、报告和截图。
否则失败信息只存在于 Agent 的上下文里,任务结束后就很难复核。
用 Playwright 验证真实用户操作
登录页的最小验收测试可以这样写:
import { test, expect } from "@playwright/test";
test("用户可以切换密码的显示状态", async ({ page }) => {
await page.goto("http://localhost:3000/login");
const password = page.getByLabel("密码");
const toggleButton = page.getByRole("button", {
name: "显示密码",
});
await password.fill("example-password");
await expect(password).toHaveAttribute("type", "password");
await toggleButton.click();
await expect(password).toHaveAttribute("type", "text");
await expect(
page.getByRole("button", { name: "隐藏密码" })
).toBeVisible();
await page.screenshot({
path: "artifacts/desktop-after.png",
fullPage: true,
});
});
移动端可以增加一个视口用例:
test("移动端密码按钮没有溢出输入框", async ({ page }) => {
await page.setViewportSize({
width: 390,
height: 844,
});
await page.goto("http://localhost:3000/login");
const password = page.getByLabel("密码");
await password.fill("example-password");
await page.getByRole("button", {
name: "显示密码",
}).click();
await expect(password).toHaveAttribute("type", "text");
await page.screenshot({
path: "artifacts/mobile-after.png",
fullPage: true,
});
});
需要注意,截图本身不会自动理解“是否好看”。它首先是一份可追溯证据,可以由人检查,也可以在后续接入视觉回归工具。
三道验收关卡,如何避免“形式上通过”
流程搭起来不难,难的是防止 Agent 钻流程的空子。
第一关:不能只跑 Agent 自己写的测试
如果 Agent 修改实现后,又根据自己的实现编写测试,就可能形成“自问自答”。
例如,需求要求按钮支持键盘操作,Agent 却只写了鼠标点击测试。测试当然可以通过,但需求没有被完整覆盖。
建议把测试分成四层:
- 基础检查:
lint、类型检查、构建; - 功能测试:组件测试、单元测试、接口测试;
- 回归测试:验证原有登录、错误提示、回车提交等功能;
- 黑盒验收:从用户视角操作页面,不依赖内部实现。
其中,关键验收条件应尽量在开发开始前固定下来。例如:
- 默认状态下密码不可见;
- 点击按钮后密码可见;
- 再次点击后恢复隐藏;
- 按钮支持键盘聚焦和触发;
- 按回车仍可提交登录;
- 桌面端和移动端均无明显溢出;
- 不修改登录接口和鉴权逻辑。
验收条件先写,代码后写。这样 Agent 就不能根据结果临时降低标准。
第二关:截图必须对应具体状态
“已经生成截图”没有意义,关键是截了什么。
如果任务是修改登录页,Agent 却只交一张首页截图,这份证据等于不存在。
建议每张截图都绑定以下信息:
- 页面 URL;
- 视口尺寸;
- 操作步骤;
- 操作后的目标状态;
- 对应测试用例;
- 浏览器控制台是否出现异常。
产物目录可以统一为:
artifacts/
├── desktop-before.png
├── desktop-after.png
├── mobile-after.png
├── test-report.html
├── dev-server.log
├── diff-stat.txt
├── changes.diff
└── acceptance-summary.md
涉及交互的任务,至少保留操作前后两个状态;涉及响应式布局时,至少覆盖桌面端和移动端。
这能直接抓住“测试通过但布局异常”的失败案例。
第三关:变更说明不能只写“已优化”
一份合格的变更说明,必须能回答三个问题:
1. 需求中的每一项是如何实现的?
2. 具体修改了哪些文件?
3. 还有什么风险没有被验证?
推荐固定使用下面的模板:
## 任务目标
为登录页增加密码显示与隐藏功能,同时保留原有登录行为。
修改内容
- 修改
LoginForm.tsx,增加密码可见状态;
- 增加切换按钮和无障碍标签;
- 明确设置按钮类型为
button;
- 新增 Playwright 黑盒验收测试;
- 增加桌面端与移动端截图。
验证结果
- lint:通过
- typecheck:通过
- unit test:通过
- build:通过
- Playwright:通过
- 桌面端截图:已生成
- 移动端截图:已生成
风险与未覆盖项
- 尚未在 Safari 真机环境验证;
- 尚未执行视觉基线像素对比;
- 未修改登录接口及鉴权逻辑。
无关改动
无。
如果 Agent 无法明确说明“无关改动”,或者 Git diff 中出现依赖升级、全局样式重写,就应该返回 NEEDS_REVIEW,而不是自动通过。
用结构化结果代替一句“完成了”
为了让 CI 或其他 Agent 继续处理,可以同时输出机器可读的 JSON。
下面是本文最小案例的一份示例结果,不代表生产统计或行业基准:
{
"status": "PASS",
"checks": {
"lint": "passed",
"typecheck": "passed",
"unit_tests": "passed",
"build": "passed",
"e2e_tests": "passed",
"screenshots": 2
},
"changed_files": 3,
"risks": [
"Safari real-device testing not performed"
],
"unrelated_changes": false
}
三种状态可以这样定义:
PASS:强制检查全部通过,证据完整,没有阻断风险;FAIL:测试、构建、启动或关键交互失败;NEEDS_REVIEW:自动检查通过,但存在较大改动、未覆盖环境或需要主观判断的问题。
不要把所有无法判断的情况都硬塞进 PASS。承认不确定性,本身就是可靠工程流程的一部分。
这套流程真正节省的,不只是时间
如果没有连续记录足够多的任务,就不应该编造“效率提升多少”或“缺陷下降多少”。对于个人项目或小团队,更适合先记录这些可核验指标:
- Agent 首次声明完成后,自动验收是否通过;
- 每个任务经历了多少轮返工;
- 人工验收需要检查多少个文件;
- 自动发现了多少个回归问题;
- 失败来自代码、环境、依赖还是页面行为;
- 有多少任务进入了
NEEDS_REVIEW。
建议至少连续记录一批任务,再判断流程是否真正有效。单次成功只能说明案例跑通,不能证明它适用于所有项目。
但即使暂时没有统计数据,工作方式已经发生变化:
过去,人需要替 Agent 找按钮点不动、项目起不来、旧功能失效等低级错误;现在,自动流程先筛掉这些明显失败,人只需要处理更高价值的问题。
自动验收仍然不能替代什么
这套流程不是万能的。
以下问题依然需要人做最终判断:
- 品牌视觉是否符合预期;
- 动画和交互是否自然;
- 架构取舍是否合理;
- 安全风险是否可以接受;
- 需求本身是否值得实现;
- 大范围重构是否符合长期维护目标。
尤其是支付、权限、鉴权、数据删除等高风险功能,测试通过不等于可以直接上线。安全审查、权限边界和业务复核仍然不可省略。
自动验收不是取消人工评审,而是让人从“替 Agent 找低级错误”,升级为“判断高价值问题”。
三档落地方案:今天就能开始
不必一上来就搭建复杂的多 Agent 平台。
入门版
适合个人项目和小型代码库:
- 一条统一测试命令;
- 一份固定格式的变更说明;
- 每次任务必须附上
Git diff。
实用版
适合有前端界面的日常项目:
- 加入 Playwright 黑盒测试;
- 自动生成桌面端和移动端截图;
- 保存测试报告、启动日志和变更统计;
- 输出
PASS / FAIL / NEEDS_REVIEW。
进阶版
适合准备接入 CI 的团队:
- 开发失败后自动携带证据回传;
- 开发 Agent 与审查 Agent 分离;
- 验收规则只读;
- 回归测试禁止开发 Agent 随意修改;
- 未生成完整验收包时禁止合并。
如果你也想复刻这套流程,可以先把“改代码”和“验收结果”拆成两个独立调用。需要为开发、验收和总结环节接入不同模型时,可以通过 api.884819.xyz 统一配置调用,再把测试、截图和变更说明固定成验收模板。
8848AI 平台用户名加密码即可注册,不需要邮箱验证;平台内置 AI 对话,注册后可以直接使用。国产模型如 Deepseek、千问等完全免费,没有月租和订阅,其他模型按量付费。
建议先在非生产项目中验证模型兼容性、调用成本与数据安全策略,再逐步接入 CI 或正式代码仓库。
新用户注册即送体验token。我现在已经不再追问 Agent:“你真的改好了吗?”
因为它的回答并不重要。测试结果、运行截图和变更记录,才是任务是否完成的答案。
但这套流程还有一个更棘手的问题:如果测试、截图和变更说明也全部由同一个 Agent 生成,它会不会为了通过验收,悄悄降低测试标准,甚至修改原本失败的用例?
下一篇,我会继续拆解:如何把开发与验收权限分开,用独立审查 Agent、只读验收规则和 CI 门禁,避免 AI 在一场自己出题、自己答题的考试里轻松拿满分。
本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。#AI编程 #编程Agent #自动化测试 #Playwright #软件工程 #人工智能 #8848AI #AI教程