← Back to Index

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:

  1. Observe every HTTP request and response
  2. Read response bodies after they arrive
  3. Detect failed requests (404s, CORS errors, timeouts)
  4. Block specific URLs
  5. Intercept requests and modify them before they reach the server
  6. 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:

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:

  1. Network.requestWillBeSent - request is about to go out
  2. Network.responseReceived - headers arrived from the server
  3. Network.dataReceived - body chunks arriving (may fire multiple times)
  4. Network.loadingFinished - request complete, body fully received

Or, if something goes wrong:

  1. Network.requestWillBeSent
  2. Network.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:

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:

For a debugging agent: Monitoring 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.

Timing matters. You can only call 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'
});
Important: When 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
});
Getting the original request body: 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).