常见问题

用一句话说,Hover 是什么?

Hover 是一套开源的 Vibe Testing 套件,围绕一个你添加到自己已在使用的编码 agent 中的 MCP 服务器(@hover-dev/mcp)构建;agent 探索你的应用,把每条流程晶化成一个由你拥有的纯 @playwright/test spec,它在你的 CI 中运行,回路中零 AI。

我需要 VS Code 扩展吗?

不需要。MCP 就是完整的编写回路——把它添加到你的 agent 并运行 /mcp__hover__test_app。VS Code 扩展是一个可选的审查驾驶舱(一张业务地图图谱 + 一个仪表盘),它不驱动任何 agent。

如何安装?

全局安装 MCP,然后把这个 bin 注册到你的 agent(此处以 Claude Code 为例——任何支持 MCP 的 agent 都可以):

npm i -g @hover-dev/mcp
claude mcp add hover -- hover-mcp

优先选择全局安装——npx -y @hover-dev/mcp 这条一行命令会在首次启动时重新下载,可能超过你 agent 的 30s MCP 连接超时。

这会同时带来接地的浏览器工具和 /mcp__hover__test_app 命令。你的项目里没有插件,无需改动打包器配置,也没有任何 Hover 自己的密钥。

"record == replay" 是什么意思?

agent 通过 Hover 的接地浏览器工具(click_control / fill_control / …)行动,这些工具按照从页面快照读到的 role+name → testId → text 来定位一个控件。驱动该操作的那个 selector,正是 Hover 保存下来的那一个,而且晶化是确定性的(没有任何 LLM 来写代码)。所以你回放的就是你录制的——不会有凭空捏造的 selector。Playwright codegen、Stagehand 和 Midscene 都无法保证这一点。

我的 UI 变了,保存的 spec 挂了。怎么办?

对任何 AI 编写的端到端测试来说,这都是核心问题。Hover 的答案分三层。

1. 大多数 UI 变动不会让 spec 挂掉

Hover 生成 getByRole / getByLabel / getByTestId 这类语义化 selector——从不用 CSS class 或 XPath。"Submit button" 在一次布局调整之后还是 "Submit button";spec 照常运行。这个选择在 packages/core/src/specs/writeSpec.ts 中被强制执行,而接地操作让它不止是一种指望——接地的目标就是 selector。

2. 当语义发生变化——按钮改名、标签变更、role 被替换——spec 会变红

你有两个本地选项:

  • 手动修改。 spec 就是纯 @playwright/test——把 getByRole('button', { name: 'Submit' }) 改成 'Sign in'。如果你清楚地知道改了什么,这样很快,而且这个文件归你所有。
  • 重新晶化。 重跑 /mcp__hover__test_app <flow>,让 agent 针对当前 UI 重新编写该流程。
  • 当成一次回归来看。 如果测试失败是因为流程坏了(而不只是 selector),那是测试抓到了一个真实 bug——修应用,而不是修 spec。

失败时自动愈合——针对当前 UI 重放一个 spec 的意图并提出 diff——被规划为 Hover Cloud 的一部分,而非本地功能。在本地,CI 保持确定性且免费。

3. 我们为什么不在 CI 阶段自动愈合

Stagehand / Midscene 的模型:测试靠在运行途中调用 LLM 来"自愈",不断用新 selector 重试直到通过。它是有效的,但会建立起对托管 AI 提供商的永久运行时依赖——每次 CI 运行都付一次 LLM 调用,每个 PR、每个 nightly 都如此。在一年的 CI 周期里,这是可度量的花费,也是一个脆弱面(速率限制、区域性宕机、模型弃用)。

Hover 采取相反的立场:AI 用于编写测试,而不是运行测试。 保存下来的 .spec.ts 就是纯 Playwright——npx playwright test __vibe_tests__ 是确定性的且免费。token 成本集中在你真正需要模型的那一刻,而不是摊到成千上万次回归运行里。

我的按钮还在 DOM 里,只是被移到了 kebab 菜单后面——spec 能抓到吗?

能——而且怎么抓到的比"我们加了个检查"更微妙。Playwright 的 .click() / .fill() / .hover() / .selectOption() / .dblclick() 全都会对可操作性(actionability)自动等待,这其中就包含可见性——所以即便是一个裸的 .click() 也不会悄无声息地打到一个隐藏元素上。它会在 30 秒后超时,并抛出一个读起来像 flake 的通用可操作性错误。

Hover 发出的可见性前奏是一次更快、语义更清晰的失败,而不是全新的检测:

// Hover emits this:
{
  const el = page.getByRole('button', { name: 'Submit' });
  await expect(el).toBeVisible();
  await el.click();
}

当按钮漂移进一个折叠的 <details> / kebab / 抽屉里时,toBeVisible() 这一行会在约 3 秒内以 Locator expected to be visible 失败——工程师会立刻把这类失败识别为 UI 回归,而不会当成网络抖动打发掉。同样的漂移放在一个裸的 .click() 上会卡 30 秒,并产生一个 Timeout 30000ms exceeded ... element is not visible 错误,很容易被误判为 flake。

这个改动作用于 click / dblclick / hover / fill / selectOptionpage.goto 是页面级的(没有元素),保持一行。

它仍然解决不了什么:

  • disabled 按钮 —— Playwright 在那里也会对可操作性自动等待,所以点击一个被禁用的控件仍会在 30 秒时以通用消息超时。(在要紧处用 await expect(el).toBeEnabled() 手动收紧。)
  • 某个中间步骤悄悄从流程里消失 —— 每个步骤单独看仍然通过;针对当前 UI 重跑原始提示能抓到这个,这正是规划给 Hover Cloud 的那种意图级修复。

Hover 会不会再起一个 headless Chromium?我的 CI 已经很忙了。

不会。Hover 的引擎在 <tmpdir>/hover-chrome 下启动一个隔离的调试 Chrome 并通过 CDP 连接,且仅在你编写时如此。它从不为每条命令起一个新的 Chromium,也不碰你 CI 的 Playwright 浏览器——那些完全由 playwright.config.ts 配置,与 Hover 的调试 Chrome 无关。在 CI 里根本没有 Hover;你运行的是纯 npx playwright test

Hover 会把我的源代码或 DOM 发到某个托管服务吗?

不会。Hover 不捆绑任何模型,也不持有任何密钥(BYO-CLI)。你的编码 agent 与它自己的提供商对话(Anthropic、OpenAI、Google,或一个自托管端点);@hover-dev/mcp@hover-dev/core 没有任何 LLM SDK 代码、没有遥测、也没有上传通道。引擎只绑定到 127.0.0.1

Hover 会往我应用的构建产物里注入任何东西吗?

不会。Hover 在一个隔离的调试 Chrome 里通过 CDP 驱动你的应用——没有插件、不改打包器配置、页面里没有挂件。无论是 dev 还是生产构建,你应用的 bundle 都不受影响。

.hover/ 里放的是什么,我该提交它吗?

.hover/ 是你应用的测试知识:一张业务地图(hover-map.md)、记住的业务规则(memory/)、按 spec 划分的旁挂文件(sidecars/),以及一次性的工作数据(cache/)。在你项目中建议的策略是忽略 cache/ 并提交其余部分——它们是纯 Markdown 和 JSON,会随着它所描述的代码一起做版本管理。

我能在 CI 里跑 Hover 来自动编写新测试吗?

你可以把你的 agent + MCP 接进一个 CI 任务,但多数情况下这是一种反模式。Hover 的构建前提是:每个生成的 spec 在提交之前都由人来审查。没有审查的自动编写往往会产出这样的 spec——它通过一次,然后不断累积没人注意到的 selector 债务,直到某天挂掉。

受支持的工作流是:由人在开发期间运行 Hover、审查晶化出的 spec、提交它们。CI 只负责运行 Playwright。