跳到主要内容

模拟 API

简介

Web API 通常以 HTTP 端点形式实现。Playwright 提供了 API 来模拟修改网络流量,包括 HTTP 和 HTTPS。页面发出的任何请求,包括 XHRfetch 请求,都可以被跟踪、修改和模拟。使用 Playwright,您还可以使用 HAR 文件进行模拟,HAR 文件包含页面发出的多个网络请求。

模拟 API 请求

以下代码将拦截所有对 */**/api/v1/fruits 的调用,并返回自定义响应。不会向 API 发出任何请求。测试访问使用模拟路由的 URL,并断言页面上存在模拟数据。

// Intercept the route to the fruit API
await page.RouteAsync("*/**/api/v1/fruits", async route => {
var json = new[] { new { name = "Strawberry", id = 21 } };
// fulfill the route with the mock data
await route.FulfillAsync(new()
{
Json = json
});
});

// Go to the page
await page.GotoAsync("https://demo.playwright.dev/api-mocking");

// Assert that the Strawberry fruit is visible
await Expect(page.GetByTextAsync("Strawberry")).ToBeVisibleAsync();

您可以从示例测试的跟踪中看到,API 从未被调用,但它已使用模拟数据完成。 api mocking trace

阅读更多关于高级网络的信息。

修改 API 响应

有时,必须发出 API 请求,但需要修补响应以实现可重现的测试。在这种情况下,可以执行请求并使用修改后的响应来完成它,而不是模拟请求。

在下面的示例中,我们拦截对水果 API 的调用,并将一个名为 'Loquat' 的新水果添加到数据中。然后,我们访问 URL 并断言该数据在那里

await page.RouteAsync("*/**/api/v1/fruits", async (route) => {
var response = await route.FetchAsync();
var fruits = await response.JsonAsync<Fruit[]>();
fruits.Add(new Fruit() { Name = "Loquat", Id = 100 });
// Fulfill using the original response, while patching the response body
// with the given JSON object.
await route.FulfillAsync(new ()
{
Response = response,
Json = fruits
});
}
);
// Go to the page
await page.GotoAsync("https://demo.playwright.dev/api-mocking");

// Assert that the Loquat fruit is visible
await Expect(page.GetByTextAsync("Loquat", new () { Exact = true })).ToBeVisibleAsync();

在我们的测试跟踪中,我们可以看到 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.RouteFromHARAsync()BrowserContext.RouteFromHARAsync() 方法。此方法接受 HAR 文件的路径和一个可选的选项对象。选项对象可以包含 URL,以便只有与指定 glob 模式匹配的 URL 的请求才会从 HAR 文件提供。如果未指定,则所有请求都将从 HAR 文件提供。

update 选项设置为 true 将创建或更新包含实际网络信息的 HAR 文件,而不是从 HAR 文件提供请求。在创建测试以使用真实数据填充 HAR 时使用它。

// Get the response from the HAR file
await page.RouteFromHARAsync("./hars/fruit.har", new () {
Url = "*/**/api/v1/fruits",
Update = true,
});

// Go to the page
await page.GotoAsync("https://demo.playwright.dev/api-mocking");

// Assert that the fruit is visible
await Expect(page.GetByText("Strawberry")).ToBeVisibleAsync();

修改 HAR 文件

录制 HAR 文件后,您可以通过打开 'hars' 文件夹中哈希的 .txt 文件并编辑 JSON 来修改它。此文件应提交到您的源代码控制。任何时候您使用 update: true 运行此测试,它都会使用来自 API 的请求更新您的 HAR 文件。

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

从 HAR 文件回放

现在您已经录制了 HAR 文件并修改了模拟数据,它可以用于在测试中提供匹配的响应。为此,只需关闭或直接删除 update 选项。这将针对 HAR 文件运行测试,而不是访问 API。

// Replay API requests from HAR.
// Either use a matching response from the HAR,
// or abort the request if nothing matches.
await page.RouteFromHARAsync("./hars/fruit.har", new ()
{
Url = "*/**/api/v1/fruits",
Update = false,
}
);

// Go to the page
await page.GotoAsync("https://demo.playwright.dev/api-mocking");

// Assert that the Playwright fruit is visible
await page.ExpectByTextAsync("Playwright", new() { Exact = true }).ToBeVisibleAsync();

在我们的测试跟踪中,我们可以看到路由是从 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.
pwsh bin/Debug/netX/playwright.ps1 open --save-har=example.har --save-har-glob="**/api/**" https://example.com

阅读更多关于高级网络的信息。

模拟 WebSocket

以下代码将拦截 WebSocket 连接并模拟 WebSocket 上的整个通信,而不是连接到服务器。此示例使用 "response" 响应 "request"

await page.RouteWebSocketAsync("wss://example.com/ws", ws => {
ws.OnMessage(frame => {
if (frame.Text == "request")
ws.Send("response");
});
});

或者,您可能希望连接到实际服务器,但拦截中间的消息并修改或阻止它们。这是一个示例,它修改了页面发送到服务器的某些消息,而其余消息保持不变。

await page.RouteWebSocketAsync("wss://example.com/ws", ws => {
var server = ws.ConnectToServer();
ws.OnMessage(frame => {
if (frame.Text == "request")
server.Send("request2");
else
server.Send(frame.Text);
});
});

有关更多详细信息,请参阅 WebSocketRoute