先走完一次验收
用 Claude Code、Codex 或 Cursor 改网页,常见的回报是:格式化、类型检查和测试都绿了,再加一句 Implemented and verified。打开页面,仍可能看见筛选后留着旧数据、保存后一刷新就回到旧值、弹窗在屏幕上放不下。
Timothy Ogbemudia 想补上的是这一圈:改代码,跑已有测试,启动应用,使用刚改的功能,看结果,修完再使用一次。已有测试回答它们覆盖的场景。浏览器验收从这次改动出发,看正在运行的应用。
先读这次差异,再决定验什么。改筛选,就去用筛选。改重置,先把状态改掉,再按重置,看界面是否回到原样。改保存,要修改、保存、再刷新,读回当前值。点过保存按钮,说明不了刷新之后还在。
证据按问题分开。地址、按钮是否禁用、对话框是否打开,读页面结构和无障碍状态。重叠、裁切、窄屏换行,看截图。模态框可以待在页面结构里,屏幕上却点不到。两类有时都要。
交互式地图那次,新增的单元测试在旧代码上也能通过。被当作证据的,是代理打开应用后看到的光标变化和复位。这些例子用来说明规则,仓库里没有同名测试,也没有统计这种失误有多少次。
报告要让人能复查
只把浏览器交出去,报告仍可能只有三句:Opened /route. Page loaded successfully. Browser verification complete。页面打开了,没有人规定它必须证明什么。
交回来的内容要能让另一个人复查:走了哪条路径,预期是什么,实际看到什么,证据在哪,相关测试结果,修了什么,复验是否重走,哪里受阻或没测。
- 功能状态:读当前值、选中、禁用、弹窗是否打开。
- 页面效果:看截图里的遮挡、裁切和重叠。截图单独证明不了刷新后的字段值。
- 接口和报错:页面显示成功、接口却失败时,仍要写进报告。
作者的演示是一个故意写坏的注册表单:空表单提交后什么都不发生,正确行为应出现错误提示。三个 Playwright 测试。一次运行指向本机 4173 端口,结果是 2 passed、1 failed、0 blocked、0 not tested。失败项是“空表单应显示必填错误”,Playwright 记录 getByRole('alert') 从未出现。证据种类包括截图、录像、日志和 trace,文件本身没有附在文章里。
这次报告的 Runner 是 katalepsis 0.1.0、Playwright 1.63.0、Node 22.22.0,只对应该份报告。预览 commit 没有报上来,标成 unconfirmed。报告目录时间是 2026-10-19T08-21-25-288Z。这是一次运行,没有重复试验。
把规则留在项目里
短说明每次都会被读到,长步骤等用到再读。Codex 每次会话读取 AGENTS.md,Claude Code 读取 CLAUDE.md。AGENTS.md 只写一条完成标准:用户看得见的工作,必须在正在运行的应用里验证,否则不算做完。构建、类型检查、单元测试和端到端测试都不能代替这一步。验证产生的截图和日志不提交。交接前清掉这次为验证启动的进程和文件。
具体步骤放进 browser-review 技能。技能是一个含 SKILL.md 的文件夹。会话开始时,代理只看见名称和短描述,任务对上了才读全文。示意目录里还有 pr-review 和 grill-me,作者没有说明后两者做什么,也没有测量两份文件各占多少上下文。
更新后的技能要求先跑 katalepsis。字段值、持久化、控制台、网络和跨视口布局这些它还查不了的,再退回普通浏览器操作。第一个按新说明运行的 Codex 会话去找一个叫 katalepsis 的技能,没找到,就退回旧的浏览器复查。作者随后写明:katalepsis 是命令行工具,browser-review 才是技能。这里只记了一个会话。
写代码的那个代理容易只走自己打算改的路径。作者另请一个没有这段会话记录的代理,自己读差异,自己判断影响范围。
那次复查要把端到端地址改成可由 E2E_BASE_URL 配置。种子数据里三张图片仍写死在本机 8080 端口。应用改在 8081 上跑之后,三个指向 8080 的请求以 net::ERR_CONNECTION_REFUSED 失败,页面静默画出三张回退卡片。只检查“有卡片”的测试,真图片和回退卡片都能通过。没有原始网络日志或截图文件。
构建 katalepsis 时,负责测试的代理在清理时关掉了机器上每一个 Playwright 浏览器,自己启动的只是其中一个,它在总结里承认了。清理因此收成一条:只停、只删自己创建的进程、容器、截图、trace 和其他产物。前后各看一次 git status --short。作者没有给出命令、浏览器数量或发生时间。
启动按项目原来的方式。先看 package.json 的 dev 脚本,再看 README。只有别的办法都跑不起来,才用仓库里已经有的 Docker,不新写一份配置。不能为了省事关掉认证、改 .env、造假服务,或加一条只为测试存在的路由。文档里的测试账号仍登录不上,就停,并写明浏览器复查受阻。关掉认证再看一遍,查的已经是另一个应用。
给 Agent 接上真浏览器
Playwright MCP 交给代理的是一个真浏览器:打开本机页面,读页面,定位控件,点击,输入,核对变化,再截图。pnpm playwright test 跑的是已经写好的场景。两件事各答各的。
Playwright MCP 需要 Node.js 20 或更新版本。安装后面的 katalepsis 需要 Node.js 22.12 或更新版本,还要有 Git。
弱报告和强报告的差别就在指令。没有人规定必须证明什么时,代理会用“页面加载成功”结束。指令要把它按回这次改动的最终状态。
用 katalepsis 整理证据
前面的流程可以单独用。作者后来把它做成开源工具 katalepsis。到 2026年10月20日发表时,它还不在 npm 上。安装器会克隆仓库并构建,需要 pnpm 10,或随 Node 提供的 corepack。安装脚本没有被另行抓取核验。价格、速率和供应商是否在线,文章没写。
katalepsis run 跑项目里已经有的 Playwright 测试,排序、脱敏、打包,并以 Playwright 的断言为结论。katalepsis verify 接受自然语言检查,模型使用调用方自己的 Anthropic、OpenAI 或 OpenRouter 密钥去开真浏览器。
指向预览之后,它交出一份 HTML、一份可以贴进拉取请求的 Markdown,和一份 JSON manifest。作为 MCP 服务器时只在指定文件夹里工作,工具是 verification_run_tests、verification_check_browser、verification_get_report。
另一次 verify 用 OpenRouter 上的 anthropic/claude-haiku-5.5,对着同一个演示,结果仍是 2 passed、1 failed、0 blocked。完整 checks.json 里三项检查指向同一个空表单缺陷。正文示例只展示了两个检查对象,那份报告时间是 2026-10-10T12-45-05-343Z,早于上面的 run。
结果有四种:passed、failed、blocked、not tested。预览不可用时,它在启动 Playwright 之前只请求预览地址一次。遇到 502、503、504、网络错误或网关状态,选中的测试一起变成 blocked,Playwright 不启动。指向没有服务的本机 4999 端口时,输出是 0 passed、0 failed、3 blocked,原因是 connect ECONNREFUSED。记的是一次受阻,三条测试不会各自记成失败。
指向预览自身、来源不可达,算 blocked。第三方 API 失效仍是 failed。浏览器起不来也是 blocked。第一轮没有会话记录的复查返回 15 项发现,其中一条就是这条分类。
公开前作者跑了自己的两个项目。Conclave 是 95 passed。另一个更大的应用是 137 passed、1 failed,失败断言是缩放级别略有偏差。那次失败原来就在,他没注意到,katalepsis 把它摆到不能略过的位置。两个项目都写死了 baseURL,测试没有访问他传入的地址。工具现在会警告这种情况。对错误服务器给出的绿色结果,比没有运行更糟。警告原文和两份配置没有贴出。
一条 LinkedIn 评论指出,本地检出的 commit 不一定是预览构建的 commit。报告现在同时记下两者,标成 confirmed、mismatched 或 unknown。不匹配会挡住一次本来可以通过的运行。预览可以用名为 x-commit-sha 的响应头,或名为 commit-sha 的 meta 标签,把 commit 交出来。也可以传 --preview-commit。评论者身份和加入日期没有给出。
还有一条真实分支,把 GeoJSON 做成主下载按钮,CSV 和 KML 放进下拉菜单。verify 会把页面内容发给模型供应商,作者的规则要求先同意,所以代理在运行前停了。用种子合成数据并明确同意后,检查通过:看得见一个主按钮,打开下拉后有其他格式,报告里有截图和 trace。含真实数据的页面仍走回退。检查数量、模型、耗时和原始报告没有给出。发送了哪些字段、留多久,文章也没写。
模型说通过,程序来核对
verify 里的模型可以点击、输入、阅读。它不能自己把一件事叫成通过。能用的工具只有 navigate、snapshot、click、fill、press、assert、finish。assert 由 katalepsis 在浏览器里执行,记下类似 a3 的编号。finish 必须引用这些编号。
要通过,必须引用 katalepsis 实际跑过、结果为通过、并且能证明模型动手之前页面上还没有的内容。否则是 blocked。decideOutcome 会把声称的通过改掉。被引用的断言有失败、只引用了隐藏断言或地址断言、引用的内容在行动前已经可见、最后一条被引用断言之后还有未引用的失败、或者引用了从未运行的编号,都会变成 blocked,后一种代码是 no-evidence。这是修剪过的规则,不能当作完整源码。
每个检查会开两份起始页,放在两个独立浏览器上下文里。模型只操作一份,另一份不动,用来看被断言的东西是不是一开始就在。作者试过的一个模型,曾拿始终存在的电子邮件字段标签来证明表单有效,得到 trueAtStart: true,不能单独支撑通过。模型名称、次数和失败率没有给出。动态页面若在两份副本之间自己变了,文章没有写该怎么处理。
早期还有并行 fill 互相抢:名字进了电子邮件字段。修复是让每个浏览器动作排队,等前一个结束。一个失败不会把后面的队列一并弄坏。没有竞态次数,也没有修复后的回归数字。
默认最多 15 步、200,000 token、120 秒。任何一项用尽,记为 blocked 并写明原因,不记成 failed。这些默认值可以改。触发频率没有写。
M2 复查发现,模型可以引用一个什么也没证明的断言来换取通过,规则因此收紧,并留下一份未触碰的页面。M3 发现客户端中途退出会留下 Playwright 和浏览器,构造出的符号链接还可能让报告工具读到项目外的文件。作者说都已修好并有测试覆盖。没有测试名称、数量或独立复验。
哪些改动才要打开浏览器
Markdown 里的错字,以及不会改变页面渲染的后端重构,不走这套复查。组件、表单、导航、对话框、客户端状态、地图、筛选、响应式布局、持久化,还有用户看得见的缺陷修复,才打开浏览器。
作者的经验法则是:只要你在声称界面运行时会怎样,正在运行的界面就该出现在证据里。这套流程不代替测试、代码评审,或人自己去看重要改动。它补上的是原来缺的那一圈反馈。

