Overview
Playwright’s network interception allows you to intercept, modify, mock, or block network requests. This is essential for testing edge cases, simulating API responses, and controlling network conditions.Basic Request Interception
Intercept and handle network requests using theroute API.
import { test, expect } from '@playwright/test';
test('intercept API request', async ({ page }) => {
// Intercept all API calls
await page.route('**/api/**', route => {
console.log('Request URL:', route.request().url());
route.continue();
});
await page.goto('https://example.com');
});
import { test } from '@playwright/test';
test('block images', async ({ page }) => {
// Block all image requests
await page.route('**/*.{png,jpg,jpeg,gif}', route => route.abort());
await page.goto('https://example.com');
// Page loads without images
});
import { test } from '@playwright/test';
test('match specific patterns', async ({ page }) => {
// Match using glob pattern
await page.route('**/api/users/*', route => route.continue());
// Match using RegExp
await page.route(/\.json$/, route => route.continue());
// Match using function
await page.route(url => url.pathname.startsWith('/api'), route => {
route.continue();
});
await page.goto('https://example.com');
});
Mock API Responses
Return mock data instead of making real API requests.import { test, expect } from '@playwright/test';
test('mock API response', async ({ page }) => {
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' },
]),
});
});
await page.goto('https://example.com');
// Verify mocked data appears
await expect(page.locator('text=John Doe')).toBeVisible();
});
import { test } from '@playwright/test';
test('mock using json option', async ({ page }) => {
await page.route('**/api/users', route => {
route.fulfill({
json: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
],
});
});
await page.goto('https://example.com');
});
import { test } from '@playwright/test';
import path from 'path';
test('serve mock data from file', async ({ page }) => {
await page.route('**/api/data', route => {
route.fulfill({
path: path.join(__dirname, 'mocks/data.json'),
});
});
await page.goto('https://example.com');
});
import { test } from '@playwright/test';
test('mock error response', async ({ page }) => {
await page.route('**/api/users', route => {
route.fulfill({
status: 404,
contentType: 'application/json',
body: JSON.stringify({ error: 'Not found' }),
});
});
await page.goto('https://example.com');
// Test error handling
});
test('mock server error', async ({ page }) => {
await page.route('**/api/users', route => {
route.fulfill({
status: 500,
body: 'Internal Server Error',
});
});
await page.goto('https://example.com');
});
Modify Requests
Change request properties before they are sent.import { test } from '@playwright/test';
test('add custom headers', async ({ page }) => {
await page.route('**/api/**', route => {
const headers = {
...route.request().headers(),
'X-Custom-Header': 'custom-value',
'Authorization': 'Bearer token123',
};
route.continue({ headers });
});
await page.goto('https://example.com');
});
import { test } from '@playwright/test';
test('modify POST data', async ({ page }) => {
await page.route('**/api/users', route => {
const postData = route.request().postDataJSON();
// Modify the data
const modifiedData = {
...postData,
timestamp: Date.now(),
};
route.continue({
postData: JSON.stringify(modifiedData),
});
});
await page.goto('https://example.com');
});
import { test } from '@playwright/test';
test('redirect to different endpoint', async ({ page }) => {
await page.route('**/api/v1/**', route => {
const url = route.request().url().replace('/v1/', '/v2/');
route.continue({ url });
});
await page.goto('https://example.com');
});
Modify Responses
Fetch the real response and modify it before returning.import { test } from '@playwright/test';
test('modify API response', async ({ page }) => {
await page.route('**/api/users', async route => {
// Fetch the original response
const response = await route.fetch();
const json = await response.json();
// Modify the data
const modified = json.map(user => ({
...user,
modified: true,
}));
// Return modified response
route.fulfill({
response,
json: modified,
});
});
await page.goto('https://example.com');
});
import { test } from '@playwright/test';
test('modify response headers', async ({ page }) => {
await page.route('**/api/**', async route => {
const response = await route.fetch();
route.fulfill({
response,
headers: {
...response.headers(),
'X-Modified': 'true',
'Cache-Control': 'no-cache',
},
});
});
await page.goto('https://example.com');
});
import { test } from '@playwright/test';
test('inject script into page', async ({ page }) => {
await page.route('**/index.html', async route => {
const response = await route.fetch();
let body = await response.text();
// Inject script before closing body tag
body = body.replace(
'</body>',
'<script>window.injected = true;</script></body>'
);
route.fulfill({
response,
body,
});
});
await page.goto('https://example.com');
const injected = await page.evaluate(() => window.injected);
expect(injected).toBe(true);
});
Request Information
Access detailed information about intercepted requests.Inspect Requests
import { test } from '@playwright/test';
test('log request details', async ({ page }) => {
await page.route('**/*', route => {
const request = route.request();
console.log('URL:', request.url());
console.log('Method:', request.method());
console.log('Headers:', request.headers());
console.log('Post Data:', request.postData());
console.log('Resource Type:', request.resourceType());
console.log('Is Navigation:', request.isNavigationRequest());
route.continue();
});
await page.goto('https://example.com');
});
Context-Level Interception
Intercept requests for all pages in a context.Context Routes
import { test } from '@playwright/test';
test('intercept at context level', async ({ context, page }) => {
// Apply to all pages in the context
await context.route('**/api/**', route => {
route.fulfill({
json: { mocked: true },
});
});
await page.goto('https://example.com');
// Open another page - routing still applies
const page2 = await context.newPage();
await page2.goto('https://example.com');
});
Conditional Interception
Apply different handling based on request properties.Conditional Routing
import { test } from '@playwright/test';
test('conditional interception', async ({ page }) => {
await page.route('**/api/**', route => {
const request = route.request();
// Different handling based on method
if (request.method() === 'POST') {
route.fulfill({ json: { created: true } });
} else if (request.method() === 'DELETE') {
route.fulfill({ status: 204 });
} else {
route.continue();
}
});
await page.goto('https://example.com');
});
test('intercept based on headers', async ({ page }) => {
await page.route('**/api/**', route => {
const request = route.request();
const headers = request.headers();
if (headers['authorization']) {
// Allow authenticated requests
route.continue();
} else {
// Block unauthorized requests
route.fulfill({ status: 401, body: 'Unauthorized' });
}
});
await page.goto('https://example.com');
});
Abort Requests
Block specific requests to test offline scenarios or reduce load times.import { test } from '@playwright/test';
test('simulate network failure', async ({ page }) => {
await page.route('**/api/users', route => {
route.abort('failed');
});
await page.goto('https://example.com');
// Test how app handles failed requests
});
import { test } from '@playwright/test';
test('different abort reasons', async ({ page }) => {
await page.route('**/slow-endpoint', route => route.abort('timedout'));
await page.route('**/blocked', route => route.abort('blockedbyclient'));
await page.route('**/failed', route => route.abort('failed'));
await page.goto('https://example.com');
});
HAR Recording and Replay
Record network traffic to HAR files and replay them later.import { test } from '@playwright/test';
test('record network traffic', async ({ context, page }) => {
// Start recording
await context.routeFromHAR('recordings/network.har', {
update: true,
});
await page.goto('https://example.com');
// All network traffic is recorded
});
import { test } from '@playwright/test';
test('replay from HAR', async ({ context, page }) => {
// Replay recorded traffic
await context.routeFromHAR('recordings/network.har', {
notFound: 'abort',
});
await page.goto('https://example.com');
// Uses recorded responses
});
import { test } from '@playwright/test';
test('update HAR file', async ({ context, page }) => {
await context.routeFromHAR('recordings/network.har', {
update: true,
url: '**/api/**', // Only record API calls
});
await page.goto('https://example.com');
});
Unroute
Remove route handlers when no longer needed.Remove Routes
import { test } from '@playwright/test';
test('remove route handler', async ({ page }) => {
const handler = route => route.fulfill({ json: { mocked: true } });
// Add route
await page.route('**/api/data', handler);
await page.goto('https://example.com');
// Uses mocked data
// Remove route
await page.unroute('**/api/data', handler);
await page.reload();
// Uses real API now
});
test('remove all routes', async ({ page }) => {
await page.route('**/api/**', route => route.continue());
// Remove all routes
await page.unrouteAll({ behavior: 'wait' });
});
Best Practices
Pattern Specificity
Use specific patterns to avoid unintended interceptions. Match exact endpoints when possible.
Error Handling
Always test both success and failure scenarios. Mock error responses to test error handling.
Performance
Use network mocking to speed up tests by avoiding slow external APIs.
Realistic Mocks
Keep mock data realistic and up-to-date with actual API responses.
Related Resources
Network Events
Monitor and analyze network activity
API Testing
Test APIs directly with APIRequestContext
