跳到主要内容

模拟 API

介绍

Web API 通常实现为 HTTP 端点。Playwright 提供了用于模拟修改网络流量的 API,包括 HTTP 和 HTTPS。页面进行的任何请求,包括 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()

从示例测试的追踪中可以看到,API 从未被调用,但响应已通过模拟数据完成。 api 模拟追踪

阅读更多关于高级网络的内容。

修改 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 被调用并且响应被修改了。 显示 API 被调用和响应被完成的测试追踪

通过检查响应,我们可以看到我们的新水果已添加到列表中。 显示模拟响应的测试追踪

阅读更多关于高级网络的内容。

使用 HAR 文件进行模拟

HAR 文件是 HTTP Archive 文件,其中包含了页面加载时发出的所有网络请求的记录。它包含有关请求和响应头、cookies、内容、时间等信息。您可以使用 HAR 文件在测试中模拟网络请求。您需要进行以下步骤:

  1. 记录 HAR 文件。
  2. 将 HAR 文件与测试一起提交。
  3. 在测试中使用保存的 HAR 文件路由请求。

记录 HAR 文件

要记录 HAR 文件,我们使用 page.route_from_har()browser_context.route_from_har() 方法。此方法接受 HAR 文件路径和一个可选的 options 对象。options 对象可以包含 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。 显示正在使用 HAR 文件的追踪

如果我们检查响应,可以看到我们的新水果被添加到了 JSON 中,这是通过手动更新 hars 文件夹中经过哈希处理的 .txt 文件完成的。 显示来自 HAR 文件的响应的追踪

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 的整个通信过程,而不是连接到服务器。此示例对 "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(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