Using Selenium 4's newest WebDriver BiDi

Jochen DJochen D
5 min read

Selenium is the go-to framework for automating web browsers, enabling developers and testers to simulate user interactions and verify web applications across various browsers. There's support from all the major browser vendors (Chrome, Firefox, Microsoft and Apple), in the form of drivers (ChromeDriver, Geckodriver, Edgedriver and Safaridriver).

With the release of Selenium 4, an exciting new feature called WebDriver BiDi (Bidirectional communication) was introduced. This new protocol allows for new features to be used in your tests and it improves the performance of your tests.

What is WebDriver BiDi?

WebDriver BiDi (Bidirectional) is a protocol introduced in Selenium 4 that enables bidirectional communication between the WebDriver client (your test script) and the browser. Unlike the traditional Selenium WebDriver, which only supports unidirectional communication, BiDi allows the browser to send events back to the WebDriver client. This opens up a plethora of new testing capabilities, especially for real-time monitoring and interaction with the browser.

The new protocol uses WebSockets under the hood, whereas the current WebDriver protocol uses HTTP.

Key Features of WebDriver BiDi

Bidirectional Communication: Unlike the traditional WebDriver protocol, BiDi supports bidirectional communication. This means that not only can your test script send commands to the browser, but the browser can also send events and data back to the test script. This is crucial for features like real-time logging and network interception.

Network Interception: With BiDi, you can intercept network requests and responses in real-time. This feature is particularly useful for testing scenarios involving network conditions, security checks, and API interactions. You can monitor, modify, or block network requests directly from your test script.

Console Log Access: BiDi allows you to capture and inspect the browser’s console logs during test execution. This feature is invaluable for debugging JavaScript errors, warnings, or any other logs that appear in the browser’s console.

Page Events: BiDi provides access to a wide range of browser events, such as page load, DOM changes, and user interactions. This allows for more detailed and accurate test reporting and analysis.

JavaScript Evaluation: BiDi enables the execution of arbitrary JavaScript in the browser context and returns the result to the WebDriver client. This can be useful for testing complex scenarios where direct interaction with the browser’s JavaScript environment is required.

Improved Performance: Since BiDi reduces the overhead of making multiple HTTP requests (as in the W3C protocol), tests can run faster and more efficiently, particularly when leveraging a large online browser grid like TestingBot.

Java Examples of WebDriver BiDi Features

Let’s explore some practical Java examples that demonstrate how to use WebDriver BiDi’s features.

1. Capturing Console Logs

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.module.LogInspector;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.MutableCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;


public class ListenToConsoleLogs {
  public static final String KEY = "KEY";
  public static final String SECRET = "SECRET";
  public static final String REMOTE_URL = "https://" + KEY + ":" + SECRET + "@hub.testingbot.com/wd/hub";
  static Boolean success = false;

   public static void main(String[] args) throws MalformedURLException {
       MutableCapabilities capabilities = new MutableCapabilities();
       capabilities.setCapability("browserName", "chrome");
       capabilities.setCapability("webSocketUrl", true);
       HashMap<String, Object> testingbotOptions = new HashMap<>();
       testingbotOptions.put("selenium-version", "4.23.0");
       capabilities.setCapability("tb:options", testingbotOptions);

       WebDriver driver = new RemoteWebDriver(new URL(REMOTE_URL), capabilities);

       try {
           Augmenter augmenter = new Augmenter();
           driver = augmenter.augment(driver);

           try (LogInspector logInspector = new LogInspector(driver)) {
               logInspector.onConsoleEntry(consoleLogEntry -> {
                   System.out.println("text: " + consoleLogEntry.getText());
                   System.out.println("level: " + consoleLogEntry.getLevel());
                   success = true;
               });

               String page = "https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html";
               driver.get(page);
               driver.findElement(By.id("consoleLog")).click();

               if (success) {
                   markTestStatus(true, "Console logs streaming", driver);
               } else {
                   markTestStatus(false, "Console logs did not stream", driver);
               }
               driver.quit();
           }
       } catch (Exception e) {
          markTestStatus(false, "Exception!", driver);
          e.printStackTrace();
          driver.quit();
       }
   }

   public static void markTestStatus(Boolean status, String reason, WebDriver driver) {
    TestingbotREST r = new TestingbotREST("key", "secret");
    Map<String, String> data = new HashMap<String, String>();
    data.put("success", "1");
    data.put("name", "My Test");
    r.updateTest(driver.getSessionId(), data);
   }
}

2. Intercepting Network Requests

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.module.LogInspector;
import org.openqa.selenium.remote.Augmenter;
import org.openqa.selenium.MutableCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

public class ListenToConsoleLogs {
  public static final String KEY = "KEY";
  public static final String SECRET = "SECRET";
  public static final String REMOTE_URL = "https://" + KEY + ":" + SECRET + "@hub.testingbot.com/wd/hub";
  static Boolean success = false;

   public static void main(String[] args) throws MalformedURLException {
       MutableCapabilities capabilities = new MutableCapabilities();
       capabilities.setCapability("browserName", "chrome");
       capabilities.setCapability("webSocketUrl", true);
       HashMap<String, Object> testingbotOptions = new HashMap<>();
       testingbotOptions.put("selenium-version", "4.23.0");
       capabilities.setCapability("tb:options", testingbotOptions);

       WebDriver driver = new RemoteWebDriver(new URL(REMOTE_URL), capabilities);

       try {
           Augmenter augmenter = new Augmenter();
           driver = augmenter.augment(driver);

           try (Network network = new Network(driver)) {
               network.addIntercept(new AddInterceptParameters(InterceptPhase.BEFORE_REQUEST_SENT));
               CountDownLatch latch = new CountDownLatch(1);
               network.onBeforeRequestSent(
                       beforeRequestSent -> {
                           network.continueRequest(
                                   new ContinueRequestParameters(beforeRequestSent.getRequest().getRequestId()));
                           latch.countDown();
                       });
               driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html");
               boolean countdown = latch.await(5, TimeUnit.SECONDS);
               if (countdown) {
                   markTestStatus(true, "Network request intercepted successfully", driver);
               } else {
                   markTestStatus(false, "Network request failed to intercept", driver);
               }
               driver.quit();
           }
       } catch (Exception e) {
          markTestStatus(false, "Exception!", driver);
          e.printStackTrace();
          driver.quit();
       }
   }

   public static void markTestStatus(Boolean status, String reason, WebDriver driver) {
    TestingbotREST r = new TestingbotREST("key", "secret");
    Map<String, String> data = new HashMap<String, String>();
    data.put("success", "1");
    data.put("name", "My Test");
    r.updateTest(driver.getSessionId(), data);
   }
}

Using WebDriver BiDi with TestingBot

TestingBot, a popular cloud-based browser testing platform, supports Selenium 4 and its WebDriver BiDi features. TestingBot’s large browser grid is optimized for running tests faster and more efficiently, which makes it an ideal platform to leverage the full capabilities of BiDi.

How TestingBot Enhances BiDi Testing

  1. Scalability: TestingBot’s grid can scale to run hundreds or thousands of tests in parallel. With BiDi’s reduced communication overhead, tests run faster.

  2. Real-Time Feedback: With BiDi, you can capture logs, network events and browser events in real-time. TestingBot’s dashboard integrates these capabilities, providing detailed insights and reports as tests are executed.

  3. Cross-Browser Support: TestingBot’s grid supports a wide range of browsers, including Chrome, Firefox, Opera and Safari. BiDi’s cross-browser capabilities align perfectly with TestingBot’s multi-browser testing environment, ensuring consistent test results across different platforms.

  4. Performance Optimization: By utilizing WebDriver BiDi on TestingBot’s infrastructure, tests are not only faster but also more reliable. The reduced need for multiple HTTP requests and the ability to handle browser events in real-time make BiDi an excellent choice for optimizing performance on large-scale testing grids.

0
Subscribe to my newsletter

Read articles from Jochen D directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jochen D
Jochen D