Overview
Playwright’s network interception capabilities allow you to mock API responses, simulate network failures, and test edge cases without depending on external services.
Basic Request Interception
Intercepting Requests
Use page.route() to intercept and handle network requests:
import { test , expect } from '@playwright/test' ;
test ( 'intercept API request' , async ({ page }) => {
// Intercept all requests to /api/user
await page . route ( '**/api/user' , async ( route ) => {
await route . fulfill ({
status: 200 ,
contentType: 'application/json' ,
body: JSON . stringify ({
id: 1 ,
name: 'John Doe' ,
email: '[email protected] ' ,
}),
});
});
await page . goto ( 'https://example.com/profile' );
await expect ( page . getByText ( 'John Doe' )). toBeVisible ();
});
Pattern Matching
Playwright supports multiple pattern types for route matching:
Glob Patterns
RegEx Patterns
Exact URL
await page . route ( '**/api/**' , route => route . fulfill ({ ... }));
await page . route ( '**/*.{png,jpg,jpeg}' , route => route . abort ());
Mocking API Responses
Mock with Static Data
import { test , expect } from '@playwright/test' ;
test ( 'display user list' , async ({ page }) => {
await page . route ( '**/api/users' , async ( route ) => {
await route . fulfill ({
status: 200 ,
body: JSON . stringify ([
{ id: 1 , name: 'Alice' },
{ id: 2 , name: 'Bob' },
{ id: 3 , name: 'Charlie' },
]),
});
});
await page . goto ( 'https://example.com/users' );
await expect ( page . getByText ( 'Alice' )). toBeVisible ();
await expect ( page . getByText ( 'Bob' )). toBeVisible ();
await expect ( page . getByText ( 'Charlie' )). toBeVisible ();
});
Mock from File
import { test , expect } from '@playwright/test' ;
import path from 'path' ;
test ( 'load data from fixture' , async ({ page }) => {
await page . route ( '**/api/products' , async ( route ) => {
await route . fulfill ({
path: path . join ( __dirname , 'fixtures/products.json' ),
});
});
await page . goto ( 'https://example.com/shop' );
await expect ( page . getByRole ( 'heading' , { name: 'Products' })). toBeVisible ();
});
Modifying Requests and Responses
await page . route ( '**/api/**' , async ( route ) => {
const headers = {
... route . request (). headers (),
'X-Custom-Header' : 'custom-value' ,
'Authorization' : 'Bearer mock-token' ,
};
await route . continue ({ headers });
});
Modify Response
await page . route ( '**/api/user' , async ( route ) => {
// Get original response
const response = await route . fetch ();
const json = await response . json ();
// Modify response data
json . isAdmin = true ;
json . permissions = [ 'read' , 'write' , 'delete' ];
// Fulfill with modified data
await route . fulfill ({
response ,
json ,
});
});
Simulating Network Conditions
Simulate Network Errors
import { test , expect } from '@playwright/test' ;
test ( 'handle network failure' , async ({ page }) => {
await page . route ( '**/api/data' , ( route ) => route . abort ( 'failed' ));
await page . goto ( 'https://example.com' );
await expect ( page . getByText ( 'Network error occurred' )). toBeVisible ();
});
Simulate Different Status Codes
Test 404 Not Found
await page . route ( '**/api/user/999' , ( route ) => {
route . fulfill ({
status: 404 ,
body: JSON . stringify ({ error: 'User not found' }),
});
});
Test 500 Server Error
await page . route ( '**/api/data' , ( route ) => {
route . fulfill ({
status: 500 ,
body: JSON . stringify ({ error: 'Internal server error' }),
});
});
Test 401 Unauthorized
await page . route ( '**/api/protected' , ( route ) => {
route . fulfill ({
status: 401 ,
body: JSON . stringify ({ error: 'Unauthorized' }),
});
});
Advanced Mocking Patterns
Mock Browser APIs
Mock browser APIs like Battery, Geolocation, or FileSystem using page.addInitScript():
const { test , expect } = require ( '@playwright/test' );
test . beforeEach ( async ({ page }) => {
await page . addInitScript (() => {
const mockBattery = {
level: 0.90 ,
charging: true ,
chargingTime: 1800 ,
dischargingTime: Infinity ,
addEventListener : () => { }
};
delete window . navigator . battery ;
window . navigator . getBattery = async () => mockBattery ;
});
});
test ( 'show battery status' , async ({ page }) => {
await page . goto ( '/' );
await expect ( page . locator ( '.battery-percentage' )). toHaveText ( '90%' );
await expect ( page . locator ( '.battery-status' )). toHaveText ( 'Adapter' );
});
Context-Wide Route Handlers
Apply routes at the context level for consistent mocking across all pages:
import { test , expect } from '@playwright/test' ;
test ( 'mock analytics across pages' , async ({ context , page }) => {
// Apply to all pages in context
await context . route ( '**/analytics/**' , ( route ) => route . abort ());
await page . goto ( 'https://example.com/page1' );
await page . goto ( 'https://example.com/page2' );
// Analytics blocked on both pages
});
Conditional Mocking
await page . route ( '**/api/data' , async ( route ) => {
const url = new URL ( route . request (). url ());
const id = url . searchParams . get ( 'id' );
if ( id === '1' ) {
await route . fulfill ({
status: 200 ,
body: JSON . stringify ({ id: 1 , name: 'Premium User' }),
});
} else {
// Let other requests pass through
await route . continue ();
}
});
Unrouting
Remove route handlers when they’re no longer needed:
const handler = ( route ) => route . fulfill ({ body: 'mocked' });
await page . route ( '**/api/data' , handler );
// ... perform some tests ...
// Remove specific handler
await page . unroute ( '**/api/data' , handler );
// Remove all handlers for pattern
await page . unroute ( '**/api/data' );
Real-World Example
Here’s a complete example from the Playwright repository that tests GitHub API:
import { test , expect } from '@playwright/test' ;
test . use ({
baseURL: 'https://api.github.com' ,
extraHTTPHeaders: {
'Accept' : 'application/vnd.github.v3+json' ,
'Authorization' : `token ${ process . env . API_TOKEN } ` ,
}
});
test ( 'create bug report' , async ({ request }) => {
const newIssue = await request . post (
`/repos/ ${ process . env . GITHUB_USER } /test-repo/issues` ,
{
data: {
title: '[Bug] report 1' ,
body: 'Bug description' ,
}
}
);
expect ( newIssue . ok ()). toBeTruthy ();
const issues = await request . get (
`/repos/ ${ process . env . GITHUB_USER } /test-repo/issues`
);
expect ( await issues . json ()). toContainEqual (
expect . objectContaining ({
title: '[Bug] report 1' ,
body: 'Bug description'
})
);
});
Best Practices
Use route.fetch() : When you need to modify responses, use route.fetch() to get the original response and then modify it, ensuring realistic behavior.
Mock at the right level : Mock at the context level for shared behavior, page level for specific tests
Keep mocks realistic : Ensure mocked responses match the actual API structure
Test error scenarios : Use mocking to test edge cases like network failures and error responses
Avoid over-mocking : Only mock what’s necessary; let real requests through when possible
Clean up routes : Unroute handlers when they’re no longer needed
Route handlers are called in reverse order of registration. The last registered handler is called first.
Troubleshooting
Routes not matching
Ensure your pattern matches the full URL:
// Log requests to debug pattern matching
await page . route ( '**/*' , ( route ) => {
console . log ( 'Request:' , route . request (). url ());
route . continue ();
});
CORS issues with mocked responses
Include proper CORS headers in mocked responses:
await route . fulfill ({
status: 200 ,
headers: {
'Access-Control-Allow-Origin' : '*' ,
'Access-Control-Allow-Methods' : 'GET, POST, PUT, DELETE' ,
},
body: JSON . stringify ({ data: 'value' }),
});