網路
簡介
Playwright 提供 API 來監控和修改瀏覽器網路流量,包括 HTTP 和 HTTPS。頁面所做的任何請求,包括 XHRs 和 fetch 請求,都可以被追蹤、修改和處理。
模擬 API
查看我們的 API mocking 指南 以了解更多資訊。
- 模擬 API 請求並且不會觸發 API
- 執行 API 請求並修改回應
- 使用 HAR 檔案來模擬網路請求。
HTTP 認證
執行 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) 代理的使用者名稱和密碼,您也可以指定要繞過 proxy 的主機。
這裡是一個全域代理的範例:
- 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"
})
也可以根據上下文指定它:
- Sync
- Async
browser = chromium.launch()
context = browser.new_context(proxy={"server": "http://myproxy.com:3128"})
browser = await chromium.launch()
context = await browser.new_context(proxy={"server": "http://myproxy.com:3128"})
網路事件
你可以監控所有的 Requests 和 Responses:
- Sync
- Async
from playwright.sync_api import sync_playwright, Playwright
def run(playwright: 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, Playwright
async def run(playwright: 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():
- 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
變體
使用 page.expect_response() 等待 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
處理請求
- 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 端點。
變體
在整個瀏覽器情境上使用 browser_context.route() 或頁面上使用 page.route() 設定路由。這將適用於彈出視窗和開啟的連結。
- 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")
修改請求
- 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"]
await route.continue_(headers=headers)
await page.route("**/*", handle_route)
# Continue requests as POST.
await page.route("**/*", lambda route: route.continue_(method="POST"))
你可以繼續請求並進行修改。上面的範例從傳出的請求中移除了 HTTP 標頭。
中止請求
你可以使用 page.route() 和 route.abort() 來中止請求。
- 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_())
修改回應
要修改回應,使用 APIRequestContext 取得原始回應,然後將回應傳遞給 route.fulfill()。你可以通過選項覆蓋回應的個別欄位:
- Sync
- Async
def handle_route(route: Route) -> None:
# Fetch original response.
response = route.fetch()
# 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 route.fetch()
# 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)
Glob URL 模式
Playwright 在網路攔截方法(如 page.route() 或 page.expect_response())中使用簡化的 glob 模式進行 URL 比對。這些模式支援基本的萬用字元:
- 星號:
- 單個
*
匹配除/
以外的任何字元 - 雙重
**
匹配包括/
在內的任何字元
- 單個
- 問號
?
只匹配問號?
。如果你想匹配任何字元,請改用*
。 - 大括號
{}
可用來匹配以逗號,
分隔的選項清單 - 反斜線
\
可用來跳脫任何特殊字元(注意將反斜線本身跳脫為\\
)
範例:
https://example.com/*.js
匹配https://example.com/file.js
但不匹配https://example.com/path/file.js
https://example.com/?page=1
匹配https://example.com/?page=1
但不匹配https://example.com
**/*.js
同時匹配https://example.com/file.js
和https://example.com/path/file.js
**/*.{png,jpg,jpeg}
匹配所有圖片請求
重要注意事項:
- glob 模式必須匹配整個 URL,而不僅是其中一部分。
- 使用 glob 進行 URL 匹配時,請考慮完整的 URL 結構,包括協定和路徑分隔符。
- 對於更複雜的匹配需求,請考慮使用 [RegExp] 而不是 glob 模式。
WebSockets
Playwright 支援 WebSockets 檢查、模擬和修改的開箱即用功能。查看我們的 API mocking 指南 以了解如何模擬 WebSockets。
每次建立 WebSocket 時,會觸發 page.on("websocket") 事件。此事件包含 WebSocket 實例以進一步檢查 web socket 幀:
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)
缺少網路事件和服務工作者
Playwright 的內建 browser_context.route() 和 page.route() 允許你的測試原生地路由請求並執行模擬和攔截。
- 如果您使用 Playwright 的原生 browser_context.route() 和 page.route(),並且發現缺少網路事件,請將 service_workers 設定為
'block'
來停用 Service Workers。 - 您可能正在使用諸如 Mock Service Worker (MSW) 之類的模擬工具。雖然此工具可以直接用於模擬回應,但它會新增自己的 Service Worker 來接管網路請求,因此使 browser_context.route() 和 page.route() 無法看到這些請求。如果您對網路測試和模擬都感興趣,請考慮使用內建的 browser_context.route() 和 page.route() 進行回應模擬。
- 如果你不僅僅對使用 Service Workers 進行測試和網路模擬感興趣,還對路由和監聽由 Service Workers 自己發出的請求感興趣,請參閱這個實驗性功能。