模拟 API
简介
Web API 通常作为 HTTP 端点实现。Playwright 提供了用于模拟和修改(包括 HTTP 和 HTTPS)网络流量的 API。页面进行的任何请求,包括 XHR 和 fetch 请求,都可以被追踪、修改和模拟。使用 Playwright,您还可以使用包含页面发出的多个网络请求的 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 响应
有时,必须发出 API 请求,但需要修改响应以进行可重现的测试。在这种情况下,可以执行请求并使用修改后的响应来完成它,而不是模拟请求。
在下面的示例中,我们拦截对水果 API 的调用,并向数据中添加一个名为“枇杷”的新水果。然后我们访问该 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 被调用并且响应被修改了。
通过检查响应,我们可以看到我们的新水果已添加到列表中。
阅读更多关于高级网络的内容。
使用 HAR 文件进行模拟
HAR 文件是一种 HTTP 存档文件,其中包含加载页面时发出的所有网络请求记录。它包含有关请求和响应头、Cookie、内容、时间等信息。您可以使用 HAR 文件在测试中模拟网络请求。您需要执行以下步骤:
- 录制 HAR 文件。
- 将 HAR 文件与测试一起提交。
- 在测试中使用保存的 HAR 文件来路由请求。
录制 HAR 文件
要录制 HAR 文件,我们使用 Page.RouteFromHARAsync() 或 BrowserContext.RouteFromHARAsync() 方法。此方法接受 HAR 文件的路径和一个可选的选项对象。选项对象可以包含 URL,以便只有 URL 与指定的 glob 模式匹配的请求才会从 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。
如果我们检查响应,可以看到我们的新水果已添加到 JSON 中,这是通过手动更新 hars
文件夹中的哈希 .txt
文件完成的。
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
阅读更多关于高级网络的内容。
模拟 WebSockets
以下代码将拦截 WebSocket 连接,并模拟整个 WebSocket 通信,而不是连接到服务器。这个示例会回应一个 "request"
,返回一个 "response"
。
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。