How to Intercept and Modify Network Requests in Lightpanda Browser
Purpose
I needed to intercept network requests in Lightpanda to mock API responses for testing. When I tried to find documentation on how to do this, I couldn’t find any clear examples. This post shows exactly how I got request interception working using the CDP Fetch domain.
What I Was Trying to Do
I was building a test suite for a web application and needed to:
- Mock API responses without modifying the backend
- Block tracking scripts and ads
- Add authentication headers dynamically
I knew Chrome DevTools Protocol (CDP) had a Fetch domain for this, but Lightpanda is a newer browser and I wasn’t sure if it was supported.
Environment
- Lightpanda 0.1.0
- Node.js 20.x
- Puppeteer for CDP connection
First Attempt - Using Network Domain
I started by trying the Network domain, which I was familiar with from Puppeteer:
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({ browserWSEndpoint: "ws://127.0.0.1:9222",});
const page = await browser.newPage();
// Enable Network domain to see requestsconst client = await page.target().createCDPSession();await client.send('Network.enable');
client.on('Network.requestWillBeSent', (params) => { console.log('Request:', params.request.url);});
await page.goto('https://example.com');This worked for monitoring requests, but I couldn’t intercept or modify them. The Network domain only lets you observe, not modify. I needed something different.
Discovery - Fetch Domain
I dug into Lightpanda’s source code and found it does support the Fetch domain. The Fetch domain is specifically designed for request interception.
From src/cdp/domains/fetch.zig:
InterceptStatemanages request interception- Request interception via notification system
- Auth request handling support
The Fetch domain intercepts requests before they’re sent to the server.
How to Enable Request Interception
Step 1: Connect to Lightpanda
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({ browserWSEndpoint: "ws://127.0.0.1:9222",});
const page = await browser.newPage();const client = await page.target().createCDPSession();Step 2: Enable the Fetch Domain
// Enable Fetch with request patternsawait client.send('Fetch.enable', { patterns: [ { urlPattern: '*', requestStage: 'Request' } ]});The requestStage: 'Request' tells Lightpanda to intercept before the request is sent.
Step 3: Handle requestPaused Events
When a request matches your pattern, Lightpanda pauses it and fires a requestPaused event:
client.on('Fetch.requestPaused', async ({ requestId, request }) => { console.log('Intercepted:', request.url);
// Option 1: Continue with original request await client.send('Fetch.continueRequest', { requestId });});Practical Use Cases
Mocking API Responses
This is what I needed originally - return fake data instead of calling the real API:
client.on('Fetch.requestPaused', async ({ requestId, request }) => { // Intercept specific API calls if (request.url.includes('/api/users')) { // Return mock response instead of hitting the server await client.send('Fetch.fulfillRequest', { requestId, responseCode: 200, responseHeaders: [ { name: 'Content-Type', value: 'application/json' } ], body: Buffer.from(JSON.stringify({ users: [ { id: 1, name: 'Test User' }, { id: 2, name: 'Mock User' } ] })).toString('base64') }); return; }
// Let other requests through await client.send('Fetch.continueRequest', { requestId });});Blocking Requests
To block tracking scripts and ads:
const blockedPatterns = [ 'analytics.js', 'tracking.js', 'adservice', 'doubleclick.net'];
client.on('Fetch.requestPaused', async ({ requestId, request }) => { // Check if URL matches blocked patterns const shouldBlock = blockedPatterns.some(pattern => request.url.includes(pattern) );
if (shouldBlock) { console.log('Blocked:', request.url); await client.send('Fetch.failRequest', { requestId, errorReason: 'BlockedByClient' }); return; }
// Continue normal requests await client.send('Fetch.continueRequest', { requestId });});Modifying Request Headers
Adding authentication headers dynamically:
client.on('Fetch.requestPaused', async ({ requestId, request }) => { // Add auth header to API requests if (request.url.includes('/api/')) { const headers = Object.entries(request.headers).map(([name, value]) => ({ name, value })); headers.push({ name: 'Authorization', value: 'Bearer my-secret-token' });
await client.send('Fetch.continueRequest', { requestId, headers }); return; }
// Continue without modification await client.send('Fetch.continueRequest', { requestId });});Complete Example
Here’s a complete working example:
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({ browserWSEndpoint: "ws://127.0.0.1:9222",});
const page = await browser.newPage();const client = await page.target().createCDPSession();
// Enable Fetch domain with patternsawait client.send('Fetch.enable', { patterns: [ { urlPattern: '*', requestStage: 'Request' } ]});
// Handle requestPaused eventsclient.on('Fetch.requestPaused', async ({ requestId, request }) => { console.log(`[${request.method}] ${request.url}`);
// Mock specific API if (request.url.includes('/api/status')) { await client.send('Fetch.fulfillRequest', { requestId, responseCode: 200, responseHeaders: [ { name: 'Content-Type', value: 'application/json' } ], body: Buffer.from(JSON.stringify({ status: 'ok' })).toString('base64') }); return; }
// Block tracking if (request.url.includes('analytics')) { await client.send('Fetch.failRequest', { requestId, errorReason: 'BlockedByClient' }); return; }
// Continue all other requests await client.send('Fetch.continueRequest', { requestId });});
await page.goto('https://example.com');await browser.close();What Didn’t Work
I tried a few things that failed:
-
Using Network.requestIntercepted - This method exists in older CDP versions but is deprecated. Lightpanda uses the newer Fetch domain.
-
Intercepting without patterns - You must specify at least one pattern in
Fetch.enable. Passing an empty patterns array does nothing. -
Base64 encoding issues - The
bodyparameter infulfillRequestmust be base64 encoded. I initially forgot this and got invalid responses.
Summary
In this post, I showed how to use Lightpanda’s CDP Fetch domain for network interception. The key steps are:
- Enable Fetch with
Fetch.enableand specify URL patterns - Listen for
requestPausedevents - Use
continueRequest,fulfillRequest, orfailRequestto control the request
This enables sophisticated testing scenarios like mocking APIs, blocking resources, and modifying requests on the fly - all without touching the backend server.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Chrome DevTools Protocol - Fetch Domain
- 👨💻 Lightpanda GitHub Repository
- 👨💻 CDP Network Domain Documentation
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments