跳到主要内容

API 测试

简介

Playwright 可以用来访问你的应用程序的 REST API。

有时你可能想直接从 Python 发送请求到服务器而不加载页面和运行其中的 JavaScript 代码。以下是一些可能需要这样做的例子:

  • 测试你的服务器端 API。
  • 在测试中访问网页应用之前准备服务器端状态。
  • 在浏览器中执行一些操作后验证服务器端的后续条件。

所有这些都可以通过 APIRequestContext 方法实现。

以下示例依赖于 pytest-playwright 包,它将 Playwright 夹具添加到了 Pytest 测试运行器中。

编写 API 测试

APIRequestContext 可以通过网络发送各种 HTTP(S) 请求。

以下示例演示了如何使用 Playwright 通过 GitHub API 创建问题。测试套件将完成以下工作:

  • 在运行测试前创建一个新的仓库。
  • 创建几个问题并验证服务器状态。
  • 删除仓库。

配置

GitHub API 需要授权,因此我们将为所有测试配置一次 token。同时,我们也将设置 baseURL 以简化测试。

import os
from typing import Generator

import pytest
from playwright.sync_api import Playwright, APIRequestContext

GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")
assert GITHUB_API_TOKEN, "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",
# 向所有请求添加授权 token。
# 假设个人访问 token 在环境中可用。
"Authorization": f"token {GITHUB_API_TOKEN}",
}
request_context = playwright.request.new_context(
base_url="https://api.github.com", extra_http_headers=headers
)
yield request_context
request_context.dispose()

编写测试

现在我们已经初始化了请求对象,我们可以添加一些将在仓库中创建新问题的测试。

import os
from typing import Generator

import pytest
from playwright.sync_api import Playwright, APIRequestContext

GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")
assert GITHUB_API_TOKEN, "GITHUB_API_TOKEN 未设置"

GITHUB_USER = os.getenv("GITHUB_USER")
assert GITHUB_USER, "GITHUB_USER 未设置"

GITHUB_REPO = "test"

# ...

def test_should_create_bug_report(api_request_context: APIRequestContext) -> None:
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

issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issues_response = issues.json()
issue = list(filter(lambda issue: issue["title"] == "[Bug] report 1", issues_response))[0]
assert issue
assert issue["body"] == "Bug description"

def test_should_create_feature_request(api_request_context: APIRequestContext) -> None:
data = {
"title": "[Feature] request 1",
"body": "Feature description",
}
new_issue = api_request_context.post(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data)
assert new_issue.ok

issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issues_response = issues.json()
issue = list(filter(lambda issue: issue["title"] == "[Feature] request 1", issues_response))[0]
assert issue
assert issue["body"] == "Feature description"

设置和清理

这些测试假设仓库存在。你可能希望在运行测试前创建一个新仓库,并在之后删除它。为此使用一个 会话夹具yield 之前的那部分是 before all,之后的部分是 after all。

# ...
@pytest.fixture(scope="session", autouse=True)
def create_test_repository(
api_request_context: APIRequestContext,
) -> Generator[None, None, None]:
# Before all
new_repo = api_request_context.post("/user/repos", data={"name": GITHUB_REPO})
assert new_repo.ok
yield
# After all
deleted_repo = api_request_context.delete(f"/repos/{GITHUB_USER}/{GITHUB_REPO}")
assert deleted_repo.ok

完整测试示例

以下是完整的 API 测试示例:

from enum import auto
import os
from typing import Generator

import pytest
from playwright.sync_api import Playwright, Page, APIRequestContext, expect

GITHUB_API_TOKEN = os.getenv("GITHUB_API_TOKEN")
assert GITHUB_API_TOKEN, "GITHUB_API_TOKEN 未设置"

GITHUB_USER = os.getenv("GITHUB_USER")
assert GITHUB_USER, "GITHUB_USER 未设置"

GITHUB_REPO = "test"


@pytest.fixture(scope="session")
def api_request_context(
playwright: Playwright,
) -> Generator[APIRequestContext, None, None]:
headers = {
# We set this header per GitHub guidelines.
"Accept": "application/vnd.github.v3+json",
# Add authorization token to all requests.
# Assuming personal access token available in the environment.
"Authorization": f"token {GITHUB_API_TOKEN}",
}
request_context = playwright.request.new_context(
base_url="https://api.github.com", extra_http_headers=headers
)
yield request_context
request_context.dispose()


@pytest.fixture(scope="session", autouse=True)
def create_test_repository(
api_request_context: APIRequestContext,
) -> Generator[None, None, None]:
# Before all
new_repo = api_request_context.post("/user/repos", data={"name": GITHUB_REPO})
assert new_repo.ok
yield
# After all
deleted_repo = api_request_context.delete(f"/repos/{GITHUB_USER}/{GITHUB_REPO}")
assert deleted_repo.ok


def test_should_create_bug_report(api_request_context: APIRequestContext) -> None:
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

issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issues_response = issues.json()
issue = list(
filter(lambda issue: issue["title"] == "[Bug] report 1", issues_response)
)[0]
assert issue
assert issue["body"] == "Bug description"


def test_should_create_feature_request(api_request_context: APIRequestContext) -> None:
data = {
"title": "[Feature] request 1",
"body": "Feature description",
}
new_issue = api_request_context.post(
f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data
)
assert new_issue.ok

issues = api_request_context.get(f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues")
assert issues.ok
issues_response = issues.json()
issue = list(
filter(lambda issue: issue["title"] == "[Feature] request 1", issues_response)
)[0]
assert issue
assert issue["body"] == "Feature description"

通过 API 调用准备服务器状态

以下测试通过 API 创建一个新问题,然后导航到项目中的所有问题列表以检查它是否出现在列表顶部。使用 LocatorAssertions 进行验证。

def test_last_created_issue_should_be_first_in_the_list(api_request_context: APIRequestContext, page: Page) -> None:
def create_issue(title: str) -> None:
data = {
"title": title,
"body": "Feature description",
}
new_issue = api_request_context.post(
f"/repos/{GITHUB_USER}/{GITHUB_REPO}/issues", data=data
)
assert new_issue.ok
create_issue("[Feature] request 1")
create_issue("[Feature] request 2")
page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")
first_issue = page.locator("a[data-hovercard-type='issue']").first
expect(first_issue).to_have_text("[Feature] request 2")

在执行用户操作后检查服务器状态

以下测试通过浏览器中的用户界面创建一个新问题,然后通过 API 检查它是否已创建:

def test_last_created_issue_should_be_on_the_server(api_request_context: APIRequestContext, page: Page) -> None:
page.goto(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues")
page.locator("text=New issue").click()
page.locator("[aria-label='Title']").fill("Bug report 1")
page.locator("[aria-label='Comment body']").fill("Bug description")
page.locator("text=Submit new issue").click()
issue_id = page.url.split("/")[-1]

new_issue = api_request_context.get(f"https://github.com/{GITHUB_USER}/{GITHUB_REPO}/issues/{issue_id}")
assert new_issue.ok
assert new_issue.json()["title"] == "[Bug] report 1"
assert new_issue.json()["body"] == "Bug description"

重用认证状态

Web 应用程序使用基于 cookie 或 token 的认证方式,其中认证状态存储为 cookies。Playwright 提供了 api_request_context.storage_state() 方法,可以用来从经过身份验证的上下文中检索存储状态,然后使用该状态创建新的上下文。

存储状态可以在 BrowserContextAPIRequestContext 之间互换。你可以使用它通过 API 调用来登录,然后创建一个带有这些 cookies 的新上下文。以下代码片段从经过身份验证的 APIRequestContext 获取状态,并使用该状态创建一个新的 BrowserContext

request_context = playwright.request.new_context(http_credentials={"username": "test", "password": "test"})
request_context.get("https://api.example.com/login")
# 将存储状态保存到变量中。
state = request_context.storage_state()

# 使用保存的存储状态创建一个新上下文。
context = browser.new_context(storage_state=state)