Skip to content

第 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()

python
# 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 示例的骨架:

python
# 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 管理"被测资源本身"的存在性(先建仓库、跑完删除):

python
@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.ok

Schema 校验思路

.json() 拿到字典后,除了断言业务字段,生产上建议再校验响应结构(防止字段悄悄改名)。Python 侧惯用 pydantic 承接:

python
from pydantic import BaseModel


class Issue(BaseModel):          # 声明你依赖的字段结构
    id: int
    title: str
    body: str | None


Issue.model_validate(new_issue.json())   # 结构不符会抛 ValidationError

17.4 API 造数 + UI 验证:最快组合拳

E2E 最慢的环节是用 UI 一路点到目标状态。把"铺垫"交给 API,把"验证"留给 UI:

python
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 章保存的登录态:

python
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 对象上,判断请求成功的惯用方式是?

🛠️ 动手实践

  1. https://reqres.in 或公司内网 API 编写一组 CRUD 用例,包含一个 pydantic schema 校验。
  2. 把第 9 章保存的 storage_state 接入 API 上下文,实现"免登录调用需授权接口"。
  3. 选一条现有 E2E 用例,把其中的 UI 铺垫步骤改写为 API 造数,对比前后耗时。

完成后进入下一章:异步 API 与 FastAPI 联测