网络 (Network)
Playwright 提供 API 来 监控 和 修改 网络流量,包括 HTTP 和 HTTPS。页面发出的任何请求,包括 XHR 和 fetch 请求,都可以被跟踪、修改和处理。
HTTP Authentication
使用 browser.new_context(**kwargs) 执行 HTTP 认证。
- Sync
- Async
context = browser.new_context(
http_credentials={"username": "bill", "password": "pa55w0rd"}
)
page = context.new_page()
page.goto("https://example.com")
context = await browser.new_context(
http_credentials={"username": "bill", "password": "pa55w0rd"}
)
page = await context.new_page()
await page.goto("https://example.com")
HTTP Proxy
您可以配置页面通过 HTTP(S) 代理或 SOCKSv5 加载。代理可以为整个浏览器全局设置,也可以为每个浏览器上下文单独设置。
您可以选择为 HTTP(S) 代理指定用户名和密码,也可以指定绕过代理的主机。
这是一个全局代理的示例:
- Sync
- Async
browser = chromium.launch(proxy={
"server": "http://myproxy.com:3128",
"username": "usr",
"password": "pwd"
})
browser = await chromium.launch(proxy={
"server": "http://myproxy.com:3128",
"username": "usr",
"password": "pwd"
})
当单独为每个上下文指定代理时,Windows 上的 Chromium 需要提示将设置代理。这是通过向浏览器本身传递一个非空代理服务器来完成的。这是一个特定于上下文的代理示例:
- Sync
- Async
# Browser proxy option is required for Chromium on Windows.
browser = chromium.launch(proxy={"server": "per-context"})
context = browser.new_context(proxy={"server": "http://myproxy.com:3128"})
# Browser proxy option is required for Chromium on Windows.
browser = await chromium.launch(proxy={"server": "per-context"})
context = await browser.new_context(proxy={"server": "http://myproxy.com:3128"})
Network events
- Sync
- Async
from playwright.sync_api import sync_playwright
def run(playwright):
chromium = playwright.chromium
browser = chromium.launch()
page = browser.new_page()
# Subscribe to "request" and "response" events.
page.on("request", lambda request: print(">>", request.method, request.url))
page.on("response", lambda response: print("<<", response.status, response.url))
page.goto("https://example.com")
browser.close()
with sync_playwright() as playwright:
run(playwright)
import asyncio
from playwright.async_api import async_playwright
async def run(playwright):
chromium = playwright.chromium
browser = await chromium.launch()
page = await browser.new_page()
# Subscribe to "request" and "response" events.
page.on("request", lambda request: print(">>", request.method, request.url))
page.on("response", lambda response: print("<<", response.status, response.url))
await page.goto("https://example.com")
await browser.close()
async def main():
async with async_playwright() as playwright:
await run(playwright)
asyncio.run(main())
或者在点击按钮后使用 page.expect_response(url_or_predicate, **kwargs) 等待网络响应:
- Sync
- Async
# Use a glob url pattern
with page.expect_response("**/api/fetch_data") as response_info:
page.get_by_text("Update").click()
response = response_info.value
# Use a glob url pattern
async with page.expect_response("**/api/fetch_data") as response_info:
await page.get_by_text("Update").click()
response = await response_info.value
Variations
使用 page.expect_response(url_or_predicate, **kwargs) 等待 Response
- Sync
- Async
# Use a regular expression
with page.expect_response(re.compile(r"\.jpeg$")) as response_info:
page.get_by_text("Update").click()
response = response_info.value
# Use a predicate taking a response object
with page.expect_response(lambda response: token in response.url) as response_info:
page.get_by_text("Update").click()
response = response_info.value
# Use a regular expression
async with page.expect_response(re.compile(r"\.jpeg$")) as response_info:
await page.get_by_text("Update").click()
response = await response_info.value
# Use a predicate taking a response object
async with page.expect_response(lambda response: token in response.url) as response_info:
await page.get_by_text("Update").click()
response = await response_info.value
Handle requests
- Sync
- Async
page.route(
"**/api/fetch_data",
lambda route: route.fulfill(status=200, body=test_data))
page.goto("https://example.com")
await page.route(
"**/api/fetch_data",
lambda route: route.fulfill(status=200, body=test_data))
await page.goto("https://example.com")
您可以通过在 Playwright 脚本中处理网络请求来模拟 API 端点。
Variations
使用 browser_context.route(url, handler, **kwargs) 在整个浏览器上下文或使用 page.route(url, handler, **kwargs) 在页面上设置路由。它将应用于弹出窗口和打开的链接。
- Sync
- Async
context.route(
"**/api/login",
lambda route: route.fulfill(status=200, body="accept"))
page.goto("https://example.com")
await context.route(
"**/api/login",
lambda route: route.fulfill(status=200, body="accept"))
await page.goto("https://example.com")
Modify requests
- Sync
- Async
# Delete header
def handle_route(route):
headers = route.request.headers
del headers["x-secret"]
route.continue_(headers=headers)
page.route("**/*", handle_route)
# Continue requests as POST.
page.route("**/*", lambda route: route.continue_(method="POST"))
# Delete header
async def handle_route(route):
headers = route.request.headers
del headers["x-secret"]
route.continue_(headers=headers)
await page.route("**/*", handle_route)
# Continue requests as POST.
await page.route("**/*", lambda route: route.continue_(method="POST"))
您可以继续带有修改的请求。上面的示例从传出请求中删除了 HTTP 头。
Abort requests
您可以使用 page.route(url, handler, **kwargs) 和 route.abort(**kwargs) 中止请求。
- Sync
- Async
page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
# Abort based on the request type
page.route("**/*", lambda route: route.abort() if route.request.resource_type == "image" else route.continue_())
await page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())
# Abort based on the request type
await page.route("**/*", lambda route: route.abort() if route.request.resource_type == "image" else route.continue_())
Modify responses
要修改响应,请使用 APIRequestContext 获取原始响应,然后将响应传递给 route.fulfill(**kwargs)。您可以通过选项覆盖响应上的各个字段:
- Sync
- Async
def handle_route(route: Route) -> None:
# Fetch original response.
response = page.request.fetch(route.request)
# Add a prefix to the title.
body = response.text()
body = body.replace("<title>", "<title>My prefix:")
route.fulfill(
# Pass all fields from the response.
response=response,
# Override response body.
body=body,
# Force content type to be html.
headers={**response.headers, "content-type": "text/html"},
)
page.route("**/title.html", handle_route)
async def handle_route(route: Route) -> None:
# Fetch original response.
response = await page.request.fetch(route.request)
# Add a prefix to the title.
body = await response.text()
body = body.replace("<title>", "<title>My prefix:")
await route.fulfill(
# Pass all fields from the response.
response=response,
# Override response body.
body=body,
# Force content type to be html.
headers={**response.headers, "content-type": "text/html"},
)
await page.route("**/title.html", handle_route)
Record and replay requests
您可以将网络活动录制为 HTTP 归档文件 (HAR)。稍后,此归档可用于模拟对网络请求的响应。您需要:
- 录制 HAR 文件。
- 将 HAR 文件与测试一起提交。
- 在测试中使用保存的 HAR 文件路由请求。
Recording HAR with CLI
使用 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.coms
Recording HAR with a script
或者,您可以以编程方式录制 HAR,而不是使用 CLI。使用 browser.new_context(**kwargs) 创建 BrowserContext 时传递 har 选项以创建归档。如果 har 文件名以 .zip 结尾,则工件将作为单独的文件写入,并全部压缩为单个 zip。
- Sync
- Async
context = browser.new_context(record_har_path="example.har", record_har_url_filter="**/api/**")
# ... Perform actions ...
# Close context to ensure HAR is saved to disk.
context.close()
context = await browser.new_context(record_har_path="example.har", record_har_url_filter="**/api/**")
# ... Perform actions ...
# Close context to ensure HAR is saved to disk.
await context.close()
Replaying from HAR
使用 page.route_from_har(har, **kwargs) 或 browser_context.route_from_har(har, **kwargs) 从 HAR 文件提供匹配的响应。
- Sync
- Async
# Either use a matching response from the HAR,
# or abort the request if nothing matches.
page.route_from_har("example.har")
# Either use a matching response from the HAR,
# or abort the request if nothing matches.
await page.route_from_har("example.har")
HAR 回放严格匹配 URL 和 HTTP 方法。对于 POST 请求,它还严格匹配 POST 负载。如果多个录制匹配一个请求,则选择具有最多匹配头的录制。导致重定向的条目将被自动跟随。
与录制时类似,如果给定的 HAR 文件名以 .zip 结尾,则将其视为包含 HAR 文件以及存储为单独条目的网络负载的归档。您还可以提取此归档,手动编辑负载或 HAR 日志,并指向提取的 har 文件。所有负载都将相对于文件系统上提取的 har 文件进行解析。
WebSockets
Playwright 开箱即用地支持 WebSockets 检查。每次创建 WebSocket 时,都会触发 page.on("websocket") 事件。此事件包含用于进一步检查 Web Socket 帧的 WebSocket 实例:
def on_web_socket(ws):
print(f"WebSocket opened: {ws.url}")
ws.on("framesent", lambda payload: print(payload))
ws.on("framereceived", lambda payload: print(payload))
ws.on("close", lambda payload: print("WebSocket closed"))
page.on("websocket", on_web_socket)
Missing Network Events and Service Workers
Playwright 内置的 browser_context.route(url, handler, **kwargs) 和 page.route(url, handler, **kwargs) 允许您的测试原生路由请求并执行模拟和拦截。
- 如果您正在使用 Playwright 的原生 browser_context.route(url, handler, **kwargs) 和 page.route(url, handler, **kwargs),并且似乎缺少网络事件,请通过将
browser.new_context.service_workers设置为'block'来禁用 Service Workers。 - 可能是您正在使用模拟工具,例如 Mock Service Worker (MSW)。虽然此工具开箱即用地用于模拟响应,但它添加了自己的 Service Worker 来接管网络请求,从而使它们对 browser_context.route(url, handler, **kwargs) 和 page.route(url, handler, **kwargs) 不可见。如果您对网络测试和模拟都感兴趣,请考虑使用内置的 browser_context.route(url, handler, **kwargs) 和 page.route(url, handler, **kwargs) 进行 响应模拟。
- 如果您不仅对使用 Service Workers 进行测试和网络模拟感兴趣,而且对路由和监听 Service Workers 本身发出的请求感兴趣,请参阅 此实验性功能。