跳到主内容

模拟 API

简介

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

模拟 API 请求

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

test("mocks a fruit and doesn't call api", async ({ page }) => {
// Mock the api call before navigating
await page.route('*/**/api/v1/fruits', async route => {
const json = [{ name: 'Strawberry', id: 21 }];
await route.fulfill({ json });
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');

// Assert that the Strawberry fruit is visible
await expect(page.getByText('Strawberry')).toBeVisible();
});

从示例测试的跟踪中可以看出,API 从未被调用,但已通过模拟数据填充了响应。 api mocking trace

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

修改 API 响应

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

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

test('gets the json from api and adds a new fruit', async ({ page }) => {
// Get the response and add to it
await page.route('*/**/api/v1/fruits', async route => {
const response = await route.fetch();
const json = await response.json();
json.push({ name: 'Loquat', id: 100 });
// Fulfill using the original response, while patching the response body
// with the given JSON object.
await route.fulfill({ response, json });
});

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

// Assert that the new fruit is visible
await expect(page.getByText('Loquat', { exact: true })).toBeVisible();
});

在测试跟踪中,我们可以看到 API 被调用并且响应被修改了。 trace of test showing api being called and fulfilled

通过检查响应,我们可以看到我们的新水果已添加到列表中。 trace of test showing the mock response

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

使用 HAR 文件进行模拟

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

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

录制 HAR 文件

要录制 HAR 文件,我们使用 page.routeFromHAR()browserContext.routeFromHAR() 方法。此方法接受 HAR 文件的路径和一个可选的 options 对象。options 对象可以包含 URL,以便只有 URL 与指定的 glob 模式匹配的请求才会从 HAR 文件中提供。如果未指定,所有请求都将从 HAR 文件中提供。

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

test('records or updates the HAR file', async ({ page }) => {
// Get the response from the HAR file
await page.routeFromHAR('./hars/fruit.har', {
url: '*/**/api/v1/fruits',
update: true,
});

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

// Assert that the fruit is visible
await expect(page.getByText('Strawberry')).toBeVisible();
});

修改 HAR 文件

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

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

从 HAR 文件重放

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

test('gets the json from HAR and checks the new fruit has been added', async ({ page }) => {
// Replay API requests from HAR.
// Either use a matching response from the HAR,
// or abort the request if nothing matches.
await page.routeFromHAR('./hars/fruit.har', {
url: '*/**/api/v1/fruits',
update: false,
});

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

// Assert that the Playwright fruit is visible
await expect(page.getByText('Playwright', { exact: true })).toBeVisible();
});

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

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

模拟 WebSockets

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

await page.routeWebSocket('wss://example.com/ws', ws => {
ws.onMessage(message => {
if (message === 'request')
ws.send('response');
});
});

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

await page.routeWebSocket('wss://example.com/ws', ws => {
const server = ws.connectToServer();
ws.onMessage(message => {
if (message === 'request')
server.send('request2');
else
server.send(message);
});
});

更多详细信息,请参阅 WebSocketRoute