Mock APIs
简介
Web API通常以HTTP端点形式实现。Playwright提供了API来模拟和修改网络流量,包括HTTP和HTTPS。页面发出的任何请求,包括XHRs和fetch请求,都可以被跟踪、修改和模拟。使用Playwright,您还可以通过包含页面发出的多个网络请求的HAR文件进行模拟。
模拟API请求
以下代码将拦截所有对*/**/api/v1/fruits
的调用,并返回自定义响应。不会向API发出任何请求。测试访问使用模拟路由的URL,并断言页面上存在模拟数据。
- Sync
- 异步
def test_mock_the_fruit_api(page: Page):
def handle(route: Route):
json = [{"name": "Strawberry", "id": 21}]
# fulfill the route with the mock data
route.fulfill(json=json)
# Intercept the route to the fruit API
page.route("*/**/api/v1/fruits", handle)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Strawberry fruit is visible
expect(page.get_by_text("Strawberry")).to_be_visible()
async def test_mock_the_fruit_api(page: Page):
async def handle(route: Route):
json = [{"name": "Strawberry", "id": 21}]
# fulfill the route with the mock data
await route.fulfill(json=json)
# Intercept the route to the fruit API
await page.route("*/**/api/v1/fruits", handle)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Strawberry fruit is visible
await expect(page.get_by_text("Strawberry")).to_be_visible()
从示例测试的跟踪记录中可以看到,API从未被调用过,而是用模拟数据完成了请求。
了解更多关于高级网络的信息。
修改API响应
有时,必须发起一个API请求,但需要对响应进行修补以实现可重复的测试。在这种情况下,与其模拟请求,不如执行请求并使用修改后的响应来完成它。
在下面的示例中,我们拦截了对水果API的调用,并向数据中添加了一种名为'枇杷'的新水果。然后我们访问该URL并断言该数据存在:
- Sync
- 异步
def test_gets_the_json_from_api_and_adds_a_new_fruit(page: Page):
def handle(route: Route):
response = route.fetch()
json = response.json()
json.append({ "name": "Loquat", "id": 100})
# Fulfill using the original response, while patching the response body
# with the given JSON object.
route.fulfill(response=response, json=json)
page.route("https://demo.playwright.dev/api-mocking/api/v1/fruits", handle)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the new fruit is visible
expect(page.get_by_text("Loquat", exact=True)).to_be_visible()
async def test_gets_the_json_from_api_and_adds_a_new_fruit(page: Page):
async def handle(route: Route):
response = await route.fetch()
json = await response.json()
json.append({ "name": "Loquat", "id": 100})
# Fulfill using the original response, while patching the response body
# with the given JSON object.
await route.fulfill(response=response, json=json)
await page.route("https://demo.playwright.dev/api-mocking/api/v1/fruits", handle)
# Go to the page
await page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the new fruit is visible
await expect(page.get_by_text("Loquat", exact=True)).to_be_visible()
在我们的测试跟踪中,可以看到API被调用且响应已被修改。
通过检查响应,我们可以看到新的水果已添加到列表中。
了解更多关于高级网络的信息。
使用HAR文件进行模拟
HAR文件是一种HTTP存档文件,记录了页面加载时发出的所有网络请求。它包含请求和响应头信息、cookies、内容、时间等数据。您可以在测试中使用HAR文件来模拟网络请求。您需要:
- 录制一个HAR文件。
- 将HAR文件与测试一起提交。
- 在测试中使用保存的HAR文件来路由请求。
录制HAR文件
要录制HAR文件,我们使用page.route_from_har()或browser_context.route_from_har()方法。该方法接收HAR文件路径和一个可选的选项对象。选项对象可以包含URL,这样只有URL匹配指定通配模式的请求才会从HAR文件中获取响应。如果未指定,则所有请求都将从HAR文件中获取响应。
将update
选项设置为true将创建或更新HAR文件,其中包含实际的网络信息,而不是从HAR文件中提供请求。在创建测试时使用它,以便用真实数据填充HAR。
- Sync
- 异步
def test_records_or_updates_the_har_file(page: Page):
# Get the response from the HAR file
page.route_from_har("./hars/fruit.har", url="*/**/api/v1/fruits", update=True)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the fruit is visible
expect(page.get_by_text("Strawberry")).to_be_visible()
async def test_records_or_updates_the_har_file(page: Page):
# Get the response from the HAR file
await page.route_from_har("./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.get_by_text("Strawberry")).to_be_visible()
修改HAR文件
一旦您录制了一个HAR文件,您可以通过打开'hars'文件夹中的哈希.txt文件并编辑JSON来修改它。该文件应提交到您的源代码控制中。每当您使用update: true
运行此测试时,它将用API的请求更新您的HAR文件。
[
{
"name": "Playwright",
"id": 100
},
// ... other fruits
]
从HAR文件回放
现在您已经录制了HAR文件并修改了模拟数据,可以在测试中使用它来提供匹配的响应。为此,只需关闭或直接移除update
选项。这样测试将针对HAR文件运行,而不会实际调用API。
- Sync
- 异步
def test_gets_the_json_from_har_and_checks_the_new_fruit_has_been_added(page: Page):
# Replay API requests from HAR.
# Either use a matching response from the HAR,
# or abort the request if nothing matches.
page.route_from_har("./hars/fruit.har", url="*/**/api/v1/fruits", update=False)
# Go to the page
page.goto("https://demo.playwright.dev/api-mocking")
# Assert that the Playwright fruit is visible
expect(page.get_by_text("Playwright", exact=True)).to_be_visible()
async def test_gets_the_json_from_har_and_checks_the_new_fruit_has_been_added(page: Page):
# Replay API requests from HAR.
# Either use a matching response from the HAR,
# or abort the request if nothing matches.
await page.route_from_har("./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.get_by_text("Playwright", exact=True)).to_be_visible()
在我们的测试跟踪中,可以看到该路由是从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.
playwright open --save-har=example.har --save-har-glob="**/api/**" https://example.com
了解更多关于高级网络的信息。
模拟WebSockets
以下代码将拦截WebSocket连接并模拟整个WebSocket通信过程,而不是连接到服务器。这个示例用"response"
来响应"request"
。
- Sync
- 异步
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
ws.send("response")
page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
lambda message: message_handler(ws, message)
))
def message_handler(ws: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
ws.send("response")
await page.route_web_socket("wss://example.com/ws", lambda ws: ws.on_message(
lambda message: message_handler(ws, message)
))
或者,您可能希望连接到实际的服务器,但在消息传输过程中拦截它们并进行修改或阻止。以下是一个示例,它修改了页面发送到服务器的部分消息,而其余部分保持不变。
- Sync
- 异步
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
server.send("request2")
else:
server.send(message)
def handler(ws: WebSocketRoute):
server = ws.connect_to_server()
ws.on_message(lambda message: message_handler(server, message))
page.route_web_socket("wss://example.com/ws", handler)
def message_handler(server: WebSocketRoute, message: Union[str, bytes]):
if message == "request":
server.send("request2")
else:
server.send(message)
def handler(ws: WebSocketRoute):
server = ws.connect_to_server()
ws.on_message(lambda message: message_handler(server, message))
await page.route_web_socket("wss://example.com/ws", handler)
更多详情,请参阅WebSocketRoute。