📌 この記事はこんな方におすすめ
- PlaywrightのAPIRequestContextを使ったAPIテストを始めたい方
- 認証フロー(トークン取得・付与・拒否)のテスト実装に興味がある方
- pytestのfixtureを活用してテストデータを効率的に管理したい方
- REST APIのCRUD操作(POST・PUT・DELETE)を自動テストしたい方
✅ この記事を読むとわかること
- PlaywrightのAPIRequestContextでAPIテストを実装する方法
- 認証トークンをfixtureで管理してテスト間で共有するパターン
- GET/POST/PUT/DELETE の各HTTPメソッドのテスト実装
- 認証なしアクセスを正しく拒否することの自動テスト
APIテストというと「requestsライブラリ + pytest」の組み合わせが有名ですが、実はPlaywrightにもAPIRequestContextという強力なAPIテスト機能が搭載されています。
この記事では、ホテル予約APIの練習サイト Restful Booker を使って、認証フロー(ログイン→トークン取得→認証付きリクエスト)を6つのテストケースで自動化する実装例を解説します。
対象API・テスト構成
使用するAPI
| 項目 | 内容 |
|---|---|
| 対象サイト | Restful Booker(ホテル予約API練習サイト) |
| BASE URL | restful-booker.herokuapp.com |
| フレームワーク | Playwright(Python)+ pytest |
| 認証方式 | トークン認証(Cookie: token=xxx) |
6つのテストケース
| TC | HTTPメソッド | 内容 | 期待ステータス |
|---|---|---|---|
| TC01 | POST /auth | 正常ログイン → トークン取得 | 200 |
| TC02 | POST /auth | 異常ログイン(誤パスワード)→ エラー確認 | 200 + reason |
| TC03 | POST /booking | トークンを使って予約作成 | 200 |
| TC04 | PUT /booking/{id} | トークンを使って予約更新 | 200 |
| TC05 | DELETE /booking/{id} | トークンを使って予約削除 | 201 |
| TC06 | DELETE /booking/{id} | トークンなしで削除 → 拒否確認 | 403 |
環境構築
必要なパッケージのインストール
# Playwright + pytestのインストール
pip install playwright pytest pytest-playwright
# Playwrightのブラウザをインストール(APIテストはブラウザ不要だが念のため)
playwright install
playwright install は省略できますが、将来E2Eテストと組み合わせる場合は入れておくと便利です。
pytest.ini:HTMLレポートを自動生成する
プロジェクトのルートに pytest.ini を置くことで、テスト実行のたびにHTMLレポートが自動生成されます。テスト結果をブラウザで確認できるようになり、エビデンスとしても活用できます。
▼ フォルダ構成
project/
├── pytest.ini # ← ここに置く
├── test_auth_api.py
└── report.html # ← 実行後に自動生成される
▼ pytest.ini の内容
[pytest]
addopts = --html=report.html --self-contained-html
▼ 各オプションの意味
| オプション | 意味 |
|---|---|
--html=report.html |
report.html という名前でHTMLレポートを生成する |
--self-contained-html |
CSSや画像をHTMLに埋め込む。ファイル1つで完結するため共有しやすい |
▼ HTMLレポートの実行方法
# pytest.iniがあれば追加オプション不要で自動的にHTMLレポートが生成される
pytest test_auth_api.py -v -s
# 実行後、同フォルダに report.html が生成される
# ブラウザで開くとテスト結果が一覧で確認できる
pytest.ini にオプションを書いておけば、毎回コマンドに --html=report.html を打つ必要がなくなります。チームで共有するときも設定が統一されます。
pytest-html プラグインが必要です。未インストールの場合は先に追加してください。
pip install pytest-html
report.html がうまく表示されないときの対処法
report.html をダブルクリックで直接ブラウザで開くと、セキュリティ制限(CORS)によってスタイルが崩れたり内容が表示されないことがあります。そのような場合は、Pythonの内蔵サーバーを使ってローカルでホストする方法が有効です。
▼ 手順
| STEP | 操作内容 |
| 1 | report.html があるフォルダでターミナルを開く |
| 2 | 以下のコマンドを実行してローカルサーバーを起動する |
| 3 | ブラウザで localhost:8080/report.html を開く(URLバーに直接入力) |
# STEP 2:ローカルサーバーを起動(report.htmlがあるフォルダで実行)
python -m http.server 8080
# 起動後、ターミナルに以下のようなメッセージが表示される
# Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
# STEP 3:ブラウザでこのURLを開く
localhost:8080/report.html
print() で出力した内容(-s オプション使用時)
Ctrl + C を押してください。python -m http.server はPythonに標準搭載されており、追加インストールは不要です。
PlaywrightのAPIテスト基本パターン
実際のコードを見る前に、PlaywrightのAPIテストで押さえておきたい4つの基本パターンを整理します。
① APIRequestContext:HTTPクライアントの作成
PlaywrightでAPIリクエストを送るには、まず APIRequestContext を作成します。base_url を指定することで、以降のリクエストでパスだけ書けばよくなります。
# base_urlを指定してAPIRequestContextを作成
request_context = playwright.request.new_context(
base_url="https://restful-booker.herokuapp.com"
)
# 以降はパスだけでリクエストを送れる
response = request_context.get("/booking")
② トークン取得:認証エンドポイントへのPOST
認証APIにPOSTリクエストを送り、レスポンスのJSONからトークンを取り出す基本パターンです。
response = request_context.post(
"/auth",
data={"username": "admin", "password": "password123"}
)
# レスポンスJSONからトークンを取得
token = response.json()["token"]
print(token) # → "abc123xyz..."
③ 認証ヘッダーの渡し方:Cookie方式 vs Bearer方式
取得したトークンをAPIに渡す方法は、APIの仕様によって異なります。今回使用するRestful BookerはCookie方式です。一般的なREST APIではBearer方式が多いため、両方を覚えておくと他のAPIにも応用できます。
| 方式 | ヘッダーの書き方 | 主な用途 |
|---|---|---|
| Cookie方式 ← 今回 | "Cookie": f"token={token}" |
Restful Bookerなど一部のAPI |
| Bearer方式 | "Authorization": f"Bearer {token}" |
JWT認証など一般的なREST API |
# ✅ Cookie方式(今回のRestful Bookerで使用)
headers = {"Cookie": f"token={token}"}
# Bearer方式(一般的なREST APIで多い)
# headers = {"Authorization": f"Bearer {token}"}
403 Forbidden が返ってきます。
④ APIテストの実行:リクエスト送信と検証
ヘッダーを組み立てたらリクエストを送信し、ステータスコードとレスポンスボディを検証します。
# GETリクエスト(認証ヘッダー付き)
response = request_context.get(
"/booking/1",
headers={"Cookie": f"token={token}"}
)
# ステータスコードの検証
assert response.status == 200, f"期待値200に対して{response.status}が返りました"
# レスポンスボディの検証
body = response.json()
assert body["firstname"] == "Taro", "firstnameが期待値と異なります"
fixture設計:トークンと予約IDを効率的に管理する
このテストではpytestのfixtureを3層構造で設計しています。認証トークンと予約IDをセッション全体で共有することで、テストごとにログインし直す無駄を省いています。
▼ fixture依存関係
|
api_request_context セッション全体で1つ |
→ |
auth_token 一度取得して全テストで共有 |
→ |
booking_id TC03で作成→TC04/05で使用 |
fixture①:APIRequestContext
@pytest.fixture(scope="session")
def api_request_context(playwright: Playwright) -> APIRequestContext:
context = playwright.request.new_context(base_url=BASE_URL)
yield context
context.dispose() # テスト終了後に自動でリソースを解放
scope="session" を指定することで、テストセッション全体で1つのコンテキストを再利用します。毎テストごとに接続を張り直さないため高速です。
fixture②:認証トークン
@pytest.fixture(scope="session")
def auth_token(api_request_context: APIRequestContext) -> str:
response = api_request_context.post(
"/auth",
data={"username": USERNAME, "password": PASSWORD},
)
assert response.status == 200, "トークン取得に失敗しました"
token = response.json().get("token")
assert token, "レスポンスにtokenが含まれていません"
return token
fixture③:テスト用予約ID
@pytest.fixture(scope="session")
def booking_id(api_request_context: APIRequestContext, auth_token: str) -> int:
response = api_request_context.post(
"/booking",
headers={"Content-Type": "application/json", "Accept": "application/json"},
data="""{
"firstname": "Taro",
"lastname": "Yamada",
"totalprice": 12000,
"depositpaid": true,
"bookingdates": {
"checkin": "2025-01-01",
"checkout": "2025-01-05"
},
"additionalneeds": "Breakfast"
}""",
)
assert response.status == 200
booking_id = response.json().get("bookingid")
assert booking_id, "予約IDが取得できませんでした"
return booking_id
テストコード全文
TC01:正常ログイン → トークン取得
def test_tc01_login_success(api_request_context: APIRequestContext):
"""TC01: 正しい認証情報でトークンが取得できること"""
response = api_request_context.post(
"/auth",
data={"username": USERNAME, "password": PASSWORD},
)
assert response.status == 200, f"ステータスコードが200ではありません: {response.status}"
body = response.json()
assert "token" in body, "レスポンスにtokenが含まれていません"
assert len(body["token"]) > 0, "トークンが空です"
print(f"\n✅ TC01 PASS | Token取得成功: {body['token']}")
token キーが存在 / トークンが空でないこと
TC02:異常ログイン(誤ったパスワード)
def test_tc02_login_failure(api_request_context: APIRequestContext):
"""TC02: 誤ったパスワードでトークンが発行されないこと"""
response = api_request_context.post(
"/auth",
data={"username": USERNAME, "password": "wrongpassword"},
)
assert response.status == 200, f"ステータスコードが200ではありません: {response.status}"
body = response.json()
assert "token" not in body, "誤ったパスワードなのにトークンが返されました"
assert body.get("reason") == "Bad credentials", (
f"エラーメッセージが期待値と違います: {body.get('reason')}"
)
print(f"\n✅ TC02 PASS | 認証失敗を正しく検出: {body}")
reason: "Bad credentials" でエラーを判定します。実APIではステータス401が一般的ですが、APIによって仕様が異なります。
TC03:トークンを使って予約作成(POST)
def test_tc03_create_booking_with_token(
api_request_context: APIRequestContext, auth_token: str, booking_id: int
):
"""TC03: 認証済みトークンで予約が作成できること"""
assert booking_id > 0, "予約IDが正しくありません"
print(f"\n✅ TC03 PASS | 予約作成成功 (BookingID: {booking_id})")
booking_id fixtureの中で行われます。TC03はfixtureが正常に実行できたことを確認するだけのシンプルなテストです。
TC04:トークンを使って予約更新(PUT)
def test_tc04_update_booking_with_token(
api_request_context: APIRequestContext, auth_token: str, booking_id: int
):
"""TC04: 認証済みトークンで予約が更新できること"""
response = api_request_context.put(
f"/booking/{booking_id}",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"Cookie": f"token={auth_token}", # トークンをCookieヘッダーで渡す
},
data="""{
"firstname": "Hanako",
"lastname": "Yamada",
"totalprice": 15000,
"depositpaid": false,
"bookingdates": {
"checkin": "2025-02-01",
"checkout": "2025-02-07"
},
"additionalneeds": "Dinner"
}""",
)
assert response.status == 200, f"更新に失敗しました: {response.status}"
body = response.json()
assert body.get("firstname") == "Hanako", "firstnameが更新されていません"
assert body.get("totalprice") == 15000, "totalpriceが更新されていません"
print(f"\n✅ TC04 PASS | 予約更新成功: {body}")
TC05:トークンを使って予約削除(DELETE)
def test_tc05_delete_booking_with_token(
api_request_context: APIRequestContext, auth_token: str, booking_id: int
):
"""TC05: 認証済みトークンで予約が削除できること"""
response = api_request_context.delete(
f"/booking/{booking_id}",
headers={"Cookie": f"token={auth_token}"},
)
assert response.status == 201, f"削除に失敗しました: {response.status}"
print(f"\n✅ TC05 PASS | 予約削除成功 (BookingID: {booking_id})")
TC06:トークンなしで保護エンドポイントへアクセス(拒否確認)
def test_tc06_delete_booking_without_token(
api_request_context: APIRequestContext, booking_id: int
):
"""TC06: トークンなしで削除リクエストを送ると拒否されること"""
# TC05で削除済みなので同IDで再度試みる(認証エラーを確認)
response = api_request_context.delete(
f"/booking/{booking_id}",
headers={}, # Cookieなし(認証情報を渡さない)
)
assert response.status == 403, (
f"トークンなしで削除できてしまいました: {response.status}"
)
print(f"\n✅ TC06 PASS | 未認証アクセスを正しく拒否: status={response.status}")
実行方法と結果サンプル
テストの実行
# 通常実行
pytest test_auth_api.py -v
# print出力も表示する場合
pytest test_auth_api.py -v -s
実行結果サンプル(ターミナル)
実際に実行した結果です。6テスト全てが PASSED、合計 5.07秒 で完了しました。
▲ ターミナルでの実行結果 — TC01〜TC06 すべて PASSED
HTMLレポート(report.html)
pytest.ini の設定により自動生成された report.html をブラウザで開いた画面です。テスト名・実行時間・PASS/FAILが一覧で確認できます。
▲ report.html の表示結果 — 0 Failed / 6 Passed / 合計 5秒
設計のポイント:なぜこの構造にしたのか
毎テストでログインし直すのではなく、セッションで1回だけ取得。DRY原則の実践。
booking_id fixture が auth_token に依存する設計で、前提条件を自動保証。
TC06で「拒否されること」を検証。正常系だけでなく異常系・セキュリティも自動化。
APIRequestContextはブラウザを起動しないため、6テストが3秒台で完了。
📖 関連記事
まとめ
この記事では、PlaywrightのAPIRequestContextとpytestを使って認証フローを6つのテストケースで自動化する方法を解説しました。
📋 この記事のまとめ
- PlaywrightのAPIRequestContextはブラウザ不要で高速なAPIテストが書ける
- 認証トークンは
scope="session"のfixtureで管理することで全テストで共有できる - POST・PUT・DELETEの各HTTPメソッドの実装パターンを習得できた
- 異常系(誤パスワード)・セキュリティ(トークンなし拒否)のテストも自動化できる
- APIによってステータスコードの仕様が異なるため、必ずAPIドキュメントを確認する
PlaywrightはE2EテストだけでなくAPIテストにも対応しています。E2EとAPIテストを同じフレームワークで統一できるのも大きなメリットです。
