前端改完别只看截图:用 hwatu 把 Agent 的页面验证做成可复查的短循环
让编码 Agent 改一个按钮颜色、补一个表单校验,最常见的交付证据仍是“已完成”或一张截图。问题不在于截图没有价值,而在于它通常不能回答三个工程问题:当前页面是否真加载了新代码?某个交互后的状态是否成立?人需要接手时,能否接到同一份浏览器会话,而不是重新打开网页、重新登录、重新描述问题?
hwatu 是一个面向本地前端开发内循环的 WebKit 验证工具。它不是用来批量爬网页的浏览器集群;其文档把定位限定为“Agent 修改代码后,在同一台机器上打开、检查并继续”的渲染验证循环。项目采用 AGPL-3.0 许可证,安装包是静态二进制加发行版的 WebKitGTK 依赖;它还可以通过 CLI、Unix socket 或 MCP 配置接入工作流。
本文不把它当成“又一个浏览器自动化工具”介绍,而是讨论怎样把页面验证拆成可复查的证据链:页面状态、可见结果、控制台反馈,以及必要时的人类接手。这也是它和只执行点击脚本的区别。
截图为什么不足以成为完成条件
截图只能说明某一时刻出现了某些像素,无法自然表达断言。例如“保存后显示成功提示,再刷新仍保留新昵称”至少包含输入、提交、状态变化和持久化四个可失败环节。若 Agent 只点击一次并截图,服务端 500、前端乐观更新、异步请求失败或刷新回滚,都可能被遗漏。
更实用的完成条件应该是可观察的页面事实,而不是行动本身:
- 点击保存不是证据;
#status显示预期文本才是; - 没有看到报错不是证据;读取本轮控制台与失败请求才是;
- “看起来像旧版”不是结论;与基线渲染比较并定位差异区域才是;
- 遇到验证码或需要业务判断时,不应让 Agent 继续猜,而应把同一会话交给人。
hwatu 的 snapshot 返回页面文字和可交互元素,expect 会轮询某个选择器直到条件成立或超时;console 读取 JavaScript 异常、控制台输出和失败请求。它们分别对应“页面有什么”“目标状态是否已达成”和“过程有没有显性错误”。将三者一起保存到任务记录中,比单独贴图更容易复盘。
一个可复现的本地验证回路
先按项目 README 的方式安装并检查环境。setup --dry-run 只展示可连接的客户端而不改配置;确认后再选择项目级或用户级配置。
curl -fsSL https://raw.githubusercontent.com/hongnoul/hwatu/main/scripts/install.sh | bash hwatu doctor hwatu setup --client claude --scope project --dry-run
假设本地开发服务器运行在 http://localhost:3000,目标是验证设置页的显示名可以保存。下面的命令不是抽象伪代码,而是项目 agent 文档列出的 CLI 形式;示例把“操作”和“验收”分开:
hwatu --headless http://localhost:3000/settings hwatu wait-load --until dom hwatu type 'input[name=displayName]' 'Test User' --enter hwatu expect '#status' --text '保存成功' hwatu console
这里 --headless 避免打断正在工作的桌面用户,wait-load --until dom 则将等待点明确放在 DOMContentLoaded。expect 默认会轮询,因而比固定 sleep 更适合处理正常的异步渲染波动。实际项目中应把 #status 和“保存成功”替换为页面真实、稳定的选择器与文案;不要为了让命令通过而临时添加只给测试使用的 UI 文案。
若需求要求刷新后仍存在,可以再做一次状态验证:先取得打开窗口的 ID,导航或刷新,再检查输入值或页面文字。对于可访问性和布局检查,先用 snapshot 找到交互对象的 ref,再用 click --ref 触发真实指针事件,能减少“选择器匹配到错误元素”的风险。文档也说明当匹配有歧义时会报出匹配数量,而非静默点击其中一个。
把多步调用压缩为一次检查,但别丢掉语义
每一步都启动命令的确会增加 Agent 的工具调用和上下文消耗。hwatu 提供 check:由守护进程完成打开、等待、执行表达式、截图、收集控制台并关闭或回收窗口。对于只需确认一个页面状态的场景,可以将一次完整检查写成:
hwatu check http://localhost:3000/settings \
--until dom \
--eval 'document.querySelector("#status")?.textContent' \
--shot=/tmp/settings-check.png
关键不是追求一条命令,而是让 --eval 的结果与验收条件绑定。比如它应该读取状态文本、特定元素是否存在或关键字段值,而不是只返回 document.title。截图适合留给审阅者看布局,结构化返回值适合让 Agent 判断下一步。
项目的基准文档给出了在特定测试机与本地 fixture 上的测量:守护进程预热后,check 加表达式与截图的中位数为 39 ms;通过持久 socket 客户端为 35 ms。文档同时给出对照与硬件、页面、测量日期,且明确指出冷启动、内存占用和不同的 Playwright 使用方式会改变结论。因此这些数字应被理解为该项目的可复现实验结果,不是所有项目都能获得的性能承诺。工程上更重要的是:用单次、带结构化结果的验证替代五个散落调用,可以减少窗口泄漏和“只执行到一半”的失败模式。
基线比较与动态页面的边界
视觉回归并不等于“像不像”。hwatu 的 diff 可以比较两个渲染结果并给出匹配分数、差异区域和热力图;seek 用于将动画固定在特定时间点,motion 列出或观测动画。它们适合用在设计系统组件、过渡效果或响应式断点的回归检查中。
不过,给每个页面设像素级基线常常适得其反。日期、实验开关、广告、随机数据、未固定的动画和字体加载时序都会制造无意义差异。正确顺序应当是:先断言业务状态,再对稳定区域做视觉比较;先固定可控时间源或测试数据,再讨论容差;最终把“差异出现在哪里”交给人审核,而不是把某个百分比当成绝对质量结论。
另一个边界是反自动化页面。challenge 只检测 CAPTCHA 或反机器人界面并返回结构化状态;它不会解题、调用第三方打码服务、注入响应令牌或绕过访问控制。如果确实需要人工处理,执行 hwatu focus 可将原先的后台或无头会话变成可见窗口。人完成操作后,Agent 仍能在相同 session 上继续检查。这个限制不是功能缺失,而是把权限边界写进自动化流程:需要人确认的地方,不能被“自动化成功率”掩盖。
适合放进 Agent 规范的最小约定
要让工具真的提升交付质量,团队不必为每个页面写庞大脚本。可以在 AGENTS.md 或类似项目说明中约定四件事:前端变更后说明用户路径;列出可观察的成功状态;检查本轮控制台;验证码、付款、不可逆写入等场景必须人工接手。hwatu README 也建议把“用户路径和能证明成功的可观察结果”写具体,而不是泛泛要求“测试页面”。
这样,Agent 的报告就可以从“已修改并截图”升级为:“进入设置页,提交后 #status 达到目标文本;刷新后字段仍为新值;控制台未出现新增失败请求;截图仅用于人工确认布局。”即使未来换成别的浏览器或测试框架,这套证据模型仍然成立。
还应把失败结果当作正常产物保留下来。一次 expect 超时并不自动说明产品有缺陷:选择器可能已改名、开发服务器尚未热更新、接口响应可能慢于验收窗口,或页面的成功条件本来就定义错了。报告中应同时写下目标断言、实际快照、控制台错误和运行环境;这样开发者能先区分“测试假设失效”与“功能回归”,而不是让下一位 Agent 从一张失败截图重新猜测。对需要重试的异步流程,优先收紧到具体元素和业务状态,而不是盲目拉长全局超时。这样既能避免偶发网络慢导致的误报,也不会掩盖真正卡住的交互。
hwatu 最适合的不是替代端到端测试套件,而是填补 Agent 编码与正式测试之间的空白:让每次小改动都以一个真实渲染页面的、可检查的结果结束。当验证条件清晰、会话可交接、失败可解释时,前端 Agent 才更接近能被工程团队信任的协作者。