Intercept Requests & Responses with Selenium WebDriver BiDi Protocol

Published on December 11, 2024

Automate Traffic Interception with BiDi Network Domain

Thumbnail for article: ๐˜๐˜ฏ๐˜ต๐˜ฆ๐˜ณ๐˜ค๐˜ฆ๐˜ฑ๐˜ต ๐˜™๐˜ฆ๐˜ฒ๐˜ถ๐˜ฆ๐˜ด๐˜ต๐˜ด & ๐˜™๐˜ฆ๐˜ด๐˜ฑ๐˜ฐ๐˜ฏ๐˜ด๐˜ฆ๐˜ด ๐˜ธ๐˜ช๐˜ต๐˜ฉ ๐˜š๐˜ฆ๐˜ญ๐˜ฆ๐˜ฏ๐˜ช๐˜ถ๐˜ฎ ๐˜ž๐˜ฆ๐˜ฃ๐˜‹๐˜ณ๐˜ช๐˜ท๐˜ฆ๐˜ณ ๐˜‰๐˜ช๐˜‹๐˜ช ๐˜—๐˜ณ๐˜ฐ๐˜ต๐˜ฐ๐˜ค๐˜ฐ๐˜ญ
Article agenda

Why Network Interception Is Important

Network traffic interception is an essential skill for modern testers, enabling us to monitor, manipulate, and validate API calls directly within our browser tests. Selenium WebDriverโ€™s BiDi (Bidirectional) Protocol provides a powerful mechanism to achieve this. This article explains how to intercept both requests and responses, with detailed code explanations.

Intercepting network traffic allows us to:

  • Debug: Monitor outgoing requests and incoming responses to detect issues.
  • Validate: Ensure APIs are called with correct parameters and headers.
  • Mock: Simulate failures or edge cases by modifying responses.
  • Measure Performance: Analyze request/response timings.

Setup and Teardown Boilerplateโ€Šโ€”โ€ŠCommon for Both Responses and Requests

Setting Up Selenium for BiDi

Before we dive into interception, we need to configure Selenium WebDriver for BiDi. Weโ€™ll use Firefox as an example, as it natively supports the BiDi protocol.

var options = new FirefoxOptions();
options.enableBiDi();
WebDriver driver = new FirefoxDriver(options);
Network network = new Network(driver);

๐‘ฌ๐’™๐’‘๐’๐’‚๐’๐’‚๐’•๐’Š๐’๐’

  1. FirefoxOptions options = new FirefoxOptions(): Creates an instance of Firefox-specific browser options.
  2. options.enableBiDi(): Enables BiDi support in Firefox.
  3. WebDriver driver = new FirefoxDriver(options): Initializes the WebDriver with BiDi-enabled options.
  4. Network network = new Network(driver): Provides access to the network domain for intercepting traffic.

Synchronizing with CountDownLatch

To ensure the program waits for network events before exiting, use CountDownLatch.

CountDownLatch latch = new CountDownLatch(2);
network.onBeforeRequestSent(event -> {
latch.countDown();
});
latch.await(5, TimeUnit.SECONDS);
assert (countdown);

๐‘ฌ๐’™๐’‘๐’๐’‚๐’๐’‚๐’•๐’Š๐’๐’

  1. CountDownLatch latch = new CountDownLatch(2): Initializes a latch with a count of 2, which ensures that the main thread waits until two network requests are intercepted before proceeding.
  2. latch.countDown(): Decrements the latch count by 1 after processing each intercepted request. This signals the main thread that a request has been handled.
  3. latch.await(5, TimeUnit.SECONDS): Blocks the main thread for up to 5 seconds or until the latch count reaches 0.
  4. assert (countdown): Asserts that the latch count reached 0 within the timeout, indicating successful interceptions.

Running the Test with Requests/Responses

Now, we can launch the browser and capture outgoing requests and incoming responses.

driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html");
Thread.sleep(3000);
network.close();
driver.quit();

๐‘ฌ๐’™๐’‘๐’๐’‚๐’๐’‚๐’•๐’Š๐’๐’

  1. driver.get(): Navigates to the specified URL.
  2. Thread.sleep(3000): Pauses the browser some time for demo only.
  3. network.close(): Terminates the BiDi network module and releases associated resources.
  4. driver.quit(): Closes the browser and ends the WebDriver session.

Intercepting Network Requests

Hereโ€™s how we can capture and log details about outgoing network requests.

Adding a Network Interceptor

network.addIntercept(new AddInterceptParameters(InterceptPhase.BEFORE_REQUEST_SENT));

๐‘ฌ๐’™๐’‘๐’๐’‚๐’๐’‚๐’•๐’Š๐’๐’

  1. network.addIntercept(): Sets up an interceptor that triggers before a network request is sent.
  2. AddInterceptParameters: Specifies the phase to intercept (BEFORE_REQUEST_SENT).

Handling Request Events

network.onBeforeRequestSent(beforeRequestSent -> {
String requestId = beforeRequestSent.getRequest().getRequestId();
FetchTimingInfo timings = beforeRequestSent.getRequest().getTimings();
String url = beforeRequestSent.getRequest().getUrl();
String method = beforeRequestSent.getRequest().getMethod();
List cookies = beforeRequestSent.getRequest().getCookies();
List
headers = beforeRequestSent.getRequest().getHeaders();
Long headersSize = beforeRequestSent.getRequest().getHeadersSize();

System.out.printf("%nRequest method %s %n. "
+ "Sent to URL %s %n. "
+ "Timing info %s %n. "
+ "Cookies %s %n. "
+ "Headers %s %n. "
+ "Headers size %s %n.",
method, url, timings.getRequestTime(), cookies, headers, headersSize);

network.continueRequest(new ContinueRequestParameters(requestId));

๐‘ฌ๐’™๐’‘๐’๐’‚๐’๐’‚๐’•๐’Š๐’๐’

  1. onBeforeRequestSent: Registers a listener that executes whenever a network request is about to be sent.
  2. Inside the callback:
  • requestId: Captures the unique ID of the request for further manipulation.
  • timings: Retrieves timing information (e.g., request start time).
  • url: The destination URL of the network request.
  • method: The HTTP method (e.g., GET, POST).
  • cookies: List of cookies sent with the request.
  • headers: List of headers sent with the request.
  • headersSize: Size of the headers, useful for debugging or optimization.

4. continueRequest: Proceeds with the request after capturing its details.

Note

Ensure that chosen parameter(s) are supported by the browser under test.

Parameters available to mock the outgoing requestโ€Šโ€”โ€Š๐˜จ๐˜ฆ๐˜ต๐˜™๐˜ฆ๐˜ฒ๐˜ถ๐˜ฆ๐˜ด๐˜ต๐˜๐˜ฅ(), ๐˜จ๐˜ฆ๐˜ต๐˜”๐˜ฆ๐˜ต๐˜ฉ๐˜ฐ๐˜ฅ(), etc.

Full Code and Test Run

InterceptRequest.java:

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.module.Network;
import org.openqa.selenium.bidi.network.AddInterceptParameters;
import org.openqa.selenium.bidi.network.ContinueRequestParameters;
import org.openqa.selenium.bidi.network.Cookie;
import org.openqa.selenium.bidi.network.FetchTimingInfo;
import org.openqa.selenium.bidi.network.Header;
import org.openqa.selenium.bidi.network.InterceptPhase;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;

import java.io.IOException;
import java.lang.Thread;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class InterceptRequest {

static protected WebDriver driver;
static protected Network network;

public static void main(String[] args) throws InterruptedException, IOException {

var options = new FirefoxOptions();
options.enableBiDi();
driver = new FirefoxDriver(options);
network = new Network(driver);

network.addIntercept(new AddInterceptParameters(InterceptPhase.BEFORE_REQUEST_SENT));

CountDownLatch latch = new CountDownLatch(2);

network.onBeforeRequestSent(beforeRequestSent -> {
String requestId = beforeRequestSent.getRequest().getRequestId();
FetchTimingInfo timings = beforeRequestSent.getRequest().getTimings();
String url = beforeRequestSent.getRequest().getUrl();
String method = beforeRequestSent.getRequest().getMethod();
List cookies = beforeRequestSent.getRequest().getCookies();
List
headers = beforeRequestSent.getRequest().getHeaders();
Long headersSize = beforeRequestSent.getRequest().getHeadersSize();

System.out.printf("%nRequest method %s %n. "
+ "Sent to URL %s %n. "
+ "Timing info %s %n. "
+ "Cookies %s %n. "
+ "Headers %s %n. "
+ "Headers size %s %n.",
method, url, timings.getRequestTime(), cookies, headers, headersSize);

network.continueRequest(new ContinueRequestParameters(requestId));

latch.countDown();

});

driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html");

boolean countdown = latch.await(5, TimeUnit.SECONDS);

assert (countdown);

Thread.sleep(3000);

network.close();
driver.quit();
};
}

When we run this code, we get the following output for debugging and analysis:

Console output with request method, timing, cookies, headers, and headers size.
Code execution demo: https://youtu.be/-T4UccVTk50

Intercepting and Modifying Network Responses

Next, we can capture and analyze incoming responses to ensure they contain the expected data.

Adding a Response Interceptor

network.addIntercept(new AddInterceptParameters(InterceptPhase.RESPONSE_STARTED));

๐‘ฌ๐’™๐’‘๐’๐’‚๐’๐’‚๐’•๐’Š๐’๐’

  • addIntercept: Configures the network module to intercept responses when they are about to be sent to the browser (RESPONSE_STARTED phase).

Response Handling Logic

network.onResponseStarted(responseDetails -> {
String responseId = responseDetails.getRequest().getRequestId();
FetchTimingInfo timings = responseDetails.getRequest().getTimings();
String url = responseDetails.getRequest().getUrl();

System.out.printf("%nResponse sent for URL %s %n. "
+ "Timing info %s %n",
url, timings.getRequestTime());

if (url.contains("selenium.dev")) {
ContinueResponseParameters responseParams = new ContinueResponseParameters(responseId).statusCode(500);
network.continueResponse(responseParams);
} else {
network.continueResponse(new ContinueResponseParameters(responseId));
}

๐‘ฌ๐’™๐’‘๐’๐’‚๐’๐’‚๐’•๐’Š๐’๐’

  1. onResponseStarted: Registers a callback when a response is received.
  2. Inside the callback:
  • responseId: A unique identifier for the response, needed to continue or modify it.
  • url: URL of the resource being requested.
  • timings: Timing information about when the request was made.

3. continueResponse: Conditional logic on how to proceed with the response.

  • If the URL contains selenium.dev, the response is intercepted and a 500 status code (Internal Server Error) is sent.
  • Otherwise, the response is allowed to continue unmodified.

Note

Parameters available to mock the incoming responseโ€Šโ€”โ€ŠstatusCode(), reasonPhrase(), etc.

Full Code and Test Run

InterceptResponse.java:

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.module.Network;
import org.openqa.selenium.bidi.network.AddInterceptParameters;
import org.openqa.selenium.bidi.network.ContinueResponseParameters;
import org.openqa.selenium.bidi.network.FetchTimingInfo;
import org.openqa.selenium.bidi.network.InterceptPhase;

import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;

import java.io.IOException;
import java.lang.Thread;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class InterceptResponse {

static protected WebDriver driver;
static protected Network network;

public static void main(String[] args) throws InterruptedException, IOException {

var options = new FirefoxOptions();
options.enableBiDi();

driver = new FirefoxDriver(options);
network = new Network(driver);

network.addIntercept(new AddInterceptParameters(InterceptPhase.RESPONSE_STARTED));

CountDownLatch latch = new CountDownLatch(2);

network.onResponseStarted(responseDetails -> {
String responseId = responseDetails.getRequest().getRequestId();
FetchTimingInfo timings = responseDetails.getRequest().getTimings();
String url = responseDetails.getRequest().getUrl();

System.out.printf("%nResponse sent for URL %s %n. "
+ "Timing info %s %n",
url, timings.getRequestTime());

if (url.contains("selenium.dev")) {
ContinueResponseParameters responseParams = new ContinueResponseParameters(responseId).statusCode(500);
network.continueResponse(responseParams);
} else {
network.continueResponse(new ContinueResponseParameters(responseId));
}

latch.countDown();

});

driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html");

boolean countdown = latch.await(5, TimeUnit.SECONDS);

assert (countdown);

Thread.sleep(3000);

network.close();
driver.quit();
};
}

When we run this program, we get the following output:

Console output with response URL and timing.
Code execution demo: https://youtu.be/FipU9ZqNBRo

Conclusion

Selenium WebDriverโ€™s BiDi protocol opens a new frontier for automated testing, offering unparalleled insights into network traffic. By following these step-by-step examples, you can integrate request and response interception into your test suites, improving debugging, validation, and performance monitoring capabilities.

๐‘ช๐’๐’Ž๐’Ž๐’๐’ ๐‘ผ๐’”๐’† ๐‘ช๐’‚๐’”๐’†๐’” ๐’‡๐’๐’“ ๐‘ฉ๐’Š๐‘ซ๐’Š ๐‘ท๐’“๐’๐’•๐’๐’„๐’๐’:

  1. API Testing: Validate that the front-end makes the correct API calls.
  2. Performance Monitoring: Track request/response timings.
  3. Error Handling: Simulate and handle network errors.
  4. Security Analysis: Inspect cookies, headers, and payloads.

Go beyond UI testingโ€Šโ€”โ€Šharness the power of network interception!

๐“—๐’ถ๐“…๐“…๐“Ž ๐“‰๐“ฎ๐“ˆ๐“‰๐’พ๐“ƒ๐“ฐ ๐’ถ๐“ƒ๐’น ๐’น๐“ฎ๐’ท๐“Š๐“ฐ๐“ฐ๐’พ๐“ƒ๐“ฐ!

I welcome any comments and contributions to the subject. Connect with me on LinkedIn, X , GitHub, or Insta. Check out my website.

If you find this post useful, please consider buying me a coffee.