When to use: Intercepting, mocking, modifying, or blocking network requests during browser automation — API stubbing, simulating errors, testing offline behavior, removing tracking, or speeding up pages by blocking heavy assets. Prerequisites: core-commands.md for basic CLI usage
# Block all images
playwright-cli route "**/*.jpg" --status=404
# Mock API response with JSON body
playwright-cli route "**/api/users" --body='[{"id":1,"name":"Alice"}]' --content-type=application/json
# Add custom response headers
playwright-cli route "**/api/data" --body='{"ok":true}' --header="X-Custom: value"
# Strip request headers (e.g., remove auth for testing)
playwright-cli route "**/*" --remove-header=cookie,authorization
# List active routes
playwright-cli route-list
# Remove a specific route
playwright-cli unroute "**/*.jpg"
# Remove all routes
playwright-cli unrouteplaywright-cli uses glob patterns for URL matching:
| Pattern | Matches | Example URLs |
|---|---|---|
**/api/users |
Exact path on any origin | https://api.example.com/api/users |
**/api/*/details |
Wildcard in path segment | https://api.example.com/api/123/details |
**/*.{png,jpg,jpeg} |
Multiple file extensions | https://cdn.example.com/hero.png |
**/search?q=* |
Query parameters | https://example.com/search?q=test |
https://api.example.com/** |
All requests to a specific origin | https://api.example.com/v2/users |
**/* |
All requests | Everything |
# Return 404 for all image requests
playwright-cli route "**/*.jpg" --status=404
# Return 503 Service Unavailable
playwright-cli route "**/api/health" --status=503
# Return 401 Unauthorized
playwright-cli route "**/api/protected" --status=401# Simple JSON body
playwright-cli route "**/api/users" --body='[{"id":1,"name":"Alice"},{"id":2,"name":"Bob"}]' --content-type=application/json
# Mock a single resource
playwright-cli route "**/api/users/1" --body='{"id":1,"name":"Alice","email":"alice@example.com"}' --content-type=application/json
# Return empty array (no results scenario)
playwright-cli route "**/api/search*" --body='[]' --content-type=application/json# Set CORS headers for testing
playwright-cli route "**/api/**" --body='{"ok":true}' --header="Access-Control-Allow-Origin: *" --header="X-Request-Id: mock-123"
# Set caching headers
playwright-cli route "**/static/**" --header="Cache-Control: max-age=3600"Strip headers from outgoing requests — useful for testing unauthenticated access:
# Remove authentication headers
playwright-cli route "**/*" --remove-header=cookie,authorization
# Remove tracking headers
playwright-cli route "**/*" --remove-header=x-tracking-id# See what's currently being intercepted
playwright-cli route-list
# Remove a specific route
playwright-cli unroute "**/*.jpg"
# Clear all routes
playwright-cli unrouteFor conditional responses, request body inspection, response modification, or timed delays, use run-code to access the full Playwright route API.
playwright-cli run-code "async page => {
await page.route('**/api/login', route => {
const body = route.request().postDataJSON();
if (body.username === 'admin' && body.password === 'secret') {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ token: 'mock-jwt-token', user: { role: 'admin' } })
});
} else {
route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ error: 'Invalid credentials' })
});
}
});
}"playwright-cli run-code "async page => {
await page.route('**/api/users', route => {
const method = route.request().method();
switch (method) {
case 'GET':
route.fulfill({
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Alice' }])
});
break;
case 'POST':
route.fulfill({
status: 201,
contentType: 'application/json',
body: JSON.stringify({ id: 2, name: 'New User' })
});
break;
case 'DELETE':
route.fulfill({ status: 204 });
break;
default:
route.continue();
}
});
}"Let the real request go through, then modify the response before the page sees it:
playwright-cli run-code "async page => {
await page.route('**/api/user/profile', async route => {
const response = await route.fetch();
const json = await response.json();
// Override specific fields
json.isPremium = true;
json.subscription = 'enterprise';
await route.fulfill({ response, json });
});
}"playwright-cli run-code "async page => {
await page.route('**/api/**', async route => {
const response = await route.fetch();
const headers = { ...response.headers(), 'x-mock': 'true' };
await route.fulfill({ response, headers });
});
}"# Internet disconnected
playwright-cli run-code "async page => {
await page.route('**/api/offline', route => route.abort('internetdisconnected'));
}"
# Connection refused
playwright-cli run-code "async page => {
await page.route('**/api/down', route => route.abort('connectionrefused'));
}"
# Timeout
playwright-cli run-code "async page => {
await page.route('**/api/slow', route => route.abort('timedout'));
}"
# Connection reset
playwright-cli run-code "async page => {
await page.route('**/api/reset', route => route.abort('connectionreset'));
}"Available abort reasons: connectionrefused, timedout, connectionreset, internetdisconnected, blockedbyclient, failed
playwright-cli run-code "async page => {
await page.route('**/api/slow-endpoint', async route => {
await new Promise(resolve => setTimeout(resolve, 3000));
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({ data: 'finally loaded' })
});
});
}"playwright-cli run-code "async page => {
const fs = require('fs');
await page.route('**/api/config', route => {
const body = fs.readFileSync('./fixtures/mock-config.json', 'utf8');
route.fulfill({
contentType: 'application/json',
body
});
});
}"playwright-cli run-code "async page => {
let apiCallCount = 0;
await page.route('**/api/analytics', route => {
apiCallCount++;
route.continue();
});
// ... perform actions ...
// Later, check count:
return \`API was called \${apiCallCount} times\`;
}"# Block images, fonts, and stylesheets
playwright-cli route "**/*.{png,jpg,jpeg,gif,svg,webp}" --status=404
playwright-cli route "**/*.{woff,woff2,ttf,eot}" --status=404
playwright-cli route "**/*.css" --status=404# Block analytics and tracking
playwright-cli run-code "async page => {
await page.route('**/*', route => {
const url = route.request().url();
const blocked = [
'google-analytics.com',
'googletagmanager.com',
'facebook.net',
'hotjar.com',
'segment.io'
];
if (blocked.some(domain => url.includes(domain))) {
route.abort('blockedbyclient');
} else {
route.continue();
}
});
}"playwright-cli run-code "async page => {
await page.route('**/graphql', route => {
const { query } = route.request().postDataJSON();
if (query.includes('GetUser')) {
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({
data: { user: { id: '1', name: 'Alice', email: 'alice@example.com' } }
})
});
} else if (query.includes('ListProducts')) {
route.fulfill({
contentType: 'application/json',
body: JSON.stringify({
data: { products: [{ id: '1', name: 'Widget', price: 9.99 }] }
})
});
} else {
route.continue();
}
});
}"Record real network traffic and replay it later — perfect for deterministic testing:
# Record all network traffic to a HAR file
playwright-cli run-code "async page => {
await page.routeFromHAR('./recordings/api-traffic.har', {
update: true,
url: '**/api/**'
});
await page.goto('https://example.com');
// Interact with the page — all matching requests are recorded
}"
# Later, replay from the HAR file (no real network needed)
playwright-cli run-code "async page => {
await page.routeFromHAR('./recordings/api-traffic.har', {
url: '**/api/**'
});
await page.goto('https://example.com');
// API responses come from the HAR file
}"playwright-cli run-code "async page => {
const ws = await page.waitForEvent('websocket');
ws.on('framereceived', event => {
console.log('WS received:', event.payload);
});
ws.on('framesent', event => {
console.log('WS sent:', event.payload);
});
}"- Order matters: Routes are evaluated in the order they were added. More specific patterns should be added before catch-all patterns.
route.continue(): Lets the request proceed to the real server — use this in conditional routes for the "pass-through" case.route.fetch(): Makes the real request and returns the response, so you can inspect or modify it before fulfilling.- Performance: Blocking images and third-party scripts can dramatically speed up page loads during automation.
- Cleanup: Always
unroutewhen done, or routes persist for the entire session and may interfere with later operations.