Skip to main content

服务工作进程

介绍

🌐 Introduction

warning

服务工作者仅在基于 Chromium 的浏览器上受支持。

🌐 Service workers are only supported on Chromium-based browsers.

note

如果你想进行一般的网络模拟、路由和拦截,请先查看网络指南。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.

要禁用服务工作者,请将 testOptions.serviceWorkers 设置为 'block'

🌐 To disable service workers, set testOptions.serviceWorkers to 'block'.

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
use: {
serviceWorkers: 'allow'
},
});

访问 Service Worker 并等待激活

🌐 Accessing Service Workers and Waiting for Activation

你可以使用 browserContext.serviceWorkers() 来列出服务 Worker,或者如果你预计某个页面会触发其 注册,可以专门监听该服务 Worker

🌐 You can use browserContext.serviceWorkers() to list the Service Workers, or specifically watch for the Service Worker if you anticipate a page will trigger its registration:

const serviceWorkerPromise = context.waitForEvent('serviceworker');
await page.goto('/example-with-a-service-worker.html');
const serviceworker = await serviceWorkerPromise;

browserContext.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:

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:

此外,对于 Page 发出的任何网络请求,当请求由 Service Worker 的 fetch 处理程序处理时,方法 response.fromServiceWorker() 会返回 true

🌐 Additionally, for any network request made by the Page, method response.fromServiceWorker() 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:

transparent-service-worker.js
self.addEventListener('fetch', event => {
// actually make the request
const responsePromise = fetch(event.request);
// send it back to the page
event.respondWith(responsePromise);
});

self.addEventListener('activate', event => {
event.waitUntil(clients.claim());
});

如果 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):

EventOwnerURLRoutedresponse.fromServiceWorker()
browserContext.on('request')Frameindex.htmlYes
page.on('request')Frameindex.htmlYes
browserContext.on('request')Service Workertransparent-service-worker.jsYes
browserContext.on('request')Service Workerdata.jsonYes
browserContext.on('request')Framedata.jsonYes
page.on('request')Framedata.jsonYes

由于示例 Service Worker 只是作为一个基本的透明“代理”

🌐 Since the example Service Worker just acts a basic transparent "proxy":

  • data.json 有两个 browserContext.on('request') 事件;一个由 Frame 拥有,另一个由 Service Worker 拥有。
  • 只有由服务工作进程(Service Worker)拥有的资源请求可以通过 browserContext.route() 路由;由 Frame 拥有的 data.json 事件无法路由,因为它们根本没有机会访问外部网络,因为服务工作进程已经注册了 fetch 处理器。
caution

需要注意的是:如果在具有非空 request.serviceWorker()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.serviceWorker().

仅路由服务工作进程请求

🌐 Routing Service Worker Requests Only

await context.route('**', async route => {
if (route.request().serviceWorker()) {
// NB: calling route.request().frame() here would THROW
await route.fulfill({
contentType: 'text/plain',
status: 200,
body: 'from sw',
});
} else {
await route.continue();
}
});

已知限制

🌐 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).