← 所有文章
·playwrightai-testingci

AI 写的 Playwright 测试,CI 里却没有 AI

一个工具号称做「AI 测试」,你只需要问一个问题:测试跑的时候,模型也在跑吗?

有的工具答「是」:每跑一次,语言模型就读一遍步骤、看一眼页面、决定点哪里,页面一有变动就自己想办法恢复。这样的测试,本质上是一段每次运行都要重新解析的 prompt。Hover 的答案是「否」:模型只探索一次应用,摸清流程,然后写出普通的测试代码。之后它就退场,留下一个确定性脚本,每次都用同样的方式跑。

这一个「是」还是「否」,决定了你的测试套件在整个生命周期里要花掉你多少钱。

运行时的 AI,是一张永远还不完的账单

把模型放到测试运行时的执行路径上,你就要背上三笔成本。

每跑一次都要花钱。每个 PR、每次夜间构建、每次合并队列重试,都会触发 LLM 调用。一套每天在几十个分支上跑几百次的测试,会把这笔账成倍放大,而且测得越多、涨得越多。你本想让「多写覆盖」变便宜,结果反而变贵了。

跑得更慢。让模型看着截图再决定点哪里,比 selector 直接在 DOM 上解析要慢;视觉模型就更慢了。

你的绿色构建,得看别人的服务是否在线。一次限流、一次区域性故障,或者供应商下线了某个模型版本,都可能在没人碰过你代码的情况下,把原本通过的测试套件变红。

这些都还不是最坏的情况,而是把推理留在热路径上,本就要付的代价。

编写期的 AI,只花一次模型的钱

Hover 是一个 MCP 服务器,挂到你现在用的编码 agent 上就行。你让它测一个流程,比如「登录,再加一条叫 verify hover 的 todo」,agent 就会通过 CDP、用 Hover 那套有据可查的工具,驱动你真正的 Chrome。这次运行验证通过后,它调用 crystallize_spec,Hover 就写出一个标准的 @playwright/test 文件:

import { test, expect } from '@playwright/test';

test('login then add todo', async ({ page }) => {
  await page.goto('http://localhost:5173/');
  await page.getByLabel('Email').fill('claude@example.com');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.getByPlaceholder('New todo').fill('verify hover');
  await expect(page.getByText('verify hover')).toBeVisible();
});

这个文件里没有 agent。它靠 npx playwright test 跑,在你的 CI 里跑,零 token,不调用任何供应商。模型只在它唯一帮得上忙的那一刻出手:把流程摸清楚。它留下的,就是普通的 Playwright。

「可是 UI 一改,selector 不就坏了?」

问得有道理,任何 AI 写的测试都该被这么追问。Hover 不往热路径上塞模型,照样能应对。

大多数改动根本传不到 spec 上。Hover 写的是语义化 locator(getByRolegetByLabelgetByText),所以一次布局重构过后,「那个 Sign in 按钮」还是「那个 Sign in 按钮」。

真要是某个名字变了,你就主动重录一次。点一下,原始 prompt 就对着当前 UI 重放一遍,把文件重写好。三十秒,一毛钱,提交前你先看 diff。

要是流程本身坏了,那构建变红正是它该干的事:测试抓到了一次回归,去修应用就好。

和运行时自愈的区别,在于你什么时候付、隔多久付一次。这里你只付一次,主动地付,在东西真的变了的时候付,而不是在整个套件的生命周期里,每次绿色运行都得付。

什么时候你确实需要模型在运行时参与

如果你的 DOM 本身不稳定,或者你测的东西不归你控制、也没法重录,那么让模型待在环路里,能换来静态脚本给不了的韧性。这是个实打实的权衡,有时候值得。

但换成你自己的应用、跑在你自己的开发服务器上、流程几秒钟就能重录,那为每次 CI 的推理付费就什么也换不来。用 AI 来写,用纯 Playwright 来跑。

看看 Hover 如何把一次会话结晶成 spec →

在你自己的应用上试试 Hover。

把 Hover 的 MCP 加到你已经在用的编码 agent。它探索你的应用,结晶出你自己拥有的纯 Playwright spec。

npm i -g @hover-dev/mcp && claude mcp add hover -- hover-mcp阅读快速开始 →