モックAPI
はじめに
Web APIは通常、HTTPエンドポイントとして実装されます。Playwrightは、HTTPおよびHTTPSの両方のネットワークトラフィックを**モック**および**変更**するためのAPIを提供します。ページが行うXHRやfetchリクエストを含むあらゆるリクエストを、追跡、変更、モックすることができます。Playwrightでは、ページによって行われた複数のネットワークリクエストを含むHARファイルを使用してモックすることもできます。
APIリクエストのモック
以下のコードは`*/**/api/v1/fruits`へのすべての呼び出しをインターセプトし、代わりにカスタムレスポンスを返します。APIへのリクエストは行われません。テストはモックされたルートを使用するURLに移動し、モックデータがページに存在することを確認します。
- 同期
- 非同期
def test_mock_the_fruit_api(page: Page):
def handle(route: Route):
json = [{"name": "Strawberry", "id": 21}]
# fulfill the route with the mock data
route.fulfill(json=json)
# Intercept the route to the fruit API
page.route("*/**/api/v1/fruits", handle)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Strawberry fruit is visible
expect(page.get_by_text("Strawberry")).to_be_visible()
async def test_mock_the_fruit_api(page: Page):
async def handle(route: Route):
json = [{"name": "Strawberry", "id": 21}]
# fulfill the route with the mock data
await route.fulfill(json=json)
# Intercept the route to the fruit API
await page.route("*/**/api/v1/fruits", handle)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Strawberry fruit is visible
await expect(page.get_by_text("Strawberry")).to_be_visible()
サンプルテストのトレースから、APIが一度も呼び出されず、モックデータで応答されたことがわかります。
詳細については、高度なネットワークを参照してください。
APIレスポンスの変更
APIリクエストを行うことが不可欠な場合でも、再現可能なテストを可能にするためにレスポンスをパッチする必要があることがあります。その場合、リクエストをモックするのではなく、実際にリクエストを実行し、変更されたレスポンスで応答することができます。
以下の例では、フルーツAPIへの呼び出しをインターセプトし、データに「ビワ(Loquat)」という新しいフルーツを追加します。その後、URLに移動し、このデータが存在することを確認します。
- 同期
- 非同期
def test_gets_the_json_from_api_and_adds_a_new_fruit(page: Page):
def handle(route: Route):
response = route.fetch()
json = response.json()
json.append({ "name": "Loquat", "id": 100})
# Fulfill using the original response, while patching the response body
# with the given JSON object.
route.fulfill(response=response, json=json)
page.route("https://demo.playwright.dev/api-mocking/api/v1/fruits", handle)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the new fruit is visible
expect(page.get_by_text("Loquat", exact=True)).to_be_visible()
async def test_gets_the_json_from_api_and_adds_a_new_fruit(page: Page):
async def handle(route: Route):
response = await route.fetch()
json = await response.json()
json.append({ "name": "Loquat", "id": 100})
# Fulfill using the original response, while patching the response body
# with the given JSON object.
await route.fulfill(response=response, json=json)
await page.route("https://demo.playwright.dev/api-mocking/api/v1/fruits", handle)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the new fruit is visible
await expect(page.get_by_text("Loquat", exact=True)).to_be_visible()
テストのトレースでは、APIが呼び出され、レスポンスが変更されたことがわかります。
レスポンスを検査すると、新しいフルーツがリストに追加されたことがわかります。
詳細については、高度なネットワークを参照してください。
HARファイルによるモック
HARファイルは、ページがロードされたときに行われるすべてのネットワークリクエストの記録を含むHTTPアーカイブファイルです。リクエストとレスポンスのヘッダー、クッキー、コンテンツ、タイミングなどの情報が含まれています。HARファイルを使用して、テストでネットワークリクエストをモックできます。必要な手順は次のとおりです。
- HARファイルを記録する。
- テストと一緒にHARファイルをコミットする。
- テストで保存されたHARファイルを使用してリクエストをルーティングする。
HARファイルの記録
HARファイルを記録するには、page.route_from_har() または browser_context.route_from_har() メソッドを使用します。このメソッドは、HARファイルへのパスとオプションのオブジェクトを受け取ります。オプションオブジェクトにはURLを含めることができ、これにより指定されたグロブパターンに一致するURLを持つリクエストのみがHARファイルから提供されます。指定されていない場合、すべてのリクエストはHARファイルから提供されます。
`update`オプションを`true`に設定すると、HARファイルからリクエストを提供する代わりに、実際のネットワーク情報でHARファイルが作成または更新されます。テストを作成する際に、HARに実際のデータを格納するために使用してください。
- 同期
- 非同期
def test_records_or_updates_the_har_file(page: Page):
# Get the response from the HAR file
page.route_from_har("./hars/fruit.har", url="*/**/api/v1/fruits", update=True)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the fruit is visible
expect(page.get_by_text("Strawberry")).to_be_visible()
async def test_records_or_updates_the_har_file(page: Page):
# Get the response from the HAR file
await page.route_from_har("./hars/fruit.har", url="*/**/api/v1/fruits", update=True)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the fruit is visible
await expect(page.get_by_text("Strawberry")).to_be_visible()
HARファイルの変更
HARファイルを記録したら、'hars'フォルダ内のハッシュ化された.txtファイルを開いてJSONを編集することで変更できます。このファイルはソース管理にコミットする必要があります。`update: true`でこのテストを実行するたびに、APIからのリクエストでHARファイルが更新されます。
[
{
"name": "Playwright",
"id": 100
},
// ... other fruits
]
HARからのリプレイ
HARファイルを記録し、モックデータを変更したので、テストで一致するレスポンスを提供するために使用できます。そのためには、`update`オプションをオフにするか、単に削除するだけです。これにより、APIにアクセスする代わりに、HARファイルに対してテストが実行されます。
- 同期
- 非同期
def test_gets_the_json_from_har_and_checks_the_new_fruit_has_been_added(page: Page):
# Replay API requests from HAR.
# Either use a matching response from the HAR,
# or abort the request if nothing matches.
page.route_from_har("./hars/fruit.har", url="*/**/api/v1/fruits", update=False)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Playwright fruit is visible
expect(page.get_by_text("Playwright", exact=True)).to_be_visible()
async def test_gets_the_json_from_har_and_checks_the_new_fruit_has_been_added(page: Page):
# Replay API requests from HAR.
# Either use a matching response from the HAR,
# or abort the request if nothing matches.
await page.route_from_har("./hars/fruit.har", url="*/**/api/v1/fruits", update=False)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Playwright fruit is visible
await expect(page.get_by_text("Playwright", exact=True)).to_be_visible()
テストのトレースでは、ルートがHARファイルから満たされ、APIが呼び出されなかったことがわかります。
レスポンスを検査すると、`hars`フォルダ内のハッシュ化された`.txt`ファイルを手動で更新することで、新しいフルーツがJSONに追加されていることがわかります。
HARのリプレイは、URLとHTTPメソッドに厳密に一致します。POSTリクエストの場合、POSTペイロードも厳密に一致します。複数の記録がリクエストに一致する場合、最も一致するヘッダーを持つものが選択されます。リダイレクトにつながるエントリは自動的に追跡されます。
記録時と同様に、指定されたHARファイル名が`.zip`で終わる場合、それはHARファイルと別々のエントリとして保存されたネットワークペイロードを含むアーカイブと見なされます。このアーカイブを解凍し、ペイロードやHARログを手動で編集して、解凍されたHARファイルを指定することもできます。すべてのペイロードは、ファイルシステム上の解凍されたHARファイルに対して相対的に解決されます。
CLIでのHARの記録
テスト用のHARファイルを記録するには、`update`オプションを使用することをお勧めします。ただし、Playwright CLIを使用してHARを記録することもできます。
Playwright CLIでブラウザを開き、`--save-har`オプションを渡してHARファイルを生成します。オプションとして、`--save-har-glob`を使用して、APIエンドポイントなど、関心のあるリクエストのみを保存できます。HARファイル名が`.zip`で終わる場合、アーティファクトは個別のファイルとして書き込まれ、すべて単一の`zip`に圧縮されます。
# Save API requests from example.com as "example.har" archive.
playwright open --save-har=example.har --save-har-glob="**/api/**" https://example.com
詳細については、高度なネットワークを参照してください。
WebSocketのモック
以下のコードはWebSocket接続をインターセプトし、サーバーに接続する代わりにWebSocket経由の通信全体をモックします。この例では、`"request"`に対して`"response"`で応答します。
- 同期
- 非同期
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
ws.send("response")
page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
lambda message: message_handler(ws, message)
))
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
ws.send("response")
await page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
lambda message: message_handler(ws, message)
))
あるいは、実際のサーバーに接続しつつ、途中のメッセージをインターセプトして変更またはブロックすることもできます。以下は、ページからサーバーに送信されるメッセージの一部を変更し、残りは変更しない例です。
- 同期
- 非同期
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
server.send("request2")
else:
server.send(message)
def handler(ws: WebSocketRoute):
server = ws.connect_to_server()
ws.on_message(lambda message: message_handler(server, message))
page.route_web_socket("wss://example.com/ws", handler)
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
server.send("request2")
else:
server.send(message)
def handler(ws: WebSocketRoute):
server = ws.connect_to_server()
ws.on_message(lambda message: message_handler(server, message))
await page.route_web_socket("wss://example.com/ws", handler)
詳細については、WebSocketRouteを参照してください。