模拟 API
简介
Web API 通常作为 HTTP 端点实现。Playwright 提供了 API 来**模拟**和**修改** HTTP 和 HTTPS 网络流量。页面发出的任何请求,包括 XHR 和 fetch 请求,都可以被跟踪、修改和模拟。使用 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 响应
有时,发出 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 被调用并且响应被修改了。
通过检查响应,我们可以看到我们的新水果已添加到列表中。
阅读更多关于高级网络的内容。
使用 HAR 文件进行模拟
HAR 文件是一种 HTTP Archive 文件,它包含页面加载时发出的所有网络请求记录。它包含请求和响应头、cookie、内容、时序等信息。您可以在测试中使用 HAR 文件来模拟网络请求。您需要:
- 录制一个 HAR 文件。
- 将 HAR 文件与测试一起提交。
- 在测试中使用保存的 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。
如果我们检查响应,可以看到我们的新水果已添加到 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.
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。