IKivan-kozenko -aqa
Try yourself as a QA tester
All topics·Advanced·19 / 24

Service Workers

How Playwright handles service workers, how to disable them for predictable tests, and how to inspect service-worker-owned network traffic.

When it matters

Service workers can cache assets, proxy fetch requests, and provide offline behavior. Most ordinary end-to-end tests should not need to test the worker directly, but PWA, offline, caching, and network-routing scenarios often do.

If you only need regular network mocking, start with Playwright routing APIs such as page.route() or browserContext.route() first.

Disable service workers

For many test suites, disabling service workers makes network behavior more predictable. Configure this in use when the app does not need the worker behavior for the scenario.

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

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

Wait for activation

Use the browser context to wait for the serviceworker event when a page registers a worker. Before evaluating inside the worker, wait until it controls the page.

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

await page.evaluate(async () => {
  const registration = await navigator.serviceWorker.getRegistration();
  if (registration?.active?.state === 'activated') return;

  await new Promise<void>((resolve) => {
    navigator.serviceWorker.addEventListener('controllerchange', () => resolve(), {
      once: true,
    });
  });
});

await serviceWorker.evaluate(() => self.location.href);

Network events

Requests made by a service worker are reported on browserContext. For service-worker-owned requests, request.serviceWorker() is set and request.frame() throws.

This lets you route only service-worker traffic and leave regular page traffic untouched.

ts
await context.route('**', async (route) => {
  const request = route.request();

  if (request.serviceWorker()) {
    await route.fulfill({
      contentType: 'text/plain',
      status: 200,
      body: 'from service worker route',
    });
    return;
  }

  await route.continue();
});