-
Notifications
You must be signed in to change notification settings - Fork 47
Description
Hey everyone,
I have been running some benchmarks comparing JS evaluation speeds across different protocols in Chrome 142, and the results for the WebDriver BiDi implementation show a significant performance difference compared to other available methods.
In my tests, the WebDriver BiDi path appears to have higher latency than both the legacy Classic HTTP path and raw CDP commands executed over the same WebSocket connection.
I am providing the full Java benchmark code below to help reproduce and investigate these observations.
The Benchmarks (Avg ms/call over 5000 iterations):
Chrome (142):
CDP (Raw WebSocket): 0.1651 ms
WebDriver BiDi: 1.4739 ms
Classic (W3C HTTP): 1.3714 ms
Firefox:
WebDriver BiDi: 0.2154 ms
Classic (W3C HTTP): 0.6153 ms
Observations:
Protocol Comparison in Chrome: There is a notable gap between CDP and WebDriver BiDi performance in Chrome. Given that both are utilizing WebSockets, I was curious about why the BiDi implementation shows roughly 9x higher latency than CDP for simple JS evaluation.
Cross-Browser Comparison: In Firefox, the WebDriver BiDi implementation is very efficient (0.21ms), performing significantly faster than its Classic HTTP counterpart and approaching the speeds seen with CDP in Chrome.
Regression vs Classic: In the Chrome environment, the WebDriver BiDi protocol is currently performing slightly slower than the legacy HTTP-based ChromeDriver calls.
Why this matters:
For use cases involving high-frequency synchronization—such as checking framework stability (Angular/React) before interactions—this latency adds up over the course of a large test suite. It would be great to understand what contributes to this overhead in Chrome and if there are ways to bring BiDi performance closer to the results seen in other implementations.
We are curious to know if this is a known behavior or if there are specific configurations or upcoming changes that might optimize these execution times. There is an open thread in the Selenium's Slack channel if You would like to share some insights/ask some additional questions:
https://seleniumhq.slack.com/archives/C0ABCS03F/p1768152955499779
Benchmark code:
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.bidi.module.Script;
import org.openqa.selenium.bidi.script.ContextTarget;
import org.openqa.selenium.bidi.script.EvaluateParameters;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.devtools.DevTools;
import org.openqa.selenium.devtools.HasDevTools;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import java.util.Optional;
public class JsEvaluationBenchmark {
private static final int ITERATIONS = 5000;
static void main(String[] args) {
runBenchmark(new ChromeDriver(new ChromeOptions().enableBiDi()), "Chrome");
runBenchmark(new FirefoxDriver(new FirefoxOptions().enableBiDi()), "Firefox");
}
private static void runBenchmark(WebDriver driver, String browserName) {
System.out.println("\n=== Testing Browser: " + browserName + " ===");
try {
driver.get("about:blank");
JavascriptExecutor setupExec = (JavascriptExecutor) driver;
setupExec.executeScript(
"document.body.innerHTML = `" +
"<div style='font-family:Segoe UI, sans-serif; padding:20px; background:#f4f7f6;'>" +
" <h2>" + browserName + " Protocol Benchmark</h2>" +
" <div style='display:flex; gap:15px;'>" +
" <div id='classic-box' style='flex:1; padding:15px; background:white; border-left:5px solid #e74c3c;'>Classic HTTP<div id='classic-counter' style='font-size:24px;'>0</div><div id='classic-res'>-</div></div>" +
" <div id='bidi-box' style='flex:1; padding:15px; background:white; border-left:5px solid #2ecc71;'>WebDriver BiDi<div id='bidi-counter' style='font-size:24px;'>0</div><div id='bidi-res'>-</div></div>" +
" <div id='cdp-box' style='flex:1; padding:15px; background:white; border-left:5px solid #3498db;'>CDP (Legacy WS)<div id='cdp-counter' style='font-size:24px;'>0</div><div id='cdp-res'>-</div></div>" +
" </div>" +
"</div>`;"
);
String handle = driver.getWindowHandle();
// 1. Classic Benchmark
long startClassic = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
setupExec.executeScript("document.getElementById('classic-counter').innerText = 'Iter: " + (i + 1) + "';");
}
long endClassic = System.nanoTime();
double classicAvg = printOnPage(setupExec, "classic", endClassic - startClassic);
// 2. BiDi Benchmark
long startBidi;
try (Script bidiModule = new Script(driver)) {
ContextTarget target = new ContextTarget(handle);
startBidi = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
bidiModule.evaluateFunction(new EvaluateParameters(target, "document.getElementById('bidi-counter').innerText = 'Iter: " + (i + 1) + "';", false));
}
}
long endBidi = System.nanoTime();
double bidiAvg = printOnPage(setupExec, "bidi", endBidi - startBidi);
// 3. CDP Benchmark
double cdpAvg = 0;
if (driver instanceof HasDevTools hasDevTools) {
DevTools devTools = hasDevTools.getDevTools();
devTools.createSession();
long startCdp = System.nanoTime();
for (int i = 0; i < ITERATIONS; i++) {
devTools.send(org.openqa.selenium.devtools.v143.runtime.Runtime.evaluate("document.getElementById('cdp-counter').innerText = 'Iter: " + (i + 1) + "';",
Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(),
Optional.empty(), Optional.empty(), Optional.empty()));
}
long endCdp = System.nanoTime();
cdpAvg = printOnPage(setupExec, "cdp", endCdp - startCdp);
} else {
setupExec.executeScript("document.getElementById('cdp-counter').innerText = 'Unsupported';");
}
System.out.printf("[%s] Classic: %.4f | BiDi: %.4f | CDP: %.4f ms/call%n",
browserName, classicAvg, bidiAvg, cdpAvg);
} finally {
try {
Thread.sleep(3000);
} catch (Exception ignored) {
}
driver.quit();
}
}
private static double printOnPage(JavascriptExecutor executor, String prefix, long nanoTime) {
double avg = (nanoTime / 1_000_000.0) / ITERATIONS;
executor.executeScript("document.getElementById('" + prefix + "-res').innerText = 'Avg: " + String.format("%.4f", avg) + " ms/call';");
return avg;
}
}