跳到主要內容

模擬 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 封存 檔案,其中包含頁面載入時發出的所有網路請求的記錄。它包含有關請求和回應標頭、Cookie、內容、時序等的資訊。您可以使用 HAR 檔案在測試中模擬網路請求。您需要

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

錄製 HAR 檔案

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