跳至主要内容

模拟 API

介绍

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

模拟 API 请求

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

// Intercept the route to the fruit API
page.route("https://fruit.ceo/api/breeds/image/random", route -> {
List<Dictionary<String, Object>> data = new ArrayList<Dictionary<String, Object>>();
Hashtable<String, Object> dict = new Hashtable<String, Object>();
dict.put("name", "Strawberry");
dict.put("id", 21);
data.add(dict);
// fulfill the route with the mock data
route.fulfill(RequestOptions.create().setData(data));
});

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

// Assert that the Strawberry fruit is visible
assertThat(page.getByText("Strawberry")).isVisible();

您可以从示例测试的追踪中看到,API 从未被调用,但它确实使用了模拟数据进行了填充。 模拟 API 追踪

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

修改 API 响应

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

在下面的示例中,我们拦截对水果 API 的调用,并在数据中添加了一个名为 'Loquat' 的新水果。然后,我们转到 URL 并断言此数据存在。

page.route("*/**/api/v1/fruits", route -> {
Response response = route.fetch();
byte[] json = response.body();
parsed = new Gson().fromJson(json, JsonObject.class)
parsed.add(new JsonObject().add("name", "Loquat").add("id", 100));
// Fulfill using the original response, while patching the response body
// with the given JSON object.
route.fulfill(new Route.FulfillOptions().setResponse(response).setBody(json.toString()));
});

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

// Assert that the Loquat fruit is visible
assertThat(page.getByText("Loquat", new Page.GetByTextOptions().setExact(true))).isVisible();

在我们的测试追踪中,我们可以看到 API 被调用,并且响应被修改了。 显示 API 被调用并完成的测试追踪

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

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

使用 HAR 文件进行模拟

HAR 文件是 HTTP 档案 文件,它包含加载页面时发出的所有网络请求的记录。它包含有关请求和响应标头、cookie、内容、计时等信息。您可以使用 HAR 文件来模拟测试中的网络请求。您需要

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

录制 HAR 文件

要录制 HAR 文件,我们使用 Page.routeFromHAR()BrowserContext.routeFromHAR() 方法。此方法接收 HAR 文件的路径和一个可选的选项对象。选项对象可以包含 URL,以便仅使用与指定 glob 模式匹配的 URL 的请求从 HAR 文件中提供服务。如果没有指定,所有请求都将从 HAR 文件中提供服务。

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

// Get the response from the HAR file
page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions()
.setUrl("*/**/api/v1/fruits")
.setUpdate(true)
);

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

// Assert that the fruit is visible
assertThat(page.getByText("Strawberry")).isVisible();

修改 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.
page.routeFromHAR(Path.of("./hars/fruit.har"), new RouteFromHAROptions()
.setUrl("*/**/api/v1/fruits")
.setUpdate(false)
);

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

// Assert that the Playwright fruit is visible
assertThat(page.getByText("Playwright", new Page.GetByTextOptions().setExact(true))).isVisible();

在我们的测试追踪中,我们可以看到路由从 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.
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="open --save-har=example.har --save-har-glob='**/api/**' https://example.com"

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

模拟 WebSocket

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

page.routeWebSocket("wss://example.com/ws", ws -> {
ws.onMessage(message -> {
if ("request".equals(message))
ws.send("response");
});
});

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

page.routeWebSocket("wss://example.com/ws", ws -> {
WebSocketRoute server = ws.connectToServer();
ws.onMessage(message -> {
if ("request".equals(message))
server.send("request2");
else
server.send(message);
});
});

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