跳至主要内容

Mock APIs

简介

Web API通常以HTTP端点形式实现。Playwright提供了API来模拟修改网络流量,包括HTTP和HTTPS。页面发出的任何请求,包括XHRsfetch请求,都可以被跟踪、修改和模拟。使用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的调用,并向数据中添加了一种名为'枇杷'的新水果。然后我们访问该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存档文件,记录了页面加载时发出的所有网络请求。它包含请求和响应头信息、cookies、内容、时间等数据。您可以在测试中使用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。

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

了解更多关于高级网络的信息。

模拟WebSockets

以下代码将拦截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