Skip to content

加载中与空状态 ​

零条结果……还是其实还没加载完?

新账号还没有任何项目。你的测试检查结果是否为零行,结果在搜索还没加载完时就通过了。搜索出错之后,同一条断言也照样通过。

改造前:从空容器推断含义 ​

ts
const results = page.getByRole("main", { name: "Search results" });
await expect(results.getByRole("listitem")).toHaveCount(0);

零行是一个精确的事实。但它不能告诉你搜索已经完成,也不能告诉你用户知道下一步该做什么。

改造后:给状态命名,再问现在是哪一个 ​

使用 Playwright judge fixture 提供的 classifyPage对捕获的区域进行分类。这个场景期望的是空状态:

ts
const result = await judge.classifyPage(
  "What is the search results state?",
  {
    loading: "The search is in progress.",
    results: "The page lists matching projects.",
    empty: "No projects match and the page suggests what to do next.",
    error: "The search failed with an error.",
  },
  {
    region: page.getByRole("main", { name: "Search results" }),
    settled: ["results", "empty"],
    timeoutMs: 10_000,
  },
);

expect(result.choice).toBe("empty");
expect(result.confidence).toBeGreaterThanOrEqual(0.8);

设置正数的 timeoutMs 后,只要答案不在 settled 之内,裁判就会重新捕获页面,所以loading 会让它继续轮询。一旦落到某个稳定状态,就由你的断言来判断这个状态是不是本测试想要的。

陷阱

超时后,classifyPage 会返回最后一次的答案,即使这个答案是 loading 或 error。单独调用分类器永远不会让测试失败。保留显式的 choice 和 confidence 检查。

行数相同,结果不同 ​

页面内容本测试的预期结果
“Searching projects…”继续轮询;如果一直不稳定则失败。
“No projects found. Try another search or create your first project.”接受:这是有用的空状态。
“Nothing matches yet. Change your search terms to try again.”接受:文案变了也没关系。
“We couldn't search your projects. Please retry.”拒绝:如果错误一直存在。
一个匹配项目的列表拒绝:这个场景期望的是空账号。

choice 的置信度取决于 provider。用已知状态检验你的阈值,包括空白区域和渲染到一半的页面。含义模糊的内容可能被分错类。

运行现有的搜索测试 ​

先设置好仓库里的示例,然后运行:

sh
pnpm --filter semantic-assert-examples test:playwright classification.spec.ts

这个可运行的测试会让本地 HTML 从加载中切换到带引导的空状态,默认的假 provider 会按脚本返回这些答案。想要真实的判断,在 .env 中配置 AI_GATEWAY_API_KEY 后运行:

sh
EXAMPLE_PROVIDER=ai-sdk node --env-file=.env node_modules/@playwright/test/cli.js test --config=examples/playwright.config.ts classification.spec.ts

什么时候不该用分类

轮询可能会向 provider 发出多次请求。如果你的应用暴露了可靠的状态字段或精确的空状态标记,对它做一条普通断言更便宜,效果也一样好。需要在文案不断变化的情况下理解渲染出来的消息时,再用分类。