モック 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アーカイブファイルです。リクエストと応答のヘッダー、Cookie、コンテンツ、タイミングなどに関する情報が含まれています。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に実際のデータを入力するときに使用します。
または、ブラウザコンテキストを作成する際に、browser.new_context()のrecord_har_pathオプションを使用して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が呼び出されなかったことがわかります。
応答を検査すると、新しい果物がJSONに追加されたことがわかります。これは、「hars」フォルダ内のハッシュされた.txtファイルを手動で更新することによって行われました。
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を参照してください。