Table of Contents

Network Module

The Network module provides comprehensive control over HTTP/HTTPS traffic, including monitoring requests and responses, intercepting network calls, and capturing response bodies.

Overview

The Network module enables you to:

  • Monitor all network requests and responses
  • Intercept requests before they're sent
  • Modify or block network traffic
  • Capture request and response bodies
  • Handle authentication challenges
  • Track network errors

Accessing the Module

NetworkModule network = driver.Network;

Timeout and Cancellation

All commands in this module accept optional timeoutOverride and CancellationToken parameters. Use timeoutOverride to set a per-command timeout (defaults to BiDiDriver.DefaultCommandTimeout when omitted). Use CancellationToken for cooperative cancellation. See the API Design Guide for details and examples.

Monitoring Network Traffic

Basic Response Monitoring

// Add observer
driver.Network.OnResponseCompleted.AddObserver((ResponseCompletedEventArgs e) =>
{
    Console.WriteLine($"URL: {e.Response.Url}");
    Console.WriteLine($"Status: {e.Response.Status} {e.Response.StatusText}");
});

// Subscribe to events
SubscribeCommandParameters subscribe =
    new SubscribeCommandParameters(driver.Network.OnResponseCompleted.EventName);
await driver.Session.SubscribeAsync(subscribe);

// Navigate - events will fire for all requests
await driver.BrowsingContext.NavigateAsync(
    new NavigateCommandParameters(contextId, "https://example.com"));

Monitor Request Details

driver.Network.OnBeforeRequestSent.AddObserver((BeforeRequestSentEventArgs e) =>
{
    Console.WriteLine($"Request: {e.Request.Method} {e.Request.Url}");
    Console.WriteLine("Headers:");
    foreach (ReadOnlyHeader header in e.Request.Headers)
    {
        Console.WriteLine($"  {header.Name}: {header.Value.Value}");
    }
});

SubscribeCommandParameters subscribe =
    new SubscribeCommandParameters(driver.Network.OnBeforeRequestSent.EventName);
await driver.Session.SubscribeAsync(subscribe);

Filter by Content Type

// Find Content-Type header
driver.Network.OnResponseCompleted.AddObserver((ResponseCompletedEventArgs e) =>
{
    ReadOnlyHeader? contentType = e.Response.Headers
        .FirstOrDefault(h => h.Name.Equals("content-type", StringComparison.OrdinalIgnoreCase));

    if (contentType != null && contentType.Value.Value.Contains("application/json"))
    {
        Console.WriteLine($"JSON response from: {e.Response.Url}");
    }
});

Filter by URL Pattern

driver.Network.OnBeforeRequestSent.AddObserver((BeforeRequestSentEventArgs e) =>
{
    if (e.Request.Url.Contains("/api/"))
    {
        Console.WriteLine($"API call: {e.Request.Url}");
    }
});

Network Events

BeforeRequestSent

Fired when a request is about to be sent:

driver.Network.OnBeforeRequestSent.AddObserver((BeforeRequestSentEventArgs e) =>
{
    Console.WriteLine($"Method: {e.Request.Method}");
    Console.WriteLine($"URL: {e.Request.Url}");
    Console.WriteLine($"Request ID: {e.Request.RequestId}");
    Console.WriteLine($"Time origin: {e.Request.Timings.TimeOrigin}");
    Console.WriteLine($"Is Blocked: {e.IsBlocked}");
});

ResponseStarted

Fired when response headers are received:

driver.Network.OnResponseStarted.AddObserver((ResponseStartedEventArgs e) =>
{
    Console.WriteLine($"Status: {e.Response.Status}");
    Console.WriteLine($"Headers received for: {e.Response.Url}");
});

ResponseCompleted

Fired when response is fully received:

driver.Network.OnResponseCompleted.AddObserver((ResponseCompletedEventArgs e) =>
{
    Console.WriteLine($"Response complete: {e.Response.Url}");
    Console.WriteLine($"Bytes received: {e.Response.BytesReceived}");
});

FetchError

Fired when a network error occurs:

driver.Network.OnFetchError.AddObserver((FetchErrorEventArgs e) =>
{
    Console.WriteLine($"Network error for: {e.Request.Url}");
    Console.WriteLine($"Error: {e.ErrorText}");
});

AuthRequired

Fired when authentication is needed:

driver.Network.OnAuthRequired.AddObserver(async (AuthRequiredEventArgs e) =>
{
    // Provide credentials
    ContinueWithAuthCommandParameters parameters =
        new ContinueWithAuthCommandParameters(e.Request.RequestId)
        {
            Action = ContinueWithAuthActionType.ProvideCredentials,
            Credentials = new AuthCredentials("myuser", "mypassword"),
        };

    await driver.Network.ContinueWithAuthAsync(parameters);
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Intercepting Network Traffic

Network interception allows you to block, modify, or replace network requests.

Add Intercept

// Specify which phase to intercept
AddInterceptCommandParameters parameters =
    new AddInterceptCommandParameters(InterceptPhase.BeforeRequestSent);

// Optional: limit to specific contexts
parameters.Contexts.Add(contextId);

// Optional: URL patterns to intercept
parameters.UrlPatterns.AddRange(
[
    new UrlPatternPattern { HostName = "example.com" }
]);

AddInterceptCommandResult result = await driver.Network.AddInterceptAsync(parameters);
string interceptId = result.InterceptId;

Intercept Specific URLs

URL patterns are not wildcard or glob expressions. A UrlPatternPattern compares each part it sets (protocol, host name, port, path, query) for equality with the same part of the request URL, and a part it leaves unset matches anything. A UrlPatternString is a complete URL and matches only that URL. In either form the characters (, ), *, { and } are reserved: the remote end rejects a pattern containing one with an invalid argument error unless it is escaped with a backslash (\). To intercept a kind of resource, such as images, intercept without a pattern and check the request's Destination in the handler.

AddInterceptCommandParameters parameters =
    new AddInterceptCommandParameters(InterceptPhase.BeforeRequestSent);

// A pattern has no wildcards. Each part it sets is compared for equality with
// the same part of the request URL, and a part it leaves unset matches anything.
parameters.UrlPatterns.AddRange(
[
    // Every request to one host, over any protocol, port, path or query
    new UrlPatternPattern { HostName = "images.example.com" },

    // Every request to one path, on any host
    new UrlPatternPattern { PathName = "/api/data" },

    // A string pattern is a complete URL; it matches that URL only
    new UrlPatternString("https://example.com/app/config.json"),
]);

await driver.Network.AddInterceptAsync(parameters);

Block Requests

// Add intercept
AddInterceptCommandParameters addIntercept =
    new AddInterceptCommandParameters(InterceptPhase.BeforeRequestSent);
addIntercept.UrlPatterns.AddRange(
[
    new UrlPatternPattern { HostName = "ads.example.com" },
]);
await driver.Network.AddInterceptAsync(addIntercept);

// Handle intercepted requests
driver.Network.OnBeforeRequestSent.AddObserver(async (BeforeRequestSentEventArgs e) =>
{
    if (e.IsBlocked)
    {
        // Fail the request
        FailRequestCommandParameters failParams =
            new FailRequestCommandParameters(e.Request.RequestId);

        await driver.Network.FailRequestAsync(failParams);
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Continue Requests

driver.Network.OnBeforeRequestSent.AddObserver(async (BeforeRequestSentEventArgs e) =>
{
    if (e.IsBlocked)
    {
        // Optionally modify request
        ContinueRequestCommandParameters parameters =
            new ContinueRequestCommandParameters(e.Request.RequestId);

        // Add custom header
        parameters.Headers = new List<Header>();
        foreach (ReadOnlyHeader readOnlyHeader in e.Request.Headers)
        {
            parameters.Headers.Add(new Header(readOnlyHeader.Name, readOnlyHeader.Value.Value));
        }
        parameters.Headers.Add(new Header("X-Custom-Header", "MyValue"));

        await driver.Network.ContinueRequestAsync(parameters);
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Provide Custom Response

driver.Network.OnBeforeRequestSent.AddObserver(async (BeforeRequestSentEventArgs e) =>
{
    if (e.IsBlocked && e.Request.Url.Contains("/api/data"))
    {
        // Return custom JSON response
        string jsonResponse = "{\"message\": \"Mocked response\"}";

        ProvideResponseCommandParameters parameters =
            new ProvideResponseCommandParameters(e.Request.RequestId)
            {
                StatusCode = 200,
                ReasonPhrase = "OK",
                Body = BytesValue.FromString(jsonResponse),
            };

        parameters.Headers =
        [
            new Header("Content-Type", "application/json"),
        ];

        await driver.Network.ProvideResponseAsync(parameters);
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Continue Responses

ContinueResponseAsync continues a response that the browser has intercepted after it was received from the server but before it is presented to the browser, optionally overriding the status code, reason phrase, headers, cookies, or authentication credentials first. It complements ProvideResponseAsync (which supplies a complete response) and ContinueRequestAsync (which continues a paused request before it is sent).

Remove Intercept

RemoveInterceptCommandParameters parameters =
    new RemoveInterceptCommandParameters(interceptId);

await driver.Network.RemoveInterceptAsync(parameters);

Capturing Response Bodies

To capture response bodies, you must set up a data collector.

Create Data Collector

// Allocate memory for data collection (in bytes)
ulong maxSize = Convert.ToUInt64(Math.Pow(2, 24));  // 16 MB

AddDataCollectorCommandParameters parameters =
    new AddDataCollectorCommandParameters(maxSize, DataType.Response);

parameters.Contexts.Add(contextId);

AddDataCollectorCommandResult result =
    await driver.Network.AddDataCollectorAsync(parameters);

string collectorId = result.CollectorId;

Get Response Body

driver.Network.OnResponseCompleted.AddObserver(async (ResponseCompletedEventArgs e) =>
{
    // Only capture specific responses
    if (e.Response.Url.EndsWith(".json"))
    {
        GetDataCommandParameters getDataParams =
            new GetDataCommandParameters(e.Request.RequestId, DataType.Response)
            {
                CollectorId = collectorId,
                DisownCollectedData = true,  // Free memory after retrieval
            };

        GetDataCommandResult dataResult =
            await driver.Network.GetDataAsync(getDataParams);

        string body = dataResult.Bytes.Value;
        capturedBodies.Add(body);
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Release Response Body Data (DisownDataAsync)

Use DisownDataAsync to release response-body data held by a data collector when you no longer need it. This frees memory without retrieving the data first. Construct DisownDataCommandParameters with the collector ID, request ID, and data type:

DisownDataCommandParameters parameters =
    new DisownDataCommandParameters(collectorId, requestId, DataType.Response);

await driver.Network.DisownDataAsync(parameters);

Alternatively, you can set DisownCollectedData = true when calling GetDataAsync to release the data immediately after retrieval. Disowning requires CollectorId to name the collector the data is removed from; without it the command is rejected with invalid argument.

Remove Data Collector

RemoveDataCollectorCommandParameters parameters =
    new RemoveDataCollectorCommandParameters(collectorId);

await driver.Network.RemoveDataCollectorAsync(parameters);

Request/Response Headers

Reading Headers

driver.Network.OnResponseCompleted.AddObserver((ResponseCompletedEventArgs e) =>
{
    foreach (ReadOnlyHeader header in e.Response.Headers)
    {
        string name = header.Name;
        string value = header.Value.Value;
        Console.WriteLine($"{name}: {value}");
    }
});

Setting Custom Headers

The intercept-based approach adds or modifies headers for individual requests as they are intercepted. Use this when you need per-request control, conditional header injection, or to modify existing headers:

driver.Network.OnBeforeRequestSent.AddObserver(async (BeforeRequestSentEventArgs e) =>
{
    if (e.IsBlocked)
    {
        ContinueRequestCommandParameters parameters =
            new ContinueRequestCommandParameters(e.Request.RequestId);

        // Copy existing headers
        parameters.Headers = new List<Header>();
        foreach (ReadOnlyHeader readOnlyHeader in e.Request.Headers)
        {
            parameters.Headers.Add(new Header(readOnlyHeader.Name, readOnlyHeader.Value.Value));
        }

        // Add authorization header
        parameters.Headers.Add(new Header("Authorization", "Bearer mytoken"));

        await driver.Network.ContinueRequestAsync(parameters);
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Setting Global Extra Headers

Use SetExtraHeadersAsync to add headers to every request without intercepting traffic. This is simpler and more efficient when you need the same headers (e.g., Authorization, X-API-Key) on all requests. No intercept setup is required.

SetExtraHeadersCommandParameters parameters = new SetExtraHeadersCommandParameters
{
    Headers =
    {
        new Header("Authorization", "Bearer mytoken"),
        new Header("X-API-Key", "my-api-key"),
    },
};

await driver.Network.SetExtraHeadersAsync(parameters);

Clear Extra Headers

To remove the session-wide (unscoped) extra headers, use SetExtraHeadersCommandParameters.ResetExtraHeaders. Headers set for specific browsing contexts or user contexts are stored separately and are not affected; clear them by sending an empty Headers list with the same Contexts or UserContexts populated:

SetExtraHeadersCommandParameters parameters =
    SetExtraHeadersCommandParameters.ResetExtraHeaders;

await driver.Network.SetExtraHeadersAsync(parameters);

Cache Behavior

Use SetCacheBehaviorAsync to control whether the browser uses its cache for network requests. This is useful when testing to ensure fresh data (bypass cache) or to restore normal caching behavior.

Bypass Cache

SetCacheBehaviorCommandParameters parameters =
    new SetCacheBehaviorCommandParameters(CacheBehavior.Bypass)
    {
        Contexts = { contextId },
    };

await driver.Network.SetCacheBehaviorAsync(parameters);

Restore Default Cache Behavior

SetCacheBehaviorCommandParameters parameters =
    new SetCacheBehaviorCommandParameters(CacheBehavior.Default)
    {
        Contexts = { contextId },
    };

await driver.Network.SetCacheBehaviorAsync(parameters);

Cookies (Storage Module)

Cookie commands belong to the Storage module (driver.Storage), not the Network module. They are shown here because network testing frequently involves setting up or inspecting cookies; see the Storage Module guide for full coverage.

// Use Storage module for cookies
SetCookieCommandParameters parameters = new SetCookieCommandParameters(
    new PartialCookie("sessionId", BytesValue.FromString("abc123"), "example.com")
    {
        Path = "/",
        Secure = true,
        HttpOnly = true,
        SameSite = CookieSameSiteValue.Strict,
    });

await driver.Storage.SetCookieAsync(parameters);

Get Cookies

GetCookiesCommandParameters parameters = new GetCookiesCommandParameters();
parameters.Partition = new BrowsingContextPartitionDescriptor(contextId);

GetCookiesCommandResult result = await driver.Storage.GetCookiesAsync(parameters);

foreach (Cookie cookie in result.Cookies)
{
    Console.WriteLine($"{cookie.Name}: {cookie.Value.Value}");
    Console.WriteLine($"  Domain: {cookie.Domain}");
    Console.WriteLine($"  Expires: {cookie.Expires}");
}

Capturing Traffic

The events, intercepts, and data collectors above are the building blocks for recording traffic. Dramaturge puts them together in NetworkTrafficMonitor, which records each request with its response and bodies and writes the result as a HAR file.

Timing Information

driver.Network.OnResponseCompleted.AddObserver((ResponseCompletedEventArgs e) =>
{
    FetchTimingInfo timings = e.Request.Timings;

    Console.WriteLine($"Time origin: {timings.TimeOrigin}");
    Console.WriteLine($"Request time: {timings.RequestTime}");
    Console.WriteLine($"Response start: {timings.ResponseStart}");
    Console.WriteLine($"Response end: {timings.ResponseEnd}");
});

Common Patterns

Pattern 1: Collect All Requests

List<RequestData> allRequests = new List<RequestData>();

driver.Network.OnBeforeRequestSent.AddObserver((BeforeRequestSentEventArgs e) =>
{
    allRequests.Add(e.Request);
});

SubscribeCommandParameters subscribe =
    new SubscribeCommandParameters(driver.Network.OnBeforeRequestSent.EventName);
await driver.Session.SubscribeAsync(subscribe);

await driver.BrowsingContext.NavigateAsync(navParams);

// Wait for requests to complete
await Task.Delay(2000);

Console.WriteLine($"Total requests: {allRequests.Count}");
foreach (var request in allRequests)
{
    Console.WriteLine($"  {request.Method} {request.Url}");
}

Pattern 2: Block Ad Domains

List<string> adDomains = new List<string>
{
    "ads.example.com",
    "tracker.example.com"
};

AddInterceptCommandParameters addIntercept =
    new AddInterceptCommandParameters(InterceptPhase.BeforeRequestSent);

foreach (string domain in adDomains)
{
    addIntercept.UrlPatterns.Add(
        new UrlPatternPattern { HostName = domain });
}

await driver.Network.AddInterceptAsync(addIntercept);

driver.Network.OnBeforeRequestSent.AddObserver(async (BeforeRequestSentEventArgs e) =>
{
    if (e.IsBlocked)
    {
        Console.WriteLine($"Blocking: {e.Request.Url}");
        await driver.Network.FailRequestAsync(
            new FailRequestCommandParameters(e.Request.RequestId));
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Pattern 3: Mock API Responses

Dictionary<string, string> mockResponses = new Dictionary<string, string>
{
    { "/api/user", "{\"name\": \"Test User\", \"id\": 123}" },
    { "/api/settings", "{\"theme\": \"dark\", \"lang\": \"en\"}" },
};

AddInterceptCommandParameters addIntercept =
    new AddInterceptCommandParameters(InterceptPhase.BeforeRequestSent);
await driver.Network.AddInterceptAsync(addIntercept);

driver.Network.OnBeforeRequestSent.AddObserver(async (BeforeRequestSentEventArgs e) =>
{
    if (e.IsBlocked)
    {
        string path = new Uri(e.Request.Url).AbsolutePath;

        if (mockResponses.TryGetValue(path, out string? mockData))
        {
            ProvideResponseCommandParameters parameters =
                new ProvideResponseCommandParameters(e.Request.RequestId)
                {
                    StatusCode = 200,
                    Body = BytesValue.FromString(mockData),
                };

            parameters.Headers =
            [
                new Header("Content-Type", "application/json"),
            ];

            await driver.Network.ProvideResponseAsync(parameters);
        }
        else
        {
            await driver.Network.ContinueRequestAsync(
                new ContinueRequestCommandParameters(e.Request.RequestId));
        }
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Pattern 4: Capture Complete HTTP Transaction

Dictionary<string, HttpTransaction> transactions =
    new Dictionary<string, HttpTransaction>();

// Set up data collector
AddDataCollectorCommandParameters addParams =
    new AddDataCollectorCommandParameters(Convert.ToUInt64(Math.Pow(2, 26)), DataType.Response);
addParams.Contexts.Add(contextId);

AddDataCollectorCommandResult collectorResult =
    await driver.Network.AddDataCollectorAsync(addParams);
string collectorId = collectorResult.CollectorId;

// Capture requests
driver.Network.OnBeforeRequestSent.AddObserver((BeforeRequestSentEventArgs e) =>
{
    transactions[e.Request.RequestId] = new HttpTransaction
    {
        Request = e.Request,
    };
});

// Capture responses and bodies
driver.Network.OnResponseCompleted.AddObserver(async (ResponseCompletedEventArgs e) =>
{
    if (transactions.TryGetValue(e.Request.RequestId, out HttpTransaction? transaction))
    {
        transaction.Response = e.Response;

        // Get response body
        GetDataCommandParameters getDataParams =
            new GetDataCommandParameters(e.Request.RequestId, DataType.Response)
            {
                CollectorId = collectorId,
                DisownCollectedData = true,
            };

        GetDataCommandResult dataResult =
            await driver.Network.GetDataAsync(getDataParams);

        transaction.ResponseBody = dataResult.Bytes.Value;
    }
},
ObservableEventHandlerOptions.RunHandlerAsynchronously);

Best Practices

  1. Use data collectors wisely: They consume memory; remove when done
  2. Run async handlers: Network event handlers often need to call commands
  3. Filter events: Don't process every request if you only need specific ones
  4. Clean up intercepts: Remove intercepts when no longer needed
  5. Handle errors: Network operations can fail; use try-catch
  6. Consider timing: Some network events happen very quickly

Troubleshooting

Events Not Firing

  • Ensure you've subscribed to events through Session module
  • Check that navigation has actually started
  • Verify URL patterns in intercepts are correct

Missing Response Bodies

  • Data collector must be added before navigation
  • Ensure sufficient memory allocated
  • Check that DisownCollectedData is set appropriately

Intercepts Not Working

  • Verify intercept was added before navigation
  • Check URL patterns match the requests
  • Ensure IsBlocked is true in event handler

Next Steps

API Reference

See the API documentation for complete details on all classes and methods in the Network module.