跳到主要內容

模擬 API

簡介

Web API 通常實作為 HTTP 端點。Playwright 提供 API 來模擬修改網路流量,包括 HTTP 和 HTTPS。頁面執行的任何請求,包括 XHRfetch 請求,都可以被追蹤、修改和模擬。透過 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()

您可以從範例測試的追蹤中看到,API 從未被呼叫,但它已使用模擬資料完成。api mocking trace

深入了解進階網路功能

修改 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()

在我們的測試追蹤中,我們可以看到 API 被呼叫,並且回應被修改。trace of test showing api being called and fulfilled

透過檢查回應,我們可以看見我們的新水果已新增到列表中。trace of test showing the mock response

深入了解進階網路功能

使用 HAR 檔案進行模擬

HAR 檔案是一個 HTTP Archive 檔案,其中包含頁面載入時所發出的所有網路請求的記錄。它包含有關請求和回應標頭、Cookie、內容、時序等的資訊。您可以使用 HAR 檔案在測試中模擬網路請求。您需要

  1. 錄製 HAR 檔案。
  2. 將 HAR 檔案與測試一起提交。
  3. 在測試中使用已儲存的 HAR 檔案來路由請求。

錄製 HAR 檔案

為了錄製 HAR 檔案,我們使用 page.route_from_har()browser_context.route_from_har() 方法。此方法接收 HAR 檔案的路徑和一個可選的選項物件。選項物件可以包含 URL,以便只有與指定 glob 模式相符的 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()

修改 HAR 檔案

一旦您錄製了 HAR 檔案,您可以透過開啟 'hars' 資料夾內的雜湊 .txt 檔案並編輯 JSON 來修改它。此檔案應提交到您的原始碼控制。任何時候您使用 update: true 執行此測試,它都會使用來自 API 的請求來更新您的 HAR 檔案。

[
{
"name": "Playwright",
"id": 100
},
// ... other fruits
]

從 HAR 重新播放

現在您已經錄製並修改了模擬資料的 HAR 檔案,它可以用於在測試中提供相符的回應。為此,只需關閉或直接移除 update 選項。這將針對 HAR 檔案執行測試,而不是訪問 API。

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()

在我們的測試追蹤中,我們可以看到路由是從 HAR 檔案完成的,而且 API 沒有被呼叫。trace showing the HAR file being used

如果我們檢查回應,我們可以看見我們的新水果已新增到 JSON 中,這是透過手動更新 hars 資料夾內的雜湊 .txt 檔案來完成的。trace showing response from HAR file

HAR 重新播放嚴格比對 URL 和 HTTP 方法。對於 POST 請求,它也嚴格比對 POST 酬載。如果多個錄製符合請求,則會選擇具有最多相符標頭的那個。導致重新導向的項目將自動跟隨。

與錄製時類似,如果給定的 HAR 檔案名稱以 .zip 結尾,則它被視為一個封存檔,其中包含 HAR 檔案以及儲存為個別項目的網路酬載。您也可以解壓縮此封存檔,手動編輯酬載或 HAR 日誌,並指向解壓縮的 har 檔案。所有酬載都將相對於檔案系統上解壓縮的 har 檔案進行解析。

使用 CLI 錄製 HAR

我們建議使用 update 選項來為您的測試錄製 HAR 檔案。但是,您也可以使用 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 上的通訊,而不是連線到伺服器。此範例以 "response" 回應 "request"

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(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)

如需更多詳細資訊,請參閱 WebSocketRoute