Mock API
简介
Web API 通常作为 HTTP 端点实现。Playwright 提供 API 来模拟和修改网络流量,包括 HTTP 和 HTTPS。页面发出的任何请求,包括 XHR 和 fetch 请求,都可以被跟踪、修改和模拟。使用 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 的调用,并向数据中添加了一种名为“枇杷”的新水果。然后我们转到 URL 并断言该数据存在。
page.route("*/**/api/v1/fruits", route -> {
Response response = route.fetch();
byte[] json = response.body();
JsonObject parsed = new Gson().fromJson(new String(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(parsed.toString()));
});
// Go to the page
page.navigate("https://demo.playwright.dev/api-mocking");
// Assert that the Loquat fruit is visible
assertThat(page.getByText("Loquat", new Page.GetByTextOptions().setExact(true))).isVisible();
在我们的测试跟踪中,我们可以看到 API 被调用并且响应被修改了。
通过检查响应,我们可以看到我们的新水果已被添加到列表中。
阅读更多关于 高级网络 的信息。
使用 HAR 文件进行模拟
HAR 文件是 HTTP 存档 文件,其中包含页面加载时发出的所有网络请求的记录。它包含有关请求和响应头、Cookie、内容、时间等信息。您可以使用 HAR 文件在测试中模拟网络请求。您需要
- 记录 HAR 文件。
- 将 HAR 文件与测试一起提交。
- 在测试中使用保存的 HAR 文件路由请求。
记录 HAR 文件
要记录 HAR 文件,我们使用 Page.routeFromHAR() 或 BrowserContext.routeFromHAR() 方法。此方法接受 HAR 文件的路径和可选的选项对象。选项对象可以包含 URL,以便只有与指定全局模式匹配的 URL 请求才会从 HAR 文件中提供。如果未指定,所有请求都将从 HAR 文件中提供。
将 update
选项设置为 true 将创建或更新 HAR 文件,其中包含实际网络信息,而不是从 HAR 文件提供请求。在创建测试以使用真实数据填充 HAR 时使用它。
或者,您还可以在创建浏览器上下文时,在 Browser.newContext() 中使用 setRecordHarPath 选项来记录 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.navigate("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.navigate("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。
如果我们检查响应,我们可以看到我们的新水果已添加到 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.
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"
阅读更多关于 高级网络 的信息。
模拟 WebSockets
以下代码将拦截 WebSocket 连接并模拟整个 WebSocket 通信,而不是连接到服务器。此示例响应 "request"
并带有一个 "response"
。
page.routeWebSocket("wss://example.com/ws", ws -> {
ws.onMessage(frame -> {
if ("request".equals(frame.text()))
ws.send("response");
});
});
或者,您可能希望连接到实际服务器,但在中间拦截消息并修改或阻止它们。这是一个修改页面发送给服务器的一些消息,并保持其余消息不变的示例。
page.routeWebSocket("wss://example.com/ws", ws -> {
WebSocketRoute server = ws.connectToServer();
ws.onMessage(frame -> {
if ("request".equals(frame.text()))
server.send("request2");
else
server.send(frame.text());
});
});
更多详情,请参阅 WebSocketRoute。