Перейти к основному содержимому

Service Workers

Введение

warning

Service Workers поддерживаются только в браузерах на базе Chromium.

примечание

Если вы ищете решение для общего мокинга сети, маршрутизации и перехвата запросов, сначала ознакомьтесь с Network Guide. Playwright предоставляет встроенные API для этого сценария, которые не требуют информации ниже. Однако, если вас интересуют запросы, выполняемые самими Service Workers, читайте дальше.

Service Workers предоставляют нативный для браузера способ обработки запросов, выполняемых страницей с помощью нативного Fetch API (fetch), а также других сетевых ресурсов (таких как скрипты, CSS и изображения).

Они могут выступать в роли сетевого прокси между страницей и внешней сетью для реализации логики кэширования или предоставлять пользователям офлайн‑режим, если Service Worker добавляет обработчик события FetchEvent.

Многие сайты, использующие Service Workers, применяют их лишь как прозрачную оптимизацию. Пользователи могут заметить более высокую скорость работы, однако сама реализация приложения «не знает» об их существовании. Запуск приложения с включёнными или отключёнными Service Workers выглядит функционально одинаково.

Как отключить 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 Worker’ов, или целенаправленно ожидать 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() необходимо дождаться его активации.

Существуют более идиоматичные способы ожидания активации 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() возвращает true, если запрос был обработан fetch‑обработчиком Service Worker.

Рассмотрим простой service worker, который перехватывает и проксирует каждый запрос, сделанный страницей:

transparent-service-worker.js
self.addEventListener('fetch', event => {
// фактически выполняем запрос
const responsePromise = fetch(event.request);
// отправляем его обратно на страницу
event.respondWith(responsePromise);
});

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

Если index.html регистрирует этот service worker, а затем запрашивает data.json, будут сгенерированы следующие события Request/Response (вместе с соответствующими событиями жизненного цикла сети):

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 выступает лишь в роли простого прозрачного «прокси»:

  • Для data.json существует 2 события browserContext.on('request'): одно принадлежит Frame, другое — Service Worker.
  • Только запрос, принадлежащий Service Worker, может быть маршрутизирован через browserContext.route(); события, принадлежащие Frame, для data.json не маршрутизируются, так как они даже не имели возможности попасть во внешнюю сеть — у Service Worker зарегистрирован обработчик fetch.
предупреждение

Важно отметить: вызов request.frame() или response.frame() выбросит исключение, если он выполнен для Request/Response, у которого request.serviceWorker() не равен null.

Маршрутизация только запросов Service Worker

await context.route('**', async route => {
if (route.request().serviceWorker()) {
// NB: вызов route.request().frame() здесь ВЫБРОСИТ исключение
await route.fulfill({
contentType: 'text/plain',
status: 200,
body: 'from sw',
});
} else {
await route.continue();
}
});

Известные ограничения

Запросы на обновление основного скрипта Service Worker в настоящее время не могут быть маршрутизированы (https://github.com/microsoft/playwright/issues/14711).