Lesson 5
The Network Domain
What You'll Learn
The Network domain is the Network panel made programmatic. Combined with the Fetch domain (which handles interception), you can:
- Observe every HTTP request and response
- Read response bodies after they arrive
- Detect failed requests (404s, CORS errors, timeouts)
- Block specific URLs
- Intercept requests and modify them before they reach the server
- Mock API responses without changing application code
For an AI debugging agent, this is the third "sense": DOM gives you sight (page structure), Runtime gives you hearing (console/errors), and Network gives you the data flow - you can see every API call an Angular app makes and whether it succeeded.
Network vs Fetch: Two Domains, Two Roles
CDP splits network functionality across two domains:
Network- passive observation. Enable it and you receive events for every request/response. You can also read response bodies, manage cookies, and block URLs.Fetch- active interception. Pauses requests mid-flight so you can modify, mock, or fail them before they complete.
Think of Network as a read-only wiretap, and Fetch as a man-in-the-middle proxy.
Observing Traffic with Network.enable
await send('Network.enable');Once enabled, you'll receive events for every request the page makes. The lifecycle of a single request produces these events in order:
Network.requestWillBeSent- request is about to go outNetwork.responseReceived- headers arrived from the serverNetwork.dataReceived- body chunks arriving (may fire multiple times)Network.loadingFinished- request complete, body fully received
Or, if something goes wrong:
Network.requestWillBeSentNetwork.loadingFailed- with an error reason (CORS, timeout, connection refused, etc.)
The Request Event
// Network.requestWillBeSent event
{
"method": "Network.requestWillBeSent",
"params": {
"requestId": "req-001",
"request": {
"url": "https://api.example.com/users",
"method": "GET",
"headers": {
"Authorization": "Bearer token-here",
"Accept": "application/json"
}
},
"timestamp": 1719012345.678,
"type": "XHR", // or "Fetch", "Document", "Stylesheet", etc.
"initiator": {
"type": "script",
"stackTrace": {
"callFrames": [
{
"functionName": "fetchUsers",
"url": "http://localhost:4200/main.js",
"lineNumber": 89
}
]
}
}
}
}Key fields:
requestId- use this to correlate with the response and body laterrequest.url,request.method,request.headers- what was senttype- the resource type (XHR,Fetch,Document,Stylesheet,Script,Image, etc.)initiator- what triggered this request. For XHR/fetch calls from your Angular code, this includes a stack trace showing which function made the call
The Response Event
// Network.responseReceived event
{
"method": "Network.responseReceived",
"params": {
"requestId": "req-001",
"response": {
"url": "https://api.example.com/users",
"status": 200,
"statusText": "OK",
"headers": {
"content-type": "application/json",
"content-length": "1234"
},
"mimeType": "application/json"
},
"type": "XHR"
}
}Failed Requests
// Network.loadingFailed event
{
"method": "Network.loadingFailed",
"params": {
"requestId": "req-002",
"errorText": "net::ERR_CONNECTION_REFUSED",
"canceled": false,
"type": "XHR"
}
}Common errorText values you'll encounter:
net::ERR_CONNECTION_REFUSED- server isn't runningnet::ERR_NAME_NOT_RESOLVED- DNS failurenet::ERR_FAILED- often a CORS block (checkNetwork.responseReceivedExtraInfofor details)net::ERR_TIMED_OUT- request took too longnet::ERR_CERT_AUTHORITY_INVALID- SSL certificate problem
Network.loadingFailed catches API connection issues (backend not running, wrong URL, CORS misconfiguration) - a common class of Angular bugs that produce confusing error messages in the UI but have a clear root cause in the network layer.
Reading Response Bodies
The response events give you headers and status, but not the body. To get the body, use Network.getResponseBody after the request completes (loadingFinished):
// Wait for loadingFinished, then read the body
const body = await send('Network.getResponseBody', {
requestId: 'req-001'
});
// body = {
// body: '{"users": [{"id": 1, "name": "Alice"}]}',
// base64Encoded: false
// }If base64Encoded is true, the body is binary content (images, fonts, etc.) encoded as base64. For JSON API responses, it'll be false and you get the raw string.
getResponseBody after Network.loadingFinished fires for that requestId. Calling it too early will return an error. The pattern: store requestIds from requestWillBeSent, then read bodies when loadingFinished arrives with the same ID.
Blocking URLs
You can block specific URL patterns from loading - useful for testing how the application handles missing resources:
// Block all requests matching these patterns
await send('Network.setBlockedURLs', {
urls: [
'https://api.example.com/analytics*', // Block analytics
'*.png', // Block all PNGs
'https://ads.example.com/*' // Block ads
]
});
// Blocked requests will fire loadingFailed with errorText: "net::ERR_BLOCKED_BY_CLIENT"Request Interception with the Fetch Domain
For actively modifying requests (not just observing), use the Fetch domain. This is how you'd mock API responses for testing:
// Enable interception for specific URL patterns
await send('Fetch.enable', {
patterns: [
{ urlPattern: 'https://api.example.com/*', requestStage: 'Response' }
]
});When a matching request is made, the browser pauses it and sends you a Fetch.requestPaused event. You must respond - either continue normally, modify, or provide a mock response:
Continue unmodified
// Let the request proceed as normal
await send('Fetch.continueRequest', {
requestId: pausedEvent.params.requestId
});Provide a mock response
// Respond with fake data - the server is never contacted
await send('Fetch.fulfillRequest', {
requestId: pausedEvent.params.requestId,
responseCode: 200,
responseHeaders: [
{ name: 'Content-Type', value: 'application/json' }
],
body: btoa(JSON.stringify({
users: [{ id: 1, name: 'Mock User' }]
})) // body must be base64 encoded
});Fail the request
// Simulate a network failure
await send('Fetch.failRequest', {
requestId: pausedEvent.params.requestId,
reason: 'ConnectionRefused'
});Fetch.enable is active and a request matches your patterns, the request is paused. You must respond with continueRequest, fulfillRequest, or failRequest. If you don't respond, the request hangs indefinitely. This is by design - it gives you complete control, but requires you to handle every matched request.
Practical Pattern: Watching API Calls in Angular
Here's how you'd monitor all API calls from an Angular app and detect failures:
await send('Network.enable');
// Track requests and their outcomes
const apiCalls = new Map();
ws.on('message', (data) => {
const msg = JSON.parse(data.toString());
if (!msg.method) return; // Not an event
// Track outgoing API requests
if (msg.method === 'Network.requestWillBeSent') {
const { requestId, request, type } = msg.params;
if (type === 'XHR' || type === 'Fetch') {
apiCalls.set(requestId, {
url: request.url,
method: request.method,
status: 'pending'
});
}
}
// Record successful responses
if (msg.method === 'Network.responseReceived') {
const { requestId, response } = msg.params;
if (apiCalls.has(requestId)) {
const call = apiCalls.get(requestId);
call.status = response.status;
call.statusText = response.statusText;
// Flag non-2xx responses
if (response.status >= 400) {
console.error(`API FAILURE: ${call.method} ${call.url} → ${response.status}`);
}
}
}
// Record network-level failures
if (msg.method === 'Network.loadingFailed') {
const { requestId, errorText } = msg.params;
if (apiCalls.has(requestId)) {
const call = apiCalls.get(requestId);
call.status = 'failed';
call.error = errorText;
console.error(`NETWORK FAILURE: ${call.method} ${call.url} → ${errorText}`);
}
}
});Edit and Resend Requests
Firefox DevTools has a "Edit and Resend" feature for network requests. Chrome's GUI doesn't offer this, but with CDP you can do it programmatically. The approach: capture the original request details, modify what you need, then fire a new request via Runtime.evaluate.
// Step 1: Capture original request details from a Network.requestWillBeSent event.
// (You'd store these as events arrive)
const originalRequest = {
url: 'https://api.example.com/users',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer original-token'
},
body: JSON.stringify({ name: 'Alice', role: 'admin' })
};
// Step 2: Modify whatever you want to change
const modifiedRequest = {
...originalRequest,
headers: {
...originalRequest.headers,
'Authorization': 'Bearer new-token' // Changed: different auth token
},
body: JSON.stringify({ name: 'Alice', role: 'viewer' }) // Changed: different role
};
// Step 3: Resend via Runtime.evaluate (executes a fetch in the page context)
const resendResult = await send('Runtime.evaluate', {
expression: `
fetch('${modifiedRequest.url}', {
method: '${modifiedRequest.method}',
headers: ${JSON.stringify(modifiedRequest.headers)},
body: ${JSON.stringify(modifiedRequest.body)}
}).then(response => response.json())
`,
awaitPromise: true,
returnByValue: true
});
console.log('Resend result:', resendResult.result.value);You can also use Network.replayXHR for a straight replay (no modifications) of a previous XHR request:
// Replay the exact same request - no modification possible
await send('Network.replayXHR', {
requestId: 'req-001' // The requestId from the original Network.requestWillBeSent event
});Network.requestWillBeSent includes the URL, method, and headers, but not always the POST body. To capture it, use Network.getRequestPostData({ requestId }) after the request fires. This gives you the raw body string to modify and resend.
Cookies and Cache
The Network domain also gives you access to cookies and cache - useful for debugging auth issues:
// Get all cookies for the current page
const cookies = await send('Network.getCookies');
// cookies.cookies = [{ name: 'session', value: '...', domain: '...', ... }]
// Set a cookie
await send('Network.setCookie', {
name: 'debug-mode',
value: 'true',
domain: 'localhost',
path: '/'
});
// Clear all cookies
await send('Network.clearBrowserCookies');
// Disable cache (like having DevTools "Disable cache" checked)
await send('Network.setCacheDisabled', {
cacheDisabled: true
});Key Commands Reference
| Command | Purpose |
|---|---|
Network.enable |
Start receiving network events |
Network.getResponseBody |
Read the response body (after loadingFinished) |
Network.setBlockedURLs |
Block requests matching URL patterns |
Network.getCookies |
Read cookies for the current page |
Network.setCacheDisabled |
Toggle cache (like DevTools "Disable cache") |
Fetch.enable |
Start intercepting requests (with URL patterns) |
Fetch.continueRequest |
Let an intercepted request proceed |
Fetch.fulfillRequest |
Respond with mock data (bypass server) |
Fetch.failRequest |
Simulate a network failure |
For the full list, see the Network and Fetch sections of the complete reference.
Primary Source
Read the Network domain documentation and the Fetch domain documentation. Note how Network is passive (observe) while Fetch is active (intercept and modify).