Your Selenium suite records failed requests with getDevTools(); what breaks when the same suite runs on Firefox?
answer
- One of the two routes is not cross-browser
- Check what the driver class actually implements
- HasDevTools lives on the Chromium driver
- Generated devtools classes are pinned per version
- bidi.module works where getDevTools() does not
basics
~10 sgetDevTools() comes from HasDevTools, which in Selenium 4 only ChromiumDriver implements. On Firefox the cast throws ClassCastException, so the Firefox runs either die or record nothing, leaving that browser's failures with no request evidence.
solid answer
~40 sIn Selenium 4, `getDevTools()` is a default method on `org.openqa.selenium.devtools.HasDevTools`, and among the local drivers only `ChromiumDriver` — so `ChromeDriver` and `EdgeDriver` — implements it. Casting a `FirefoxDriver` to `HasDevTools` throws `ClassCastException` before any capture starts; guarding the cast just means the Firefox runs record nothing, so a Firefox-only booking failure ships with no request evidence while the Chrome runs look fully instrumented. Selenium 4 also has `FirefoxOptions` set the `remote.active-protocols` preference to `1`, enabling BiDi only, so there is nothing to fall back to. The portable replacement is `org.openqa.selenium.bidi.module.Network` and `LogInspector` over the BiDi socket, which the options objects for Chromium, Firefox and Safari can all enable with `enableBiDi()`.
code
java · 31 linesimport java.util.Optional;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.devtools.DevTools;
import org.openqa.selenium.devtools.HasDevTools;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
public final class BookingBoardCaptureProbe {
public static Optional<DevTools> devToolsFor(WebDriver driver) {
if (!(driver instanceof HasDevTools)) {
return Optional.empty();
}
return ((HasDevTools) driver).maybeGetDevTools();
}
public static void main(String[] args) {
WebDriver chrome = new ChromeDriver();
WebDriver firefox = new FirefoxDriver(new FirefoxOptions().enableBiDi());
try {
chrome.get("https://gym.example.com/classes");
firefox.get("https://gym.example.com/classes");
System.out.println("chrome cdp available: " + devToolsFor(chrome).isPresent());
System.out.println("firefox cdp available: " + devToolsFor(firefox).isPresent());
} finally {
chrome.quit();
firefox.quit();
}
}
}go deeper
Recall that Selenium has two live-capture routes and that the getDevTools() one is Chromium-only. Knowing which class to import for a cross-browser suite is enough at this stage.
Explain the mechanics: HasDevTools is implemented by ChromiumDriver, the cast fails on other drivers, and the generated devtools classes are pinned to a browser major version with a closest-match fallback.
Demonstrate the operational consequence: a guarded cast turns a loud failure into silent blindness on one browser, and a report showing no failed requests on Firefox is then indistinguishable from a clean run.
Own the decision about how much a suite may depend on a non-standard bridge at all, what the migration path off it looks like, and how browser coverage and evidence coverage are kept from drifting apart.
## Two routes to the same evidence Selenium 4 offers a test two ways to watch traffic and console output while it happens, and they are not equally portable. - The **BiDi route**: `org.openqa.selenium.bidi.module.Network` and `org.openqa.selenium.bidi.module.LogInspector`, driven over the standard WebDriver BiDi socket. - The **DevTools bridge**: `((HasDevTools) driver).getDevTools()`, then `devTools.createSession()`, then `devTools.send(...)` and `devTools.addListener(...)` against generated classes such as `org.openqa.selenium.devtools.v150.network.Network`. The second route is the older one, and Selenium's own documentation describes its support as temporary. For an evidence harness the practical difference is not elegance — it is which browsers the harness silently stops working on. ## What `getDevTools()` needs before it works `getDevTools()` is a default method on the interface `org.openqa.selenium.devtools.HasDevTools`. Among Selenium 4's local drivers, **only `ChromiumDriver` implements it**, which means `ChromeDriver` and `EdgeDriver`. `FirefoxDriver` and `SafariDriver` do not implement the interface at all. That produces two different failures depending on how the cast is written: 1. `((HasDevTools) driver).getDevTools()` on a `FirefoxDriver` throws `ClassCastException` at the cast, before any DevTools code runs. 2. On a driver that does implement the interface but has no connection, `getDevTools()` throws `DevToolsException` with the message "Unable to create DevTools connection", because it is `maybeGetDevTools().orElseThrow(...)`. So the guard that actually holds is `driver instanceof HasDevTools` followed by `maybeGetDevTools()`, and only then `createSession()`. Selenium 4 also closed the Firefox door deliberately: the `FirefoxOptions` constructor sets the Firefox preference `remote.active-protocols` to `1`, which enables BiDi only. A capture harness built on `getDevTools()` therefore has no fallback on Firefox — there is nothing to fall back to. ## The second cost: version pinning The generated DevTools classes are per-browser-version packages — `v150`, `v151`, `v152` in the trunk sources — and Selenium ships only the three most recent Chrome versions at any time. When the installed browser moves past them, `org.openqa.selenium.devtools.CdpVersionFinder` logs a warning that it was "Unable to find an exact match for CDP version {0}, returning the closest version" and carries on against the nearest package it has. That is a *silent-ish* degradation: your imports still compile, the run still starts, and a domain or a field the newer browser changed simply stops behaving. | | BiDi `Network` / `LogInspector` | `getDevTools()` bridge | |---|---|---| | Browsers | Wherever `enableBiDi()` negotiates the socket — Chromium, Firefox, Safari options all expose it | Chromium only (`ChromeDriver`, `EdgeDriver`) | | Version coupling | Bindings track a standard protocol | Generated classes pinned to a browser major version | | Failure when unavailable | `IllegalArgumentException`: driver must support BiDi | `ClassCastException` or `DevToolsException` | | Console capture | `LogInspector.onConsoleEntry` | `HasLogEvents.onLogEvent(consoleEvent(...))`, itself backed by `HasDevTools` | ## What actually breaks on the gym suite Concretely, a class-booking suite that records every non-2xx call by sending a DevTools `Network.enable` and adding a listener will, on Firefox: - fail at the cast before the board ever loads, if the cast is unguarded; or - record nothing at all, if the code guards the cast and skips capture — so a Firefox-only booking bug ships with no request evidence while the Chrome runs look fully instrumented. Either way the cross-browser suite has capture on one browser and blindness on the rest, which is exactly the asymmetry an interviewer is probing for. ## The portable shape Rebuild the capture on `LogInspector` and the BiDi `Network` module, enable the socket through the options object the suite already builds, and keep the same sink and the same filter. The listener methods have the same shape on every browser, so the harness code stops branching on browser name: - `new Network(driver).onResponseCompleted(...)` and `.onFetchError(...)` replace a DevTools `Network.enable` plus listener pair. - `new LogInspector(driver).onConsoleEntry(...)` and `.onJavaScriptException(...)` replace `HasLogEvents.onLogEvent(consoleEvent(...))`. - Both modules are `AutoCloseable`, so a try-with-resources block gives you the subscribe and the unsubscribe in one scope. The one thing the BiDi constructors demand in return is that the driver implements `org.openqa.selenium.bidi.HasBiDi`; if the socket was never negotiated, `new Network(driver)` throws `IllegalArgumentException` with the message that the WebDriver instance must support the BiDi protocol. That is a loud, immediate failure at the start of the run rather than a quiet gap in a report, which is the behaviour you want from an evidence harness. ## When the DevTools route is still defensible It remains a reasonable choice when a suite is deliberately Chromium-only *and* needs a DevTools domain that the BiDi modules do not yet expose. Even then, treat it as a documented narrowing rather than a default: - Guard every use with `driver instanceof HasDevTools` followed by `maybeGetDevTools()`, never a bare cast. - Have the unavailable branch record "capture unavailable" in the run's evidence, so an empty list is never mistaken for a clean network. - Pin the browser version so the generated package matches, and treat the closest-version warning as a build signal rather than log noise.
- Why does casting a FirefoxDriver to HasDevTools fail rather than returning an empty Optional?`FirefoxDriver` does not implement the interface at all, so the JVM rejects the cast with `ClassCastException` before `maybeGetDevTools()` could ever be called. The empty `Optional` is only reachable on a driver that does implement `HasDevTools` but has no connection. Guard with `driver instanceof HasDevTools` first, then call `maybeGetDevTools()`.
- What tells you the generated DevTools classes no longer match the installed browser?`org.openqa.selenium.devtools.CdpVersionFinder` logs a warning that it was unable to find an exact match for the CDP version and is returning the closest one it has. Selenium ships generated packages for only the three most recent Chrome versions — `v150`, `v151` and `v152` in the trunk sources — so an auto-updated browser quietly runs against a near neighbour.
- If a Chromium-only suite needs the DevTools route anyway, how do you keep it honest?Guard it with `driver instanceof HasDevTools` and `maybeGetDevTools()`, and have the non-Chromium path report capture as unavailable rather than silently produce an empty evidence list. Pin the browser version so the generated package matches, and treat the closest-version warning in the log as a build signal rather than noise.
saying these in an interview costs you the question
- Says getDevTools() works on any Selenium driver, Firefox included
- Assumes the generated devtools packages track whatever Chrome is installed
- Calls getDevTools() and never calls createSession() before listening
- Treats the CDP bridge and BiDi capture as interchangeable everywhere
- Reads an empty evidence list on Firefox as proof the network was clean