🚦 Network Route Handling in Playwright
The Traffic Controller Story
Imagine you’re building a city with toy cars. Every car (web request) needs to go somewhere—maybe to a toy shop (API server) or a gas station (database).
What if you could be the TRAFFIC CONTROLLER?
You could:
- STOP certain cars from going anywhere 🛑
- REDIRECT cars to a different destination 🔄
- REPLACE what’s inside the car’s delivery truck 📦
- MODIFY the contents before they arrive ✏️
That’s exactly what Route Handling does in Playwright! You become the traffic controller for all network requests.
🎯 What is Network Interception?
Simple Idea: Catching requests BEFORE they reach their destination.
Think of it like a mail sorting center. Every letter (request) passes through you. You decide:
- Does this letter go out? ✉️
- Should I change what’s inside? 📝
- Should I send a fake reply instead? 🎭
// You're now the traffic controller!
await page.route('**/*', route => {
console.log('Caught a request!');
route.continue(); // Let it pass
});
Why is this AMAZING?
- Test your app without real servers
- Check how your app handles errors
- Speed up tests (no real network!)
- Control exactly what data your app sees
🔧 The page.route() Method
Your Personal Traffic Light
page.route() intercepts requests for ONE page only.
The Recipe:
await page.route(
'URL pattern', // Which cars to catch
handler // What to do with them
);
Example: Catch All Images
// Block all PNG images
await page.route('**/*.png', route => {
console.log('Blocked:', route.request().url());
route.abort(); // STOP! No images allowed
});
Example: Catch API Calls
// Intercept API requests
await page.route('**/api/users', route => {
console.log('API call intercepted!');
route.continue();
});
URL Pattern Magic ✨
| Pattern | Catches |
|---|---|
**/* |
Everything! |
**/api/* |
Any URL with /api/ |
**/*.css |
All CSS files |
https://example.com/** |
Only this domain |
graph TD A["Request Made"] --> B{page.route Check} B -->|Matches Pattern| C["Your Handler Runs"] B -->|No Match| D["Request Goes Normally"] C --> E["abort / continue / fulfill"]
🌐 The browserContext.route() Method
The SUPER Traffic Controller
What if you have multiple pages open? browserContext.route() controls traffic for ALL of them!
const context = await browser.newContext();
// This affects EVERY page in this context
await context.route('**/api/**', route => {
console.log('Caught from ANY page!');
route.continue();
});
const page1 = await context.newPage();
const page2 = await context.newPage();
// Both pages share the same traffic rules!
When to Use Which?
| Situation | Use This |
|---|---|
| Testing one page | page.route() |
| Multiple pages, same rules | context.route() |
| Login across tabs | context.route() |
| Page-specific mocking | page.route() |
Pro Tip: page.route() can OVERRIDE context.route() for specific pages!
🎭 Mocking API Responses
The Fake Delivery Truck
Sometimes you don’t want real data. You want to control EXACTLY what your app receives.
route.fulfill() = Send a fake response!
await page.route('**/api/users', route => {
// Send fake data!
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
])
});
});
Why Mock Responses?
- No server needed - Test without backend running
- Consistent data - Same test results every time
- Edge cases - Test errors, empty states, huge lists
- Speed - Instant responses, fast tests!
Mock Different Scenarios
// Mock an error response
await page.route('**/api/users', route => {
route.fulfill({
status: 500,
body: JSON.stringify({ error: 'Server down!' })
});
});
// Mock empty data
await page.route('**/api/products', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([]) // Empty array
});
});
graph TD A["App Requests /api/users"] --> B["Route Handler"] B --> C["route.fulfill"] C --> D["Fake Data Sent"] D --> E["App Shows Fake Users"]
✏️ Modifying Responses
The Editor in the Middle
What if you want the REAL response, but with some changes?
Story Time: Imagine the mail arrives, you open it, change one word, seal it back, and deliver it. That’s modifying responses!
await page.route('**/api/users', async route => {
// Get the REAL response first
const response = await route.fetch();
const json = await response.json();
// Modify it!
json.forEach(user => {
user.name = user.name.toUpperCase();
});
// Send modified version
route.fulfill({
response,
body: JSON.stringify(json)
});
});
Common Modifications
Add extra data:
await page.route('**/api/profile', async route => {
const response = await route.fetch();
const data = await response.json();
// Add test badge
data.badges = ['Tester', 'Premium'];
route.fulfill({
response,
body: JSON.stringify(data)
});
});
Change headers:
await page.route('**/*', async route => {
const response = await route.fetch();
route.fulfill({
response,
headers: {
...response.headers(),
'X-Test-Header': 'Modified!'
}
});
});
🛑 Blocking Requests
The Bouncer at the Door
Sometimes you want to COMPLETELY stop certain requests.
route.abort() = DENIED! Go away!
// Block all tracking scripts
await page.route('**/*analytics*', route => {
route.abort();
});
Common Blocking Patterns
Block images (faster tests):
await page.route('**/*.{png,jpg,jpeg,gif}', route => {
route.abort();
});
Block third-party resources:
await page.route(route => {
const url = route.request().url();
// Block anything NOT from our domain
if (!url.includes('myapp.com')) {
return route.abort();
}
route.continue();
});
Block specific file types:
await page.route('**/*', route => {
const url = route.request().url();
if (url.endsWith('.css') || url.endsWith('.woff')) {
route.abort();
} else {
route.continue();
}
});
The Three Commands
| Command | What It Does |
|---|---|
route.continue() |
Let request go normally |
route.fulfill() |
Send your own response |
route.abort() |
Block completely |
graph TD A["Request Intercepted"] --> B{Your Decision} B -->|continue| C["Request Goes to Server"] B -->|fulfill| D["Your Fake Response"] B -->|abort| E["Request Blocked"] C --> F["Real Response"] D --> G["App Gets Your Data"] E --> H["Request Failed"]
🎮 Putting It All Together
Complete Example
const { chromium } = require('playwright');
async function demo() {
const browser = await chromium.launch();
const context = await browser.newContext();
// Context-level: Block analytics everywhere
await context.route('**/*analytics*', route => {
route.abort();
});
const page = await context.newPage();
// Page-level: Mock API
await page.route('**/api/users', route => {
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'Test User' }
])
});
});
// Page-level: Modify responses
await page.route('**/api/config', async route => {
const response = await route.fetch();
const data = await response.json();
data.theme = 'dark'; // Force dark mode
route.fulfill({ response, body: JSON.stringify(data) });
});
await page.goto('https://myapp.com');
// Your app now sees mocked/modified data!
await browser.close();
}
🌟 Key Takeaways
page.route()- Traffic controller for one pagecontext.route()- Traffic controller for all pagesroute.continue()- Let it throughroute.fulfill()- Send fake responseroute.abort()- Block it completelyroute.fetch()- Get real response, then modify
You’re now the MASTER of network traffic! 🚀
Go ahead—mock that API, block those ads, modify those responses. Your tests are now faster, more reliable, and under YOUR control!
