跳转到主要内容

服务工作线程

简介

警告

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'

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

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

访问 Service Workers 并等待激活

您可以使用 browserContext.serviceWorkers() 列出 Service Workers,或者如果您预期页面将触发其 注册,则专门监视 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 对象报告

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

考虑一个获取页面发出的每个请求的简单 service worker

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 注册了这个 service worker,然后获取 data.json,将发出以下请求/响应事件(以及相应的网络生命周期事件)

EventOwnerURL路由response.fromServiceWorker()
browserContext.on('request')Frameindex.html
page.on('request')Frameindex.html
browserContext.on('request')Service Workertransparent-service-worker.js
browserContext.on('request')Service Workerdata.json
browserContext.on('request')Framedata.json
page.on('request')Framedata.json

由于示例 Service Worker 仅充当基本的透明“代理”

  • 对于 data.json,有两个 browserContext.on('request') 事件;一个由 Frame 拥有的,另一个由 Service Worker 拥有的。
  • 只有 Service Worker 拥有的资源请求可以通过 browserContext.route() 路由;Frame 拥有的 data.json 事件不可路由,因为自 Service Worker 注册了 fetch 处理程序以来,它们根本没有机会命中外部网络。
注意

重要的是要注意:如果在非 null 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)。