Q18 of 42 · Playwright

How do you intercept and mock network requests in Playwright?

PlaywrightMidplaywrightnetworkmockingpage-routemid

Short answer

Short answer: Use `page.route(pattern, handler)` to intercept matching requests. The handler can `fulfill` (mock the response), `continue` (pass through), or `abort`. Set up before navigation. `context.route` applies across all pages in the context.

Detail

page.route is the core API — same idea as Cypress's cy.intercept but with a function-handler shape:

await page.route('**/api/cart', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ items: [], total: 0 }),
  });
});

await page.goto('/cart');

Handler options:

  • route.fulfill({ ... }) — return a mock response.
  • route.continue({ ... }) — pass through, optionally modifying headers/post data/URL.
  • route.abort() — fail the request (test offline / error states).
  • route.fetch() — fetch the real response, modify it, then fulfill.

Pattern matching:

  • Glob: '**/api/cart' — Playwright's micromatch.
  • Regex: /\/api\/cart\?.*/.
  • Function: (url) => url.pathname === '/api/cart'.

Per-context vs per-page:

  • page.route — only that page.
  • context.route — all pages in the context.

Order of registration matters. Route handlers register in registration order; the first match handles the request. To unregister: await page.unroute(pattern).

Useful patterns:

// Modify a real response (fetch + fulfill)
await page.route('**/api/cart', async (route) => {
  const response = await route.fetch();
  const body = await response.json();
  body.total = 99.99;  // tweak for test
  await route.fulfill({ response, json: body });
});

// Simulate network error
await page.route('**/api/cart', (route) => route.abort('failed'));

// Spy without modifying
const requests: Request[] = [];
page.on('request', (req) => {
  if (req.url().includes('/api/cart')) requests.push(req);
});

The senior signal: knowing the four route actions, the order-of-registration rule, and the spy-via-event approach for non-modifying observation.

// EXAMPLE

intercept.spec.ts

import { test, expect } from '@playwright/test';

test('renders empty cart from mocked response', async ({ page }) => {
  await page.route('**/api/cart', (route) =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ items: [], total: 0 }),
    }),
  );

  await page.goto('/cart');
  await expect(page.getByTestId('empty-state')).toBeVisible();
});

test('shows error state when API fails', async ({ page }) => {
  await page.route('**/api/cart', (route) =>
    route.fulfill({ status: 500, body: 'Server error' }),
  );

  await page.goto('/cart');
  await expect(page.getByTestId('error')).toContainText('try again');
});

// WHAT INTERVIEWERS LOOK FOR

Naming all four route actions (fulfill, continue, abort, fetch+fulfill), pattern types (glob/regex/function), and the registration-order rule.

// COMMON PITFALL

Setting up `page.route` *after* `page.goto` and being surprised the first request slipped through.