Mock APIs
简介
Web API通常以HTTP端点形式实现。Playwright提供了API来模拟和修改网络流量,包括HTTP和HTTPS。页面发出的任何请求,包括XHRs和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的调用,并向数据中添加了一种名为'枇杷'的新水果。然后我们访问该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存档文件,记录了页面加载时发出的所有网络请求。它包含请求和响应头信息、cookies、内容、时间等数据。您可以在测试中使用HAR文件来模拟网络请求。您需要:
- 录制一个HAR文件。
- 将HAR文件与测试一起提交。
- 在测试中使用保存的HAR文件来路由请求。
录制HAR文件
要录制HAR文件,我们使用page.routeFromHAR()或browserContext.routeFromHAR()方法。该方法接收HAR文件路径和一个可选的选项对象。选项对象可以包含URL,这样只有URL匹配指定通配模式的请求才会从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通信过程,而不是连接到服务器。这个示例用"response"
来响应"request"
。
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。