模拟 API
简介
Web API 通常实现为 HTTP 端点。Playwright 提供了模拟和修改网络流量(包括 HTTP 和 HTTPS)的 API。页面执行的任何请求,包括XHR 和fetch 请求,都可以被跟踪、修改和模拟。使用 Playwright,您还可以使用包含页面发出的多个网络请求的 HAR 文件进行模拟。
模拟 API 请求
以下代码将拦截对*/**/api/v1/fruits
的所有调用,并返回自定义响应。不会向 API 发出任何请求。测试转到使用模拟路由的 URL,并断言页面上存在模拟数据。
- 同步
- 异步
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 的调用,并将名为“Loquat”的新水果添加到数据中。然后我们转到 url 并断言此数据存在
- 同步
- 异步
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 档案文件,其中包含加载页面时发出的所有网络请求的记录。它包含有关请求和响应标头、Cookie、内容、时间等信息。您可以使用 HAR 文件来模拟测试中的网络请求。您需要
- 录制 HAR 文件。
- 将 HAR 文件与测试一起提交。
- 在测试中使用保存的 HAR 文件路由请求。
录制 HAR 文件
要录制 HAR 文件,我们使用page.route_from_har() 或browser_context.route_from_har() 方法。此方法接收 HAR 文件的路径和一个可选的选项对象。选项对象可以包含 URL,以便仅从 HAR 文件提供与指定通配符模式匹配的 URL 的请求。如果未指定,则所有请求都将从 HAR 文件提供。
将update
选项设置为 true 将使用实际网络信息创建或更新 HAR 文件,而不是从 HAR 文件提供请求。在创建测试以使用真实数据填充 HAR 时使用它。
- 同步
- 异步
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。
- 同步
- 异步
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"
。
- 同步
- 异步
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)
))
或者,您可能希望连接到实际服务器,但在两者之间拦截消息并修改或阻止它们。这是一个修改页面发送到服务器的一些消息并保留其余消息不变的示例。
- 同步
- 异步
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。