跳至主要内容

Mock APIs

简介

Web API通常以HTTP端点形式实现。Playwright提供了API来模拟修改网络流量,包括HTTP和HTTPS。页面发出的任何请求,包括XHRsfetch请求,都可以被跟踪、修改和模拟。使用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 mocking trace

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

修改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被调用且响应已被修改。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,这样只有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

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

模拟WebSockets

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