PRACTICAL GUIDE / playwright route not intercepting request
Debug Missing Playwright Route Events Behind Service Workers and Caches
Trace missing Playwright route events through page, service worker, and cache ownership, then choose deterministic interception or worker-aware assertions.
In this guide8 sections
- Confirm that the expected request actually exists
- Instrument the browser context before the page
- Choose whether the service worker belongs in the test
- Register deterministic routes before navigation
- Distinguish HTTP cache from worker Cache Storage
- Handle worker activation as a lifecycle
- Diagnose route precedence and pattern errors
- Make failures operationally complete
What you will learn
- Confirm that the expected request actually exists
- Instrument the browser context before the page
- Choose whether the service worker belongs in the test
- Register deterministic routes before navigation
When a Playwright route handler never runs, start by asking who owns the request. The page, a service worker, an HTTP cache, and a mock library can all produce UI data, but they do not expose the same routing path. A visible result is not evidence that the page sent the request your test expected to intercept.
Debug this as a network ownership problem. Establish whether a request occurred, identify its initiator and response source, then choose whether the test should bypass the worker or exercise it deliberately. Do not add a wait around a handler that can never see the traffic.
Confirm that the expected request actually exists
Reduce the failure to one navigation or user action and attach listeners before it. Verify the exact URL, method, query, and payload. A renamed endpoint, GraphQL transport, preloaded request, or client-side cache can make an old glob correct syntactically but irrelevant to current behavior.
The Playwright network guide recommends registering waitForResponse() before the triggering action. The same ordering applies to routes and listeners. If the application fetches during startup, a route installed after page.goto() is already too late.
Animated field map
Missing Route Event Investigation
Prove the request, identify its network owner, control the worker or cache boundary, intercept the intended path, and assert the response effect.
01 / unseen request
Unseen request
Reproduce one action and confirm URL, method, timing, and payload.
02 / ownership check
Network ownership
Separate frame, service worker, application cache, and no-request paths.
03 / cache boundary
Worker or cache boundary
Block the worker or observe its traffic according to test purpose.
04 / route control
Route interception
Register early, match narrowly, and validate the routed request.
05 / response proof
Response assertion
Prove the controlled payload reached the intended product state.
Instrument the browser context before the page
Page listeners are useful for frame-owned traffic, but a browser context can also report requests made by a service worker. Instrument both while diagnosing. Do not call request.frame() blindly: service-worker-owned requests have no frame and that accessor can throw. Check request.serviceWorker() first.
import { test, type Request, type TestInfo } from '@playwright/test';
function requestRecord(request: Request) {
const worker = request.serviceWorker();
return {
owner: worker ? 'service-worker' : 'frame',
method: request.method(),
resourceType: request.resourceType(),
url: request.url(),
};
}
test('records catalog request ownership', async ({ context, page }, testInfo) => {
const records: ReturnType<typeof requestRecord>[] = [];
context.on('request', (request) => records.push(requestRecord(request)));
await page.goto('/catalog');
await page.getByRole('button', { name: 'Refresh catalog' }).click();
await testInfo.attach('network-owners.json', {
body: Buffer.from(JSON.stringify(records, null, 2)),
contentType: 'application/json',
});
});This log answers a narrower question than a trace: which owner emitted which network request during this context. Filter secrets from URLs and headers before attaching diagnostics. Query strings often contain identifiers or tokens that should not leave the job.
Choose whether the service worker belongs in the test
There are two valid strategies with different coverage. For an API mocking test, block service workers and let Playwright routing be the only interception layer. For an offline, cache-first, push, or worker-update test, allow the service worker and use Chromium, because Playwright's worker inspection is limited to Chromium-based browsers.
Blocking produces deterministic route ownership but changes the deployed architecture. Allowing preserves that architecture but requires the test to reason about activation, controlling clients, worker-owned requests, and Cache Storage. Do not switch between these strategies implicitly based on whether a test happens to pass.
With workers allowed, use context-level events and check request.serviceWorker() before accessing frame data. A page response can report that it came through a service worker, while the worker's own outward fetch appears as a separate context request. This can produce two related observations for one apparent page load. Label them by owner instead of deduplicating solely by URL, or the diagnostic may discard the routable event and retain the non-routable one.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'mocked-api',
use: { serviceWorkers: 'block' },
testMatch: /mocked-api\.spec\.ts/,
},
{
name: 'service-worker-contract',
use: { browserName: 'chromium', serviceWorkers: 'allow' },
testMatch: /service-worker\.spec\.ts/,
},
],
});Keep ordinary cross-browser functional tests out of worker internals unless the product claim requires them. A worker-specific assertion in Firefox or WebKit would communicate coverage the runner cannot provide.
Register deterministic routes before navigation
For the blocked-worker project, put a context route in place before creating startup traffic. Context routing also covers popups and newly opened pages, while page routing is appropriate when the mock belongs to one page. Match the smallest stable endpoint boundary and validate the incoming request before fulfillment.
import { expect, test } from '@playwright/test';
test('renders the routed catalog response', async ({ context, page }) => {
let routeCalls = 0;
await context.route('**/api/catalog?region=eu', async (route) => {
routeCalls += 1;
expect(route.request().method()).toBe('GET');
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [{ id: 'sku-routed', name: 'Route Lamp' }] }),
});
});
await page.goto('/catalog?region=eu');
await expect(page.getByRole('listitem')).toContainText('Route Lamp');
expect(routeCalls).toBe(1);
});The counter catches a UI that displays matching data from another source. The distinctive fixture value proves the handler's payload reached rendering. Avoid a catch-all route that fulfills unrelated assets or authentication calls; broad interception makes failures look like application defects.
Distinguish HTTP cache from worker Cache Storage
Playwright disables the ordinary HTTP cache when routing is enabled. That does not erase Cache Storage managed by a service worker, nor does it reset an application-level memory cache. A fresh Playwright test context normally starts with isolated browser state, but a single test can still register a worker, populate its cache, and reuse it later in that context.
If the UI changes without network activity, inspect application state and worker behavior before inventing a cache-clearing hook. A test of deterministic routing should block the worker at context creation. A test of cache behavior should seed or warm the cache through a documented action, then assert the response source and offline result.
Clearing all browser data in beforeEach is a blunt workaround. It can hide cache migration bugs and make the test order-sensitive when cleanup fails. Express cold-cache and warm-cache scenarios as separate contexts with explicit setup.
Handle worker activation as a lifecycle
A registered service worker is not necessarily active or controlling the current page. When the worker itself is under test, wait for the registration lifecycle instead of sleeping after navigation. The worker event can fire before it takes control, so assert activation or a controller change before triggering the cached request.
import { expect, test } from '@playwright/test';
test('serves the saved shell while offline', async ({ context, page }) => {
const workerAppeared = context.waitForEvent('serviceworker');
await page.goto('/offline-demo');
await workerAppeared;
await page.evaluate(async () => {
const registration = await navigator.serviceWorker.ready;
if (!navigator.serviceWorker.controller) {
await new Promise<void>((resolve) =>
navigator.serviceWorker.addEventListener('controllerchange', () => resolve(), {
once: true,
}),
);
}
if (!registration.active) throw new Error('Service worker did not activate');
});
await context.setOffline(true);
await page.reload();
await expect(page.getByRole('heading', { name: 'Saved workspace' })).toBeVisible();
});This test exercises an offline product contract and should not share its context with page-routing tests. Restore online state automatically by closing the isolated context at test completion.
Diagnose route precedence and pattern errors
If no worker is involved, inspect registration order and pattern semantics. Page routes take precedence over context routes, and newer matching handlers can affect how fallback chains behave. A broad authentication fixture may consume the same URL before a test-specific handler sees it. Centralize route ownership or use route.fallback() deliberately when handlers are meant to compose.
Log the resolved request URL rather than reading the glob as prose. Query ordering, base URL resolution, redirects, and a switch from REST to GraphQL can invalidate the assumption. For redirects, remember that a route handler applies to the initial matching request according to API behavior; inspect the full response chain when the endpoint redirects.
Also prove that the UI made a request at all. Client data libraries can answer from memory, deduplicate a second call, or skip fetching when input is unchanged. In that path, changing Playwright routing cannot help. Reset the application through a supported boundary or make the scenario trigger a genuinely new query, then test cache reuse as a separate behavior rather than forcing transport activity.
Mock Service Worker deserves explicit ownership. It can intentionally intercept page traffic before Playwright's ordinary page route sees it. Do not stack an MSW handler and a Playwright mock for the same endpoint and infer precedence from the result. Assign one mock layer to the test, disable the other, and retain a worker-enabled contract test if production relies on that layer.
When routing still appears silent, compare the request initiator with the intended product action. Prefetch, preload, and background refresh may send the same URL before the click, while the click consumes already available data. Match method and payload, count calls, and use distinctive fixture content. A URL match alone cannot prove which lifecycle produced the response.
Make failures operationally complete
A missing handler should fail with evidence, not hang until the test timeout. Track whether the route ran, bound any response wait, and attach the ownership log on unexpected outcomes. In CI, retain traces and relevant application logs even when setup or routing fails before the final assertion.
Use this root-cause checklist:
- The listener and route are installed before navigation or the triggering action.
- The observed URL, method, payload, and redirect chain match the test assumption.
- Context events identify frame or service-worker ownership safely.
- The test explicitly chooses
serviceWorkers: 'block'or an allowed-worker project. - HTTP cache, worker Cache Storage, and application memory are treated separately.
- The route validates its request and the UI displays distinctive controlled data.
Finish by running the blocked-worker mock path and the worker-enabled product path independently. A route problem is solved when network ownership is explicit, the handler sees exactly the traffic it owns, and the final assertion proves whether the controlled or cached response reached the user.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Playwright documentation
Microsoft
Canonical API, locator, fixture, browser, and test-runner behavior.
- 02Playwright best practices
Microsoft
Official guidance for resilient tests, isolation, and user-facing locators.
- 03
FAQ / QUICK ANSWERS
Questions testers ask
Why does page.route miss a request that appears in browser developer tools?
A service worker may own the fetch and satisfy it from its cache, or the route may have been registered after the request began. Determine the request owner and install routing before navigation or action.
Should Playwright tests always block service workers for network mocking?
Block them when the test contract is deterministic page-level interception. Keep a separate Chromium path with workers allowed when offline behavior, worker caching, or update logic is part of the product risk.
What is the difference between page and browser context network events?
Page listeners describe page-owned traffic. Browser context listeners also expose requests made by service workers, allowing diagnostics to distinguish frame-owned and worker-owned network activity.
Can an HTTP cache prevent a registered Playwright route from running?
Enabling Playwright routing disables the HTTP cache, but a service worker Cache Storage response is a separate layer. Register the route early and inspect worker ownership before blaming ordinary browser caching.
How can I prove that a mocked response actually reached the UI?
Count matching route calls, validate method and payload in the handler, assert the response or worker source where relevant, and finish with a user-visible assertion based on distinctive fixture data.
RELATED GUIDES
Continue the learning route
GUIDE 01
Deterministic Network Tests with Playwright HAR Recording and Replay
Record, sanitize, and replay Playwright HAR fixtures for deterministic network tests, strict request matching, safer updates, and clear failures.
GUIDE 02
20 Playwright Network Mocking and Routing Interview Scenarios
Practice 20 senior Playwright network scenarios on routing, HAR replay, service workers, WebSockets, pass-through traffic, and mock failure analysis.
GUIDE 03
Share Authentication Cookies Between Playwright API and Browser Contexts
Share Playwright authentication cookies between API requests and browser contexts using the correct cookie jar, storageState handoff, and domain checks.
GUIDE 04
API Mocking with WireMock: Stubs, Tests, and Examples
API mocking with WireMock guide for QA teams covering stubs, request matching, delays, faults, recording, contracts, CI, and mistakes.