Playwright APIResponseAssertions 完全解析:对 APIResponse 做响应状态断言(toBeOK 与 not 反转)
在 Playwright 的测试断言体系中,APIResponseAssertions 专门负责对 APIResponse 对象发起断言,最常用的场景就是用 expect(response).toBeOK() 校验 HTTP 响应状态码是否落在 200..299 区间。本文基于仓库中的 API 参考文档 class-apiresponseassertions.md 与底层匹配器实现 matchers.ts,完整覆盖该类的全部成员(toBeOK、not、Python 的 NotToBeOK),并结合 TypeScript 类型定义与测试用例,说明各语言绑定下的写法差异、失败时的错误信息构成,以及 not 反转断言的底层机制。
类定位:APIResponseAssertions 是 expect 对 APIResponse 的专用匹配器集合
根据 API 文档,APIResponseAssertions 自 v1.18 起提供(文档标注 since: v1.18),用于对 APIResponse 进行测试断言。典型入口是先通过 page.request.get() 之类的 APIRequestContext 方法拿到 APIResponse,再交给 expect:
import { test, expect } from '@playwright/test';
test('navigates to login', async ({ page }) => {
// ...
const response = await page.request.get('https://playwright.dev');
await expect(response).toBeOK();
});
从 test.d.ts 的类型定义可以看到该接口只有两个成员:toBeOK(): Promise<void> 与 not: APIResponseAssertions。同时 AllMatchers 类型把它和 PageAssertions、LocatorAssertions 等并列合并(见 test.d.ts),并且只有当被断言对象 T extends APIResponse 时,这些断言才出现在类型上——也就是说,Playwright 的 expect 会根据传入对象的类型动态收窄可用匹配器,把 APIResponse 传给 expect 后,IDE 只会提示 API 响应相关的断言方法,不会出现 DOM 定位器断言的干扰。
各语言绑定的对应写法如下(与文档一致):
Java(通过 PlaywrightAssertions.assertThat 进入断言链):
// ...
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class TestPage {
// ...
@Test
void navigatesToLoginPage() {
// ...
APIResponse response = page.request().get("https://playwright.dev");
assertThat(response).isOK();
}
}
Python(async / sync 两种风格,方法名为蛇形命名 to_be_ok):
# async
from playwright.async_api import Page, expect
async def test_navigates_to_login_page(page: Page) -> None:
# ..
response = await page.request.get('https://playwright.dev')
await expect(response).to_be_ok()
# sync
from playwright.sync_api import Page, expect
def test_navigates_to_login_page(page: Page) -> None:
# ..
response = page.request.get('https://playwright.dev')
expect(response).to_be_ok()
C#(MSTest 风格,异步方法名带 Async 后缀):
using Microsoft.Playwright;
using Microsoft.Playwright.MSTest;
namespace PlaywrightTests;
[TestClass]
public class ExampleTests : PageTest
{
[TestMethod]
public async Task NavigatesToLoginPage()
{
var response = await Page.APIRequest.GetAsync("https://playwright.dev");
await Expect(response).ToBeOKAsync();
}
}
命名规律值得注意:同一语义的断言在四种语言中分别为 JS 的 toBeOK、Java 的 isOK(文档中标注了 alias-java: isOK)、Python 的 to_be_ok、C# 的 ToBeOKAsync。写跨语言测试或从其他框架迁移时,按这个映射关系查找 API 即可。
核心断言 toBeOK:状态码 200..299 区间校验
toBeOK 自 v1.18 起可用,语义是:确保响应状态码处于 200..299 范围内(即成功类状态码,含 2xx 全部)。各语言用法:
await expect(response).toBeOK();
assertThat(response).isOK();
# async
await expect(response).to_be_ok()
# sync
expect(response).to_be_ok()
await Expect(response).ToBeOKAsync();
源码级实现:判定逻辑与失败信息
toBeOK 的 JavaScript 端实现在 matchers.ts:
export async function toBeOK(
this: ExpectMatcherStateInternal,
response: APIResponseEx
) {
const matcherName = 'toBeOK';
expectTypes(response, ['APIResponse'], matcherName);
const contentType = response.headers()['content-type'];
const isTextEncoding = contentType && isTextualMimeType(contentType);
const [log, text] = (this.isNot === response.ok()) ? await Promise.all([
response._fetchLog(),
isTextEncoding ? response.text() : null
]) : [];
const message = () => formatMatcherMessage(this.utils, {
isNot: this.isNot,
promise: this.promise,
matcherName,
receiver: 'response',
expectation: '',
log,
}) + (text === null ? '' : `\nResponse text:\n${colors.dim(text?.substring(0, 1000) || '')}`);
const pass = response.ok();
return { message, pass };
}
从这段实现可以确认几个关键点:
- 类型守卫:
expectTypes(response, ['APIResponse'], matcherName)会先校验传入对象确实是APIResponse。如果把Locator或普通对象传给expect(...).toBeOK(),会在断言阶段直接报类型错误,而不是静默通过。 - 判定标准是
response.ok():pass = response.ok(),对应 HTTP 2xx 区间。它不会检查 3xx 重定向目标或 4xx/5xx,也不会等待重试——toBeOK是一次性判定,响应对象本身在page.request.get()等调用返回时已经拿到完整状态码,断言不再有轮询等待行为。 - 失败时的诊断信息:仅当断言失败时(即
this.isNot === response.ok())才会额外收集两样东西:response._fetchLog()(底层 fetch 过程日志)和响应正文。正文只在content-type属于文本型 MIME(isTextualMimeType)时读取,并截断到前 1000 字符,以灰色(dim)追加在错误消息的Response text:段落中。这意味着测试报告里看到"status 500 + 部分响应体"的输出,正是来自这段拼接逻辑,对定位后端错误很有价值。 isNot的复用:同一个函数同时服务于toBeOK和not.toBeOK,失败条件写成this.isNot === response.ok()——即"断言方向"与"实际 ok 状态"一致时反而需要收集失败日志。这解释了为什么not不需要单独的 JS 匹配器实现。
类型定义与注册位置
toBeOK 的签名在 test.d.ts 中声明为 toBeOK(): Promise<void>,注释明确写着 "Ensures the response status code is within 200..299 range"。该函数在 expect.ts 中被导入并注册进 expect 的默认匹配器表(见第 61 行与第 189 行的 toBeOK 条目),因此 import { expect } from '@playwright/test' 后即可直接使用,无需手动 expect.extend。
not 反转断言:检查"非成功"响应
not 属性自 v1.20 起提供(文档标注 langs: java, js, csharp),作用是让断言检查相反的条件——例如验证响应状态不是成功:
await expect(response).not.toBeOK();
assertThat(response).not().isOK();
await Expect(response).Not.ToBeOKAsync();
从 test.d.ts 可以看到它的类型是递归的自引用:not: APIResponseAssertions。这正是 JS 端能复用一个 toBeOK 函数同时支持正反两种断言的机制:not 只是翻转 ExpectMatcherState 上的 isNot 标志(匹配器实现中所有 this.isNot 判断都基于它,前面 matchers.ts 中的失败条件 this.isNot === response.ok() 即是最直接的体现)。Java 与 C# 端则是显式的链式方法/属性:.not() 与 .Not。
not.toBeOK() 的典型用途:校验权限控制(401/403)、路由不存在(404)、限流(429)等"预期失败"场景。例如:
// 未携带 token 时应返回非 2xx
const response = await page.request.get('/api/protected');
await expect(response).not.toBeOK();
Python 特例:NotToBeOK 方法
由于 Python 绑定没有 .not 属性,文档为其提供了独立方法 NotToBeOK(async method,自 v1.19 起),语义就是 toBeOK 的反面:
await expect(response).not_to_be_ok() # async
expect(response).not_to_be_ok() # sync
这也是该文档列出的唯一 Python 专属条目(langs: python),与 JS 的 not 属性、Java 的 not()、C# 的 Not 一一对应。
测试用例中的实际用法
仓库的测试代码本身就展示了 toBeOK 的两种使用姿态:
- 作为 API 响应的断言:tests/library/browsercontext-fetch.spec.ts 与 tests/playwright-test/expect.spec.ts 中都有
await test.expect(res).toBeOK()的写法,用于校验page.request/context.request拿到的响应。 - 从 tests/page/expect-boolean.spec.ts 等文件对
isNot的大量引用可以看到,isNot标志是 expect 匹配器框架的通用机制,not断言的失败消息中会出现 "Expected: not ..." 之类的表述,便于区分正反断言的失败。
小结与使用边界
| 成员 | 语言 | 最低版本 | 语义 |
|---|---|---|---|
toBeOK() / isOK() / to_be_ok() / ToBeOKAsync() |
js / java / python / csharp | v1.18 | 状态码在 200..299 |
not / not() / Not |
js / java / csharp | v1.20 | 反转断言方向 |
NotToBeOK()(not_to_be_ok) |
python | v1.19 | toBeOK 的反面 |
几点使用边界需要明确:
toBeOK只判断 2xx,不跟随重定向语义、不检查响应体内容、不轮询重试;若需要等待,应在拿到响应前对业务行为做等待(如waitForResponse),拿到APIResponse后断言即是一次性的。- 传入对象必须来自 APIRequestContext(如
page.request.get()、context.request.get()等)的APIResponse,否则会被expectTypes类型检查拦截。 - 断言失败时自动附带 fetch 日志与最多 1000 字符的文本响应体,这是内建行为,无需额外配置。
- 各语言别名对照:JS
toBeOK= JavaisOK= Pythonto_be_ok= C#ToBeOKAsync;反转:JSnot= Javanot()= C#Not= Pythonnot_to_be_ok。
更多上下文可参考文档源文件 class-apiresponseassertions.md、类型声明 test.d.ts 与匹配器实现 matchers.ts。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00