验证 Finite
Finite 有两个交互面,大多数功能需要两者协同。代理通过 /mcp 的 MCP 工具(六个 doc6 工具;call 驱动 /mcp/classic 下的经典工具)来编辑设计。用户通过浏览器观察:设计列表在 /,画布在 /design/?id=<designId>。验证过程通过 call 驱动代理侧,并在页面中检查用户侧的状态,反之亦然(适用于评论等场景)。
测试工具是 e2e/headless.mjs。.agents/skills/finite-headless/SKILL.md 是其命令参考(包含了能深入到影子根、沙箱 iframe、--agent、Python 的选择器)。首次驱动前请阅读一遍。本技能在此基础上增加了隔离性、诊断、证据文件夹、前后对比运行以及功能映射。
以下所有命令都在代码检出根目录下运行:
V=.agents/skills/verify-finite/scripts/verify.sh
Shell 变量在工具调用之间不会保留。请在每次调用开始时重新指定 V=... 和 RUN=...。
步骤
- 选择功能。 在
features/README.md的“修改会影响的功能”表格中,找到您的修改所影响的功能。 - 启动 您代码的一个运行实例。对于 bug 修复,请同时启动一个修改前代码的运行实例(参见“前后对比”)。
- 诊断 每个运行实例,直到它只打印
ok行。 - 驱动 每个选定功能文件中的每个入口点,并在过程中保存证据。
- 清理 您启动的每一个运行实例,包括失败的。
- 报告 按照“报告”部分中的格式进行。
当每个选定功能的每个入口点都被验证、有理由地跳过或因失败而留下证据,并且 $V list 显示没有运行中的实例时,您就完成了。
启动
$V launch # 构建服务器、finite.wasm 和 Web 应用,然后启动它们
$V launch --no-build # 重用上次的构建结果;如果代码在上次构建之后发生了更改,则拒绝执行
$V launch --width 390 --height 844 # 一个手机尺寸的页面(默认是 1440 × 900)
$V launch --at main # 在临时检出版本中运行另一个提交(参见“前后对比”)
它会打印 RUN=<run>。请保留该 ID:每个其他命令都需要它。首次冷构建大约需要 2 分钟,无变更的构建只需几秒钟。请将 Bash 超时时间设置为 600000 毫秒。--no-build 仅在无变更的构建中节省几秒钟,因此如有疑问,请勿使用此选项。
当启动打印出 Finite at http://127.0.0.1:<port> 时,表示环境已就绪。测试工具会等待 /api/health 响应,并在打印该行之前加载设计列表。数据文件夹初始为空,没有种子数据、迁移或登录。用户以“Guest”身份发表评论,代理以“Agent”身份发表评论,除非您传递了 --agent <name>。
每个运行实例都是一个独立的会话(verify-<run>),拥有自己的空闲端口、临时数据文件夹和 Chrome 配置文件,因此多个运行实例可以同时进行。切勿驱动您未启动的 Finite 实例。vp run dev 服务于 crates/server/data 中用户的真实设计,桌面应用也服务于用户自己的数据。如果某个会话连接到了其他人的服务器,诊断将失败。
一个运行实例在空闲 30 分钟后会自动停止。之后,doctor 会失败:请清理并重新启动。
诊断
$V doctor $RUN
它只读取状态,并且每一行都会输出 ok 或 FAIL。它检查五件事:
- 会话处于活动状态,并且拥有一个私有的
finite-headless-data-*文件夹。 /api/health返回ok以及package.json中的版本。- 正在运行的构建与当前代码匹配。它会比较构建输入(
crates/、apps/web/、测试工具、锁文件,包括未跟踪的文件)的哈希值与运行实例构建时所依据的代码的哈希值。对文档、技能或测试的编辑不计入。 - 存在 Chrome、Chromium 或 Edge 浏览器,可用于
get_screenshot。 - 页面有响应。
然后,它会打印自上次调用以来页面的控制台输出。请在首次运行时、出现任何意外情况后以及捕获最终证据前运行此命令。如果出现 FAIL,请清理并重新启动,而不是继续驱动。在您修改代码后,构建检查会按设计失败:请清理并重新启动。
驱动
$V h $RUN <command> ... 对该运行实例运行任何 e2e/headless.mjs 命令。您最常用的命令:
$V h $RUN call create_design '{"name":"Verify"}' # 代理侧:打印 designId "<id>"
$V h $RUN call create_artboard @input.json # JSON 内联、@文件 或 - 表示标准输入
$V h $RUN design <designId> # 用户侧:打开画布,等待画板
$V h $RUN snapshot --selector main # 无障碍树,用户所见内容
$V h $RUN click 'button[title="Comment (C)"]'
$V h $RUN click '[data-artboard] >>> h1' --shift # Shift+点击
$V h $RUN upload 'input[type=file]' card.html # 选择文件,如同操作系统文件选择器
$V h $RUN api GET /api/designs/<designId> # 存储的设计,包含评论
请使用这些稳定的句柄。它们就是应用的可访问名称和标题:
| 位置 | 句柄 |
|---|---|
| 设计列表 | 标题 Designs、按钮 New design、搜索框 Search designs by name、每个设计卡片的 link |
| 画布顶部栏 | 链接 ← Designs、button[title="CSS variables every artboard uses, in each mode"] (Tokens)、button[title="Every comment, open and resolved"] (Comments,名称中包含未读数量)、Import、Export、每个在线代理的标签 |
| 画布工具 | 工具栏 Tools:button[title="Pointer (V)"]、button[title="Comment (C)"];快捷键 v、c、Shift+1 (适应)、=/- (缩放) |
| 评论框 | 文本框 Add a comment、button[title="Change your name"]、[data-selection-count]、按钮 Send、名称为 Comment: <first message> 的锚点 |
| 评论线程 | [role=dialog] 名称为 Comment thread:按钮 Delete、Resolve/Reopen、Close、在您最新消息上的 Edit、文本框 Reply |
| 面板 | aside[aria-label=Comments] 包含标签页 Open N / Resolved N;section[aria-label=Tokens] 包含 form[aria-label="Add a token"];nav[aria-label=Pages];[role=menu][aria-label=Export];aside[aria-label=Import] 包含 status |
| 画板内容 | [data-artboard="<artboardId>"] >>> <css>;代理的 data-name 属性会保留在 DOM 中 |
优先使用这些句柄而不是坐标。坐标仅用于沙箱化(通过脚本)的画板,因为选择器无法触及这些画板。click 接受 CSS 选择器,不接受文本,因此对于没有标题的按钮,通过位置来选择:[role=dialog] button --nth 1,可以使用 query 列出顺序。
文本输入有两个陷阱。type 会追加内容到字段已有内容之后,而 key Meta+a 无法通过 CDP 选择所有文本。要替换字段中的文本,请在聚焦该字段后运行 $V h $RUN eval "document.execCommand('selectAll')",然后使用 type。
后台调用
wait_for_comments 会阻塞,直到有人发表评论,因此它必须在您驱动页面时运行:
$V bg $RUN 02-wait call wait_for_comments '{"designId":"<D>","timeoutSeconds":30}'
# ...驱动页面...
$V await $RUN 02-wait 30 # 阻塞直到调用返回,然后打印 .verify/<run>/02-wait.txt
使用 await 读取结果,切勿直接读取文件:文件只有在调用返回后才会出现。后台调用在工具调用之间持续存在。
证据
所有内容都保存在检出版本中的 .verify/<run>/ 目录下。.gitignore 已忽略此目录,清理过程也会保留它。
$V save $RUN 01-comment-posted snapshot --selector main # 命令、其输出和退出码
$V shot $RUN 02-canvas # .verify/<run>/02-canvas.png
$V shot $RUN 03-panel --selector 'aside[aria-label=Comments]'
save 接受任何 headless 命令,包括 call,并将 <name>.txt 文件写入其中,包含命令、输出和 [exit N]。对名称进行编号,以便运行记录按顺序阅读。在依赖 PNG 之前,请使用读取工具查看每个 PNG。get_screenshot 图像以及任何未指定路径的页面截图都会保存在会话的临时文件夹中。清理过程会将它们复制到 .verify/<run>/tool-images/,并将服务器日志复制到 .verify/<run>/server.log。您作为测试输入创建的文件(例如要导入的文件)也应放在 .verify/<run>/ 中。
验证需满足以下标准:
- 真实路径。 用户的操作通过页面(点击、按键、输入、上传)进行,代理的操作通过 MCP
call进行。REST API 仅用于读取存储的状态。通过 API 写入会绕过您正在验证的用户界面。 - 操作与结果。 保存执行操作的步骤以及操作后的状态,而不仅仅是最终截图。
- 副作用。 同时展示存储的和显示的更改。
api GET /api/designs/<id>返回服务器保存的meta、nodes和comments,代理的读取工具(get_comments、get_tokens、get_tree_summary)会显示下一个代理将看到的内容。 - 实时功能需双向验证。 代理的编辑必须无需重新加载就能立即显示在页面中。用户的评论必须能够到达等待中的代理。
- 无模拟。 运行环境中使用真实的服务器、真实的浏览器和真实的 MCP 会话。
- 控制台无错误。 以
$V save $RUN <n>-console console结束。如果控制台出现异常或失败的请求,则证据无效。
前后对比
当同一个测试流程在旧代码上失败,而在新代码上通过时,即证明 bug 已被修复。同时运行新旧两个版本:
$V launch --at main # RUN=<before>:主分支的临时检出版本,已安装和构建(大约一分钟)
$V launch # RUN=<after>:当前检出版本
在两个版本中执行相同的步骤,并使用相同的证据名称。“修改前”版本必须展示 bug。如果未展示,则您的测试流程未覆盖到该 bug,并且“修改后”版本也无法证明修复有效。--at 可以接受任何 git 引用:分支、标签、提交,或者当您的修复尚未提交时的 HEAD。诊断会显示运行实例所基于的 ref 和提交。清理过程会移除临时检出版本,但保留其证据。
清理
$V cleanup $RUN
停止此运行实例的守护进程。守护进程会停止它生成的服务(通过 SIGTERM,因此设计会先保存)以及它启动的 Chrome,然后删除它们的临时数据和配置文件。然后,清理过程会将服务器日志和工具图像复制到证据文件夹中,移除 --at 运行的临时检出版本,并列出保留的内容。它不会影响其他会话、vp run dev 或桌面应用。
$V list 打印当前检出版本中仍在运行或遗留有检出版本的运行实例。请使用 $V cleanup <run> 清理每个实例,切勿使用 pkill。$V prune [days] 删除早于指定天数(默认为 7 天)的已清理运行实例的证据。切勿删除您即将要报告的运行实例的证据。
报告
以每个您驱动的功能的一个代码块结束:
<功能 ID> 通过 <入口点>: proven | skipped | failed
evidence: .verify/<run>/<files>
why (skipped or failed): <您执行的命令以及缺少或错误的内容>
然后提供运行实例 ID、每个实例的诊断输出,以及对于修复来说,“修改前”版本显示 bug 的证据。即使另一个入口点验证了相同的行为,也要将跳过的入口点报告为跳过。
辅助工具
scripts/verify.sh 接受以下命令:launch、doctor、h、save、shot、bg、await、cleanup、list 和 prune。在不带参数的情况下运行它以查看它们的参数。它是 e2e/headless.mjs 的一个薄封装:会话是 verify-<run>,证据保存在 .verify/<run>/ 中。