メインコンテンツへスキップ

APIテスト

はじめに

Playwrightは、アプリケーションのREST APIにアクセスするために使用できます。

ページを読み込んだり、その中でJavaScriptコードを実行したりせずに、Pythonから直接サーバーにリクエストを送信したい場合があります。いくつかの便利な例を挙げます

  • サーバーAPIをテストする。
  • テストでウェブアプリケーションにアクセスする前に、サーバー側の状態を準備する。
  • ブラウザでいくつかのアクションを実行した後、サーバー側の事後条件を検証する。

これらすべては、APIRequestContextメソッドを介して達成できます。

以下の例は、PlaywrightのフィクスチャをPytestテストランナーに追加するpytest-playwrightパッケージに依存しています。

APIテストの作成

APIRequestContextは、ネットワーク経由であらゆる種類のHTTP(S)リクエストを送信できます。

以下の例は、Playwrightを使用してGitHub API経由でissueの作成をテストする方法を示しています。テストスイートは以下を実行します

  • テストを実行する前に新しいリポジトリを作成する。
  • いくつかのissueを作成し、サーバーの状態を検証する。
  • テストの実行後にリポジトリを削除する。

設定

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 = {
# 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()

テストの作成

リクエストオブジェクトを初期化したので、リポジトリに新しいissueを作成するいくつかのテストを追加できます。

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"

セットアップとティアダウン

これらのテストは、リポジトリが存在することを前提としています。テストを実行する前に新しいものを作成し、後で削除することをお勧めします。そのためにセッションフィクスチャを使用してください。yieldの前の部分は「すべて実行前」、後の部分は「すべて実行後」です。

# ...
@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 = {
# 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経由で新しいissueを作成し、その後プロジェクト内のすべてのissueのリストに移動して、それがリストの先頭に表示されることを確認します。この確認は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")

ユーザーアクション実行後のサーバー状態の確認

以下のテストは、ブラウザのユーザーインターフェース経由で新しいissueを作成し、その後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として保存されるCookieベースまたはトークンベースの認証を使用します。Playwrightは、認証されたコンテキストからストレージ状態を取得し、その状態を持つ新しいコンテキストを作成するために使用できるapi_request_context.storage_state()メソッドを提供します。

ストレージ状態はBrowserContextAPIRequestContextの間で交換可能です。これを使用してAPI呼び出し経由でログインし、Cookieがすでに存在する新しいコンテキストを作成できます。以下のコードスニペットは、認証されたAPIRequestContextから状態を取得し、その状態を持つ新しいBrowserContextを作成します。

request_context = playwright.request.new_context(http_credentials={"username": "test", "password": "test"})
request_context.get("https://api.example.com/login")
# Save storage state into a variable.
state = request_context.storage_state()

# Create a new context with the saved storage state.
context = browser.new_context(storage_state=state)