Skip to main content

API 測試

簡介

Playwright 可用於存取您應用程式的 REST API。

有時您可能想要直接從 Python 向伺服器發送請求,而不載入頁面並在其中執行 js 程式碼。在以下情況下可能會很有用:

  • 測試您的伺服器 API。
  • 在測試中存取 Web 應用程式之前準備伺服器端狀態。
  • 在瀏覽器中執行某些操作後驗證伺服器端後置條件。

所有這些都可以透過 [APIRequestContext] 方法來實現。

以下範例依賴於 pytest-playwright 套件,它將 Playwright 佈置新增到 Pytest 測試執行器。

撰寫 API 測試

[APIRequestContext] 可以透過網路發送各種 HTTP(S) 請求。

以下範例示範如何使用 Playwright 透過 GitHub API 測試問題的建立。測試套件將執行以下操作:

  • 在執行測試前建立新的儲存庫。
  • 建立幾個問題並驗證伺服器狀態。
  • 在執行測試後刪除儲存庫。

設定

GitHub API 需要身份驗證,因此我們將為所有測試設定一次令牌。同時,我們也會設定 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 is not set"


@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 {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 is not set"

GITHUB_USER = os.getenv("GITHUB_USER")
assert GITHUB_USER, "GITHUB_USER is not set"

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"

設定和清理

這些測試假設儲存庫存在。您可能希望在執行測試前建立新的儲存庫,並在測試後刪除它。使用 session fixture 來實現這一點。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 is not set"

GITHUB_USER = os.getenv("GITHUB_USER")
assert GITHUB_USER, "GITHUB_USER is not set"

GITHUB_REPO = "test"


@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 {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 或基於令牌的身份驗證,其中認證狀態儲存為 cookies。Playwright 提供 api_request_context.storage_state() 方法,可用於從已認證的上下文中擷取儲存狀態,然後使用該狀態建立新的上下文。

儲存狀態在 [BrowserContext] 和 [APIRequestContext] 之間是可互換的。您可以使用它透過 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)