Playwright
适配器捕获的是屏幕阅读器能看到的内容:可访问性快照、URL 和标题。你的陈述会基于这些文本来判断,而不是原始 HTML 或截图。这样判断结果始终锚定在用户真正能分辨的信息上。
你将学到
- 如何在测试中加入
judgefixture - 为什么裁判只评估一次,以及何时该等待或轮询
- 如何把捕获范围限定到一个区域
- 如何使用
expectmatcher - 如何在数据离开浏览器之前脱敏
- 如何测试视觉样式并读懂证据报告
安装
pnpm add -D @playwright/test semantic-assert semantic-assert-playwright semantic-assert-typesafe
pnpm exec playwright install chromium
export TYPESAFE_API_KEY="your-api-key"适配器需要 @playwright/test >=1.50.0 和 Node.js 22 或更高版本。
Fixture
在 fixtures.ts 中用 judge fixture 扩展 Playwright 的 test:
import { test as base } from "@playwright/test";
import {
judgeFixtures,
type JudgeFixtures,
type JudgeFixtureOptions,
} from "semantic-assert-playwright";
import { typesafe } from "semantic-assert-typesafe";
export const test = base.extend<JudgeFixtures & JudgeFixtureOptions>({
...judgeFixtures,
judgeProvider: [typesafe({ timeoutMs: 10_000, maxRetries: 0 }), { option: true }],
});
test.use({ judgeOptions: { threshold: 0.8 } });
export { expect } from "@playwright/test";陷阱
provider 对象应该放在构建 test 的地方,如上所示。不要把它放进 Playwright 配置的 use 块。这个块会被序列化后传给 worker,provider 实例经不起这一趟传输。judgeOptions 是纯数据,放哪儿都行。
然后像使用其他 fixture 一样使用 judge,这里写在 alert.spec.ts 中:
import { test, expect } from "./fixtures";
test("an alert explains how to recover", async ({ page, judge }) => {
await page.setContent(`
<p role="alert">Your changes haven't been saved. Please give it another try.</p>
`);
const alert = page.getByRole("alert");
await expect(alert).toBeVisible();
await judge.expectPageTo("The alert asks the user to try again", {
region: alert,
});
});用 pnpm exec playwright test 运行。在真实测试中,把 page.setContent 换成页面导航和交互。
先等待,再只评估一次
语义断言默认只评估一次(timeoutMs: 0)。让 Playwright 负责等待,然后再向模型询问含义:
const alert = page.getByRole("alert");
await alert.waitFor({ state: "visible" });
await judge.expectPageTo("The alert explains how to recover", { region: alert });await expect(alert).toBeVisible() 一行代码就能完成等待并断言可见性。如果元素比最终内容先出现,还要等待你的应用进入就绪状态。
区域会自己等待
region 捕获会等待其元素挂载,最长 regionTimeoutMs。默认是 5 秒,与 Playwright 的 expect 超时相同。这段等待发生在浏览器里,不会向模型发送任何内容,所以它和 timeoutMs 是分开的。你可以按单次调用设置,也可以在 judgeOptions 中为整个测试套件设置。始终没有出现的区域会以 RegionNotFoundError 失败。
主动开启轮询
如果你希望裁判持续检查某些内容,把 timeoutMs 设为正数:
await judge.expectPageTo("The export is ready to download", {
region: page.getByRole("status"),
timeoutMs: 5_000,
pollIntervalMs: 1_000,
});裁判会在两次评估之间重新捕获区域,陈述一旦通过就停止。provider 的请求超时和重试需要单独配置。
限定捕获范围
传入 region locator 或 CSS 选择器,只捕获相关内容:
await judge.expectPageTo("The message confirms that the purchase succeeded", {
region: page.getByRole("region", { name: "Order confirmation" }),
});限定范围有两个作用。它减少发送给模型的文本,也能防止页面上无关的内容替区域补上它本身缺失的信息。
用 expectPage 把多条陈述合并到一次请求中,用 expectPageNotTo 断言单条否定陈述。两者都支持单次调用的阈值、轮询选项和 regionTimeoutMs。
Matcher 语法
如果你更想直接对 locator 做断言,创建一个能感知裁判的 expect:
import { createJudgeExpect } from "semantic-assert-playwright";
import { typesafe } from "semantic-assert-typesafe";
const expect = createJudgeExpect({ provider: typesafe() });
await expect(page.getByRole("alert")).toSatisfy("The alert explains how to recover");
await expect(page.getByTestId("agent-panel")).toSatisfyAll([
"The panel contains a reply to the customer's question",
{ claim: "The reply promises a refund", expected: false },
]);和 Playwright 内置的 locator matcher 一样,它们会等待 locator 挂载,最长 regionTimeoutMs,然后只评估一次。
陷阱
这些 matcher 不接受 .not。一条陈述没达到阈值,并不能证明相反的情况成立。要为否定陈述要求证据,请改用 { claim, expected: false }。
脱敏敏感数据
你捕获的所有内容都会发送给配置的 provider。用 redact 钩子在请求发出之前修改状态:
await judge.expectPageTo("The error gives the user a recovery step", {
region: page.getByRole("alert"),
redact: (state) => ({
...state,
aria_snapshot: state.aria_snapshot.replaceAll("alex@example.test", "[email redacted]"),
}),
});这个例子从快照中移除了一个已知的邮箱地址。如果你的捕获内容带有敏感信息,也要检查 URL、标题、链接、视觉提示和额外状态。
视觉提示
可访问性快照不包含任何样式信息。对于涉及高亮、粗体、删除线或文字颜色的陈述,开启 visualHints:
await judge.expectPageTo("A passage has a highlighted background", {
region: page.locator(".document"),
visualHints: { dataAttributes: ["data-highlight-kind"] },
});提示是从页面收集来的文本观察结果,不是截图分析。它们默认关闭,因为会多花一次评估和额外的 token。要精确检查样式,继续用 toHaveCSS 或 toHaveClass。
证据与报告
fixture 会把判断证据附到 Playwright 的报告中,陈述失败时还会附上捕获的状态。在 playwright.config.ts 中加入用量报告器,汇总整次运行的开销:
import { defineConfig } from "@playwright/test";
export default defineConfig({
reporter: [
["list"],
[
"semantic-assert-playwright/reporter",
{ outputFile: "test-results/semantic-assert-metrics.json" },
],
],
});报告会列出调用次数、token、provider 等待时间,配置好 provider 费率后还会列出预估费用。这些设置见 provider。
回顾
- 把
judgeFixtures加到你的test中,并在构建test的地方提供 provider。 - 让 Playwright 负责等待,然后只评估一次。区域会自己等待,最长
regionTimeoutMs。 - 用
region限定捕获范围。既省 token,又让模型不被无关内容误导。 - matcher 像内置 matcher 一样自动等待,并且不接受
.not。 - 请求前先脱敏,只在涉及样式的陈述中开启
visualHints,加上报告器就能看到一次运行花了多少。