
Fine-Tune Browser Context Automation with WebDriver BiDi
Practice Event-Driven Automation with Selenium 4 in Java. Listen to Selective Events from a Specific Tab/Window with BiDi.


Introduction
In the evolving landscape of software testing, Selenium WebDriver v4 continues to redefine boundaries, introducing features that enhance the ways we interact with modern web applications. One such game-changer is the ability to tune into events fired from specific browser tabs or windows using the WebDriver BiDirectional Protocol (BiDi). This feature not only simplifies monitoring but also provides granular control over event subscriptions. Let’s dive into how this works and why it’s a must-have skill for SDETs.
Why Does This Feature Matter?
Before BiDi, subscribing to browser events globally was the norm, which could overwhelm testers with irrelevant logs, especially in multi-tab scenarios. While Chrome DevTools Protocol allowed some flexibility, it lacked the precision of focusing event subscriptions on specific browser contexts. Selenium’s BiDi solves this elegantly, enabling:
- Granular Event Monitoring: Subscribing to events from specific tabs or windows.
- Optimized Performance: Reducing noise by filtering unnecessary event logs.
- Streamlined Debugging: Focusing on the context that matters most.
How It Works: Test Scenario
Using Selenium WebDriver’s BiDi support, testers can:
- Open a browser tab (or window) and subscribe it to event logs, such as console logs or network events.
- Open additional tabs without subscribing to their events.
- Seamlessly switch contexts and monitor only the subscribed tabs.
Let’s look at an example that demonstrates this concept in action.

We’ll use https://devtools.glitch.me/console/log.html as a demo page. We’ll open two separate tabs in the browser, navigate to the same URL, and trigger click events which will log console messages. The actions performed in each tab are identical. The key difference that’ll be exhibited in the code walkthrough is that we’ll selectively subscribe to log events from one tab and not the other. The script will then make assertions on the log events we deliberately subscribed to and ignore the ones that are of no interest to us.
Code Break: Subscribing to Events from a Single Tab
Let’s write a Java test which illustrates how to subscribe to console log events from one tab while ignoring events from the other. We’ll subscribe to a single context. A context here is a unique ID for a tab or a window.
@Test
public void subscribeToSingleContextTest()
throws InterruptedException, ExecutionException, TimeoutException {
}
We open a window, starting with a tab. Let’s name it mainTab.
mainTab = driver.getWindowHandle();
We subscribe mainTab to the log events.
logInspector = new LogInspector(mainTab, driver);
Then we switch to the second tab, which we’ll name secondaryTab. We do not wish to subscribe secondaryTab to the log events.
driver.switchTo().newWindow(WindowType.TAB);
secondaryTab = driver.getWindowHandle();
We perform some actions, trigger events, but we aren’t subscribed to events on secondaryTab. So anything there will pretty much time out.
CompletableFuture future = new CompletableFuture<>();
logInspector.onConsoleEntry(future::complete);
navigateToWebPageAndClick(secondaryTab, WEB_PAGE_URL, ELEMENT_ID);
try {
ConsoleLogEntry logEntry = future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (TimeoutException e) {
Assert.fail("Test failed due to timeout.");
}
But when we switch to the original mainTab again, it has all the events we need to verify, since we’ve explicitly subscribed to those events.
navigateToWebPageAndClick(mainTab, WEB_PAGE_URL, ELEMENT_ID);
try {
ConsoleLogEntry logEntry = future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
Assert.assertEquals(logEntry.getText(), "Hello, Console!", "Unexpected log text.");
Assert.assertEquals(logEntry.getType(), "console", "Unexpected log type.");
Assert.assertEquals(logEntry.getLevel(), LogLevel.INFO, "Unexpected log level.");
} catch (TimeoutException e) {
Assert.fail("Test failed due to timeout.");
}
Now, let’s actually define the helper method that would navigate to a web page and click on a specified element by its ID:
private void navigateToWebPageAndClick(String tabHandle, String url, String elementId) {
driver.switchTo().window(tabHandle);
driver.get(url);
driver.findElement(By.id(elementId)).click();
Code Overview
- Tab Identification: Each browser tab has a unique context ID (window handle) that serves as a key for event subscription.
- Selective Subscription: Using the LogInspector tied to mainTab, only events from the first tab are captured.
- Timeout Behavior: Any attempt to fetch events from the unsubscribed tab (secondaryTab) results in a timeout.
- Event Retrieval: Returning to mainTab confirms that the subscription works as expected, capturing relevant console logs.
Full Test Code and Execution
Below captioned is the complete Java program. Note that I’ve sprinkled in some Selenium Logger statements throughout the code to visibly trace test execution in the IDE console. I’ve also arbitrarily suppressed a Firefox deprecation warning with the help of the logger. Feel free to remove those lines, they aren’t mandatory for the actual test to pass.
Moreover, for demo purposes, the code contains some hard waits and a generous amount of comments. Again, feel free to copy and remove any unnecessary noise as you see fit.
ListenToSingleContextEvents.java:
public class ListenToSingleContextEvents {
// Logger instance to log test execution details and errors
private static final Logger LOGGER = Logger.getLogger(ListenToSingleContextEvents.class.getName());
// Constants used for test configuration
private static final String WEB_PAGE_URL = "https://devtools.glitch.me/console/log.html"; // Actual URL stays intact
private static final String ELEMENT_ID = "hello"; // ID of the element to be clicked
private static final long TIMEOUT_SECONDS = 5; // Timeout duration for waiting for log entries
private WebDriver driver; // WebDriver instance to interact with the browser
private LogInspector logInspector; // LogInspector instance to capture console logs from the browser
private String mainTab; // Handle for the main tab
private String secondaryTab; // Handle for the secondary tab
// This method runs before each test method to set up the environment
@BeforeTest
public void setup() {
// Suppress the warning about CDP support for Firefox
Logger.getLogger("org.openqa.selenium.firefox.FirefoxDriver").setLevel(Level.OFF);
LOGGER.setLevel(Level.FINE);
Arrays.stream(Logger.getLogger("").getHandlers()).forEach(handler -> handler.setLevel(Level.FINE));
LOGGER.info("Initializing WebDriver with BiDi support.");
FirefoxOptions options = new FirefoxOptions();
options.enableBiDi();
driver = new FirefoxDriver(options);
mainTab = driver.getWindowHandle();
LOGGER.info("Main tab handle assigned: " + mainTab);
}
// This method runs after each test method to clean up the environment
@AfterTest
public void teardown() {
LOGGER.info("Tearing down WebDriver and LogInspector.");
try {
// Closing the LogInspector if it is open to release resources
if (logInspector != null) {
logInspector.close();
}
} catch (Exception e) {
// Log any issues encountered while closing the LogInspector
LOGGER.warning("Error closing LogInspector: " + e.getMessage());
}
// Quit the WebDriver session after the test completes
if (driver != null) {
driver.quit();
}
}
// Test method to demonstrate subscribing to console logs from a specific tab
@Test
public void subscribeToSingleContextTest() throws InterruptedException, ExecutionException, TimeoutException {
LOGGER.info("Starting subscribeToSingleContextTest.");
// Step 1: Open a new tab and capture its window handle
driver.switchTo().newWindow(WindowType.TAB); // Create a new tab
secondaryTab = driver.getWindowHandle(); // Store the handle of the new tab
LOGGER.info("Secondary tab handle assigned: " + secondaryTab); // Log the handle of the new tab
// Step 2: Set up the LogInspector to listen only to the console logs of the main tab
logInspector = new LogInspector(mainTab, driver);
// Step 3: Set up a CompletableFuture to asynchronously capture console log entries
CompletableFuture future = new CompletableFuture<>();
// Attach the callback function to complete the future when a console log entry is captured
logInspector.onConsoleEntry(future::complete);
// Step 4: Perform actions in the secondary tab (open a URL and click an element)
navigateToWebPageAndClick(secondaryTab, WEB_PAGE_URL, ELEMENT_ID);
// Adding a brief pause (for demonstration purposes) to emphasize switching back to the main tab
Thread.sleep(3000); // 3-second pause to simulate real-world switching delay
// Step 5: Switch back to the main tab and perform the same actions (open URL, click element)
navigateToWebPageAndClick(mainTab, WEB_PAGE_URL, ELEMENT_ID);
// Step 6: Wait for the console log entry and verify the captured log entry
try {
// Attempt to get the console log entry from the future within the specified timeout
ConsoleLogEntry logEntry = future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
LOGGER.fine("Verifying console log entry: " + logEntry); // Log the captured log entry
// Assert that the console log entry contains the expected values
Assert.assertEquals(logEntry.getText(), "Hello, Console!", "Unexpected log text.");
Assert.assertEquals(logEntry.getType(), "console", "Unexpected log type.");
Assert.assertEquals(logEntry.getLevel(), LogLevel.INFO, "Unexpected log level.");
} catch (TimeoutException e) {
// If the timeout is reached without capturing a log entry, log the error and fail the test
LOGGER.severe("Timeout waiting for console log entry: " + e.getMessage());
Assert.fail("Test failed due to timeout.");
}
}
/**
* Helper method to navigate to a webpage and click on a specified element by its ID.
* It switches to the provided tab, performs the actions, and logs the actions.
*
* @param tabHandle The tab handle to switch to (either mainTab or secondaryTab).
* @param url The URL to navigate to in the browser.
* @param elementId The ID of the element to click on once the page is loaded.
*/
private void navigateToWebPageAndClick(String tabHandle, String url, String elementId) {
// Switch to the specified tab and log the action
driver.switchTo().window(tabHandle);
// Log the navigation action (indicating which tab is used) with placeholder URL
if (tabHandle.equals(mainTab)) {
LOGGER.info("Navigating to WEB_PAGE_URL in main tab."); // Log with WEB_PAGE_URL placeholder
} else {
LOGGER.info("Navigating to WEB_PAGE_URL in secondary tab."); // Log with WEB_PAGE_URL placeholder
}
// Perform the navigation to the given URL
driver.get(url);
// Find the element by ID and click on it
driver.findElement(By.id(elementId)).click();
// Log the click action with dynamic tab reference
if (tabHandle.equals(mainTab)) {
LOGGER.info("Clicked element in main tab.");
} else {
LOGGER.info("Clicked element in secondary tab.");
}
}
}
Now we can run the code and observe the test pass.

The console outputs the following logs that are useful for debugging:

Expanding Use Cases
This feature isn’t limited to console logs. With BiDi, you can subscribe to various event types, including network requests, DOM mutations, and performance metrics, tailored to specific tabs or windows. For broader scenarios, global event subscription across all contexts is also supported by default — simply initialize the log inspector with the driver, and you are good to go! For a deeper dive, check out Selenium’s official documentation and explore advanced BiDi features in your automation toolkit.
Conclusion
The ability to listen to events from specific browser tabs or windows with Selenium WebDriver BiDi marks a significant step forward in fine-tuning test automation workflows. It empowers SDETs to:
- Reduce noise in complex, multi-tab test scenarios.
- Enhance debugging precision.
- Streamline performance monitoring.
Mastering this feature not only ensures robust test coverage but also demonstrates proficiency in leveraging cutting-edge tools for efficient automation. Start integrating BiDi into your Selenium projects today and experience the difference.

𝓗𝒶𝓅𝓅𝓎 𝓉𝓮𝓈𝓉𝒾𝓃𝓰 𝒶𝓃𝒹 𝒹𝓮𝒷𝓊𝓰𝓰𝒾𝓃𝓰!
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.