服务工作进程
介绍
🌐 Introduction
服务工作者仅在基于 Chromium 的浏览器上受支持。
🌐 Service workers are only supported on Chromium-based browsers.
如果你想进行一般的网络模拟、路由和拦截,请先查看网络指南。Playwright 为此用例提供了内置 API,不需要以下信息。然而,如果你对 Service Worker 自身发出的请求感兴趣,请继续阅读以下内容。
Service Workers 提供了一种浏览器原生方法,用于处理页面通过原生 Fetch API (fetch) 发出的请求,以及其他网络请求的资源(如脚本、CSS 和图片)。
它们可以作为页面与外部网络之间的网络代理来执行缓存逻辑,或者如果 Service Worker 添加了 FetchEvent 监听器,则可以为用户提供离线体验。
🌐 They can act as a network proxy between the page and the external network to perform caching logic or can provide users with an offline experience if the Service Worker adds a FetchEvent listener.
许多使用 Service Workers 的网站只是将它们作为一种透明的优化技术。虽然用户可能会注意到体验更快,但应用的实现并不意识到它们的存在。无论是否启用 Service Workers,运行应用在功能上看起来是相同的。
🌐 Many sites that use Service Workers simply use them as a transparent optimization technique. While users might notice a faster experience, the app's implementation is unaware of their existence. Running the app with or without Service Workers enabled appears functionally equivalent.
如何禁用服务工作者
🌐 How to Disable Service Workers
Playwright 允许在测试期间禁用服务工作者。这使得测试更加可预测和高效。但是,如果你的实际页面使用了服务工作者,行为可能会有所不同。
🌐 Playwright allows to disable Service Workers during testing. This makes tests more predictable and performant. However, if your actual page uses a Service Worker, the behavior might be different.
要禁用服务工作者,请将 service_workers 上下文选项设置为 "block"。
🌐 To disable service workers, set service_workers context option to "block".
import pytest
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args):
return {
**browser_context_args,
"service_workers": "block"
}
访问 Service Worker 并等待激活
🌐 Accessing Service Workers and Waiting for Activation
你可以使用 browser_context.service_workers 来列出 Service Worker,或者如果你预期某个页面会触发其 注册,可以专门监视该 Service Worker:
🌐 You can use browser_context.service_workers to list the Service Workers, or specifically watch for the Service Worker if you anticipate a page will trigger its registration:
- Sync
- Async
with context.expect_event("serviceworker") as worker_info:
page.goto("/example-with-a-service-worker.html")
service_worker = worker_info.value
async with context.expect_event("serviceworker") as worker_info:
await page.goto("/example-with-a-service-worker.html")
service_worker = await worker_info.value
browser_context.on("serviceworker") 事件在 Service Worker 接管页面之前触发,因此在使用 worker.evaluate() 在 worker 中执行之前,你应该等待它的激活。
还有更惯用的等待 Service Worker 被激活的方法,但以下是与实现无关的方法:
🌐 There are more idiomatic methods of waiting for a Service Worker to be activated, but the following is an implementation agnostic method:
- Sync
- Async
page.evaluate("""async () => {
const registration = await window.navigator.serviceWorker.getRegistration();
if (registration.active?.state === 'activated')
return;
await new Promise(resolve => {
window.navigator.serviceWorker.addEventListener('controllerchange', resolve);
});
}""")
await page.evaluate("""async () => {
const registration = await window.navigator.serviceWorker.getRegistration();
if (registration.active?.state === 'activated')
return;
await new Promise(resolve => {
window.navigator.serviceWorker.addEventListener('controllerchange', resolve);
});
}""")
网络事件和路由
🌐 Network Events and Routing
Service Worker 提出的任何网络请求都通过 BrowserContext 对象报告:
🌐 Any network request made by the Service Worker is reported through the BrowserContext object:
- browser_context.on("request")、browser_context.on("requestfinished")、browser_context.on("response") 和 browser_context.on("requestfailed") 会被触发
- browser_context.route() 查看请求
- request.service_worker 将被设置为 Service Worker 实例,而 request.frame 将抛出
此外,对于 页面 发起的任何网络请求,当请求由 Service Worker 的 fetch 处理程序处理时,方法 response.from_service_worker 会返回 true。
🌐 Additionally, for any network request made by the Page, method response.from_service_worker return true when the request was handled a Service Worker's fetch handler.
考虑一个简单的服务工作进程,它会捕获页面发出的每个请求:
🌐 Consider a simple service worker that fetches every request made by the page:
如果 index.html 注册了这个服务工作者,然后再获取 data.json,将会触发以下请求/响应事件(以及相应的网络生命周期事件):
🌐 If index.html registers this service worker, and then fetches data.json, the following Request/Response events would be emitted (along with the corresponding network lifecycle events):
| Event | Owner | URL | Routed | response.from_service_worker |
|---|---|---|---|---|
| browser_context.on("request") | Frame | index.html | Yes | |
| page.on("request") | Frame | index.html | Yes | |
| browser_context.on("request") | Service Worker | transparent-service-worker.js | Yes | |
| browser_context.on("request") | Service Worker | data.json | Yes | |
| browser_context.on("request") | Frame | data.json | Yes | |
| page.on("request") | Frame | data.json | Yes |
由于示例 Service Worker 只是作为一个基本的透明“代理”
🌐 Since the example Service Worker just acts a basic transparent "proxy":
data.json有两个 browser_context.on("request") 事件:一个由 Frame 拥有,另一个由 Service Worker 拥有。- 只有由服务工作进程(Service Worker)拥有的资源请求可以通过 browser_context.route() 路由;由 Frame 拥有的
data.json事件无法路由,因为它们根本不可能访问外部网络,因为服务工作进程已经注册了 fetch 处理程序。
需要注意的是:如果在具有非空 request.service_worker 的 Request/Response 上调用 request.frame 或 response.frame,将会 抛出 异常。
🌐 It's important to note: calling request.frame or response.frame will throw an exception, if called on a Request/Response that has a non-null request.service_worker.
仅路由服务工作进程请求
🌐 Routing Service Worker Requests Only
- Sync
- Async
def handle_route(route: Route):
if route.request.service_worker:
# NB: accessing route.request.frame here would THROW
route.fulfill(content_type="text/plain", status=200, body="from sw")
else:
route.continue_()
context.route("**", handle_route)
async def handle_route(route: Route):
if route.request.service_worker:
# NB: accessing route.request.frame here would THROW
await route.fulfill(content_type="text/plain", status=200, body="from sw")
else:
await route.continue_()
await context.route("**", handle_route)
已知限制
🌐 Known Limitations
目前无法路由对更新的 Service Worker 主脚本代码的请求 (https://github.com/microsoft/playwright/issues/14711)。
🌐 Requests for updated Service Worker main script code currently cannot be routed (https://github.com/microsoft/playwright/issues/14711).