🎯 What You Will Learn
Mock API responses, abort requests, inspect route order, and verify useful offline behavior without depending on live services.
- Fulfill API requests with deterministic test data
- Modify or abort selected requests
- Emulate the browser being offline
- Avoid service-worker and route-order surprises
Know Why You Are Mocking
Network mocking removes dependence on unstable or expensive services and lets a test reproduce errors on demand. It should preserve the contract your frontend actually consumes.
Contract drift: A mock can pass while the real API changes. Keep separate integration coverage or contract validation for important boundaries.
Observe the Real Request First
Use the CLI or browser dev tooling to identify the exact URL, method, request body, and response shape before writing a route.
"$PWCLI" -s=network open https://example.com --headed
"$PWCLI" -s=network snapshot
"$PWCLI" -s=network click eLOAD
"$PWCLI" -s=network requests
"$PWCLI" -s=network request 12Fulfill an API Request
Register the route before navigation or before the action that sends the request. Fulfill it with stable JSON.
import { test, expect } from '@playwright/test';
test('shows mocked products', async ({ page }) => {
await page.route('**/api/products', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Test Keyboard', price: 49 },
]),
});
});
await page.goto('https://example.com/products');
await expect(page.getByText('Test Keyboard')).toBeVisible();
});Modify a Real Response
Fetch the original response, change only the field required by the scenario, then fulfill with the modified JSON.
await page.route('**/api/account', async route => {
const response = await route.fetch();
const json = await response.json();
await route.fulfill({
response,
json: { ...json, plan: 'enterprise' },
});
});Abort Selected Resources
Aborting images, analytics, or a specific API can reproduce partial-loading behavior. Keep patterns narrow so unrelated requests are not hidden.
await page.route(/analytics\.example\.com/, route => route.abort());
await page.route('**/*.png', route => route.abort());
await page.goto('https://example.com');Test the Offline Experience
Offline emulation affects normal browser network traffic such as navigation, fetch, XMLHttpRequest, and WebSockets.
import { test, expect } from '@playwright/test';
test('shows an offline message', async ({ page, context }) => {
await page.goto('https://example.com');
await context.setOffline(true);
await page.getByRole('button', { name: 'Refresh data' }).click();
await expect(page.getByRole('alert')).toHaveText(/offline/i);
});Handle Route Order and Service Workers
Routes registered later can take priority. Service workers can also intercept requests before Playwright's native routing sees them.
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
serviceWorkers: 'block',
},
});Use only when needed: Block service workers when native route events are missing or the test specifically needs direct routing control.
Verify the UI Consumed the Mock
Assert the user-visible result and optionally wait for the intended request. A registered route that never matches should not make the test look successful.
const responsePromise = page.waitForResponse(
response => response.url().includes('/api/products')
);
await page.getByRole('button', { name: 'Load products' }).click();
await responsePromise;
await expect(page.getByText('Test Keyboard')).toBeVisible();Recap
- Observe the real request before mocking it.
- Register routes before the request is sent.
- Use deterministic responses and narrow URL patterns.
- Assert user-visible behavior so unused mocks do not create false confidence.
Checkpoint: You have completed the workflow and have a repeatable reference for your next Playwright project.