第 17 章 · APIRequestContext 与 API 测试
本章目标:用 Playwright 内置的 APIRequestContext 直接测 REST API,并把"API 造数 + UI 验证"组合成最快的测试形态。
17.1 为什么不直接用 requests
Playwright 自带一个完整的 HTTP 客户端 APIRequestContext,官方列出的典型用途:
- 直接测服务端 API;
- 访问 Web 应用前先用 API 准备服务端状态;
- 在浏览器操作后校验服务端后置条件。
相比 requests/httpx,它的独特优势是与浏览器上下文同源共享:自动携带与页面一致的 cookie/认证状态(第 9 章的 storage_state 可直接复用),且随上下文销毁统一清理——UI 与 API 两条通道天然打通。
17.2 创建独立的 API 上下文
官方示例模式:session 级 fixture + playwright.request.new_context():
# conftest.py
import os
from typing import Generator
import pytest
from playwright.sync_api import Playwright, APIRequestContext
API_TOKEN = os.getenv("GITHUB_API_TOKEN", "")
@pytest.fixture(scope="session")
def api_request_context(
playwright: Playwright,
) -> Generator[APIRequestContext, None, None]:
headers = {
# 按 GitHub 规范声明版本
"Accept": "application/vnd.github.v3+json",
"Authorization": f"token {API_TOKEN}",
}
request_context = playwright.request.new_context(
base_url="https://api.github.com",
extra_http_headers=headers,
)
yield request_context
request_context.dispose() # 统一释放连接base_url 之后所有请求都可以写相对路径;get/post/put/delete/delete 等 方法返回 APIResponse,常用属性有 .ok(2xx 判定)、.status、.json()、.text()。
17.3 写一组 API 用例
官方 GitHub Issues 示例的骨架:
# tests/test_issues_api.py
GITHUB_USER = "your-user"
GITHUB_REPO = "api-test-repo"
def test_should_create_bug_report(api_request_context: APIRequestContext):
data = {"title": "[Bug] report 1", "body": "Bug description"}
new_issue = api_request_context.post(
f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data
)
assert new_issue.ok # 断言 2xx
issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issue = [i for i in issues.json() if i["title"] == "[Bug] report 1"][0]
assert issue["body"] == "Bug description"配合 session 级 setup/teardown 管理"被测资源本身"的存在性(先建仓库、跑完删除):
@pytest.fixture(scope="session", autouse=True)
def create_test_repository(api_request_context: APIRequestContext):
new_repo = api_request_context.post("/user/repos", data={"name": GITHUB_REPO})
assert new_repo.ok
yield # ---- 所有测试在此之间运行 ----
deleted = api_request_context.delete(f"/repos/{GITHUB_USER}/{GITHUB_REPO}")
assert deleted.okSchema 校验思路
.json() 拿到字典后,除了断言业务字段,生产上建议再校验响应结构(防止字段悄悄改名)。Python 侧惯用 pydantic 承接:
from pydantic import BaseModel
class Issue(BaseModel): # 声明你依赖的字段结构
id: int
title: str
body: str | None
Issue.model_validate(new_issue.json()) # 结构不符会抛 ValidationError17.4 API 造数 + UI 验证:最快组合拳
E2E 最慢的环节是用 UI 一路点到目标状态。把"铺垫"交给 API,把"验证"留给 UI:
from playwright.sync_api import Page, expect
def test_last_created_issue_should_be_first_in_the_list(
api_request_context: APIRequestContext, page: Page
):
def create_issue(title: str) -> None:
api_request_context.post(
f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues",
data={"title": title, "body": "Feature request"},
)
# Arrange:API 秒级造出三条数据(UI 操作可能要几十秒)
create_issue("[Feature] request 1")
create_issue("[Feature] request 2")
# Act & Assert:只对真正关心的 UI 行为走浏览器
page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")
first = page.locator('[data-testid="issue-title"]').first
expect(first).to_have_text("[Feature] request 2")这条官方示例展示了黄金分层:状态准备走 API,用户可感知行为走 UI。
与浏览器上下文共享认证
已登录场景下不必重复配 token——直接复用第 9 章保存的登录态:
request_context = playwright.request.new_context(
base_url="https://shop.example.com/api",
storage_state="state/auth.json", # 带 cookie/token 的请求上下文
)
resp = request_context.get("/orders") # 以登录用户身份调用
assert resp.ok反过来也成立:浏览器上下文里发出的请求可通过 context.request 访问同一份 cookie,适合"页面操作后立刻查接口"的后置校验。
data 参数
Python 版 post(..., data=dict) 会以表单/JSON 形式序列化字典;需要显式 JSON 时也可传 data=json.dumps(payload) 并设置 Content-Type 头。以所用版本的 API 参考为准。
17.5 本章小结
playwright.request.new_context(base_url=..., extra_http_headers=...)创建独立 API 客户端,dispose()收尾;APIResponse.ok/.status/.json()覆盖常规断言需求,pydantic 做 schema 校验;- session fixture 管 setup/teardown,测试管单条行为;
- API 造数 + UI 验证是 E2E 提速的第一杠杆;
storage_state让 API 与浏览器共享登录态。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 创建独立 API 测试上下文的正确入口是?
2. 相比 requests 库,APIRequestContext 的独特优势是?
3. "API 造数 + UI 验证"模式的收益是?
4. APIResponse 对象上,判断请求成功的惯用方式是?
🛠️ 动手实践
- 为 https://reqres.in 或公司内网 API 编写一组 CRUD 用例,包含一个 pydantic schema 校验。
- 把第 9 章保存的 storage_state 接入 API 上下文,实现"免登录调用需授权接口"。
- 选一条现有 E2E 用例,把其中的 UI 铺垫步骤改写为 API 造数,对比前后耗时。
完成后进入下一章:异步 API 与 FastAPI 联测。