服务工作线程
简介
Service workers 仅在基于 Chromium 的浏览器中受支持。
如果您需要进行常规的网络模拟、路由和拦截,请先查看 网络指南。Playwright 针对此类用例提供了内置 API,无需使用下文所述的内容。不过,如果您对 Service Worker 本身发出的请求感兴趣,请阅读下文。
Service Workers 提供了一种浏览器原生的方法,用于处理页面通过原生 Fetch API (fetch) 以及其他网络请求资源(如脚本、CSS 和图像)发出的请求。
它们可以充当页面与外部网络之间的网络代理来执行缓存逻辑,或者如果 Service Worker 添加了 FetchEvent 监听器,还可以为用户提供离线体验。
许多使用 Service Worker 的网站只是将其作为一种透明的优化技术。虽然用户可能会感觉到更快的体验,但应用程序本身并不知道它们的存在。无论是否启用 Service Worker,应用程序运行的功能表现看起来都是一样的。
如何禁用 Service Workers
Playwright 允许在测试过程中禁用 Service Workers。这可以使测试更具可预测性且性能更高。但是,如果您的实际页面使用了 Service Worker,其行为可能会有所不同。
要禁用 Service Workers,请将 testOptions.serviceWorkers 设置为 'block'。
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
serviceWorkers: 'allow'
},
});
访问 Service Workers 并等待激活
您可以使用 browserContext.serviceWorkers() 列出 Service Worker,或者如果您预期某个页面会触发其 注册 (registration),则可以专门监视该 Service Worker。
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 激活有更地道的方法,但以下是一种与具体实现无关的方法。
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);
});
});
网络事件和路由
由 Service Worker 发出的任何网络请求都会通过 BrowserContext 对象报告。
- browserContext.on('request')、browserContext.on('requestfinished')、browserContext.on('response') 和 browserContext.on('requestfailed') 会被触发。
- browserContext.route() 可以查看到该请求。
- request.serviceWorker() 将被设置为对应的 Service Worker 实例,并且 request.frame() 将会抛出异常。
此外,对于 Page 发出的任何网络请求,如果该请求是由 Service Worker 的 fetch 处理程序处理的,则方法 response.fromServiceWorker() 将返回 true。
考虑一个简单的 Service Worker,它会获取页面发出的每一个请求:
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 注册了这个 Service Worker,然后获取 data.json,则会触发以下请求/响应事件(以及相应的网络生命周期事件):
| Event | Owner | URL | 已路由 | response.fromServiceWorker() |
|---|---|---|---|---|
| browserContext.on('request') | Frame | index.html | 是 | |
| page.on('request') | Frame | index.html | 是 | |
| browserContext.on('request') | Service Worker | transparent-service-worker.js | 是 | |
| browserContext.on('request') | Service Worker | data.json | 是 | |
| browserContext.on('request') | Frame | data.json | 是 | |
| page.on('request') | Frame | data.json | 是 |
由于示例 Service Worker 仅充当基本的透明“代理”:
- 对于
data.json有 2 个 browserContext.on('request') 事件;一个由 Frame 拥有,另一个由 Service Worker 拥有。 - 只有由 Service Worker 拥有的资源请求可以通过 browserContext.route() 进行路由;
data.json的 Frame 拥有的事件不可路由,因为在 Service Worker 注册了 fetch 处理程序的情况下,它们甚至不可能触及外部网络。
需要注意的是:如果对具有非空 request.serviceWorker() 的 Request/Response 调用 request.frame() 或 response.frame(),将会抛出异常。
仅路由 Service Worker 请求
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();
}
});
已知限制
目前无法路由对已更新的 Service Worker 主脚本代码的请求 (https://github.com/microsoft/playwright/issues/14711)。