Skip to main content

模擬 API

簡介​

Web API 通常以 HTTP 端點的方式實作。Playwright 提供 API 來模擬和修改網路流量,包含 HTTP 和 HTTPS。頁面發出的任何請求,包括 XHR 和 fetch 請求,都可以被追蹤、修改和模擬。您也可以使用包含頁面多個網路請求的 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,這樣只有符合指定萬用字元模式的 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()

修改 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。