Skip to content
End To End Tester

Selenium WebDriver

The W3C standard for browser automation — where it still wins, the waiting model that decides whether a suite is stable, and Grid for scale.

2 min read · updated 19 September 2026

Selenium drives a browser through the W3C WebDriver protocol, which is a published standard implemented by the browser vendors themselves. That is its enduring advantage: every browser, every cloud device farm and every major language speaks it.

It is also older than the alternatives and shows it, chiefly in the waiting model.

#The waiting model is the whole story

Selenium does not wait for you. An element lookup that runs before the page is ready throws, and it is your job to say what "ready" means.

java
// Java. Explicit waits: the only reliable form.
var wait = new WebDriverWait(driver, Duration.ofSeconds(10));

driver.get("https://example.test/products/field-notes");
wait.until(ExpectedConditions.elementToBeClickable(By.cssSelector("[data-test=add-to-cart]")))
    .click();

var status = wait.until(
    ExpectedConditions.textToBePresentInElementLocated(By.cssSelector("[role=status]"), "1 item"));
assertThat(status).isTrue();
csharp
// C#. Same shape; note the implicit wait is explicitly disabled.
var driver = new ChromeDriver();
driver.Manage().Timeouts().ImplicitWait = TimeSpan.Zero;   // do not mix the two

var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.Until(d => d.FindElement(By.CssSelector("[data-test=pay]"))).Click();
wait.Until(d => d.FindElement(By.CssSelector("[data-test=confirmation]")).Displayed);
python
# Python. Custom conditions are just callables.
wait = WebDriverWait(driver, 10)
wait.until(lambda d: d.find_element(By.CSS_SELECTOR, "[data-test=total]").text == "£12.00")

Never mix implicit and explicit waits. An implicit wait of 10s combined with an explicit wait of 10s does not give you 10s; it gives you compound, unpredictable behaviour where a negative check (findElements(...).isEmpty()) silently takes the full implicit timeout. Set the implicit wait to zero and be explicit everywhere.

Thread.sleep is never the answer. It is the leading cause of flaky Selenium suites, and the second leading cause is a suite that removed the sleeps and replaced them with a longer implicit wait.

#What Selenium 4 added

  • W3C protocol by default — no more JSON Wire Protocol translation.
  • Relative locatorswith(By.tagName("input")).below(By.id("email")). Useful occasionally; brittle if overused.
  • CDP access for Chromium — network interception, console capture, geolocation, basic auth. Non-standard and browser-specific, but it closes much of the feature gap with Playwright for Chrome-only work.
  • BiDi — the emerging bidirectional standard that will eventually give all browsers events and interception through a standard protocol. This is the thing to watch; it is what makes Selenium competitive again on capability.
java
// Selenium 4 CDP: fail a dependency on demand, Chromium only.
var devTools = ((HasDevTools) driver).getDevTools();
devTools.createSession();
devTools.send(Network.enable(Optional.empty(), Optional.empty(), Optional.empty()));
devTools.addListener(Network.responseReceived(), response ->
    log.info("{} {}", response.getResponse().getStatus(), response.getResponse().getUrl()));

#Grid

yaml
# docker-compose.yml — a grid in fifteen lines
services:
  selenium-hub:
    image: selenium/hub:4.25
    ports: ["4442:4442", "4443:4443", "4444:4444"]

  chrome:
    image: selenium/node-chromium:4.25
    shm_size: 2gb                 # without this, Chrome crashes under load
    depends_on: [selenium-hub]
    environment:
      SE_EVENT_BUS_HOST: selenium-hub
      SE_EVENT_BUS_PUBLISH_PORT: 4442
      SE_EVENT_BUS_SUBSCRIBE_PORT: 4443
      SE_NODE_MAX_SESSIONS: 4
    deploy:
      replicas: 4
java
var options = new ChromeOptions();
var driver = new RemoteWebDriver(new URL("http://localhost:4444"), options);

shm_size: 2gb is not optional. The default 64MB of shared memory causes Chrome to crash intermittently under parallel load, and it is one of the most-reported "flaky grid" causes there is.

#Where Selenium still wins

  • Language coverage. Official bindings for Java, C#, Python, JavaScript, Ruby and Kotlin, plus community bindings for almost everything else.
  • Real devices and browser clouds. BrowserStack, Sauce Labs, LambdaTest and the rest are built on WebDriver. If you need Safari on an actual iPad, this is the road.
  • Existing estates. A thousand working Selenium tests are worth more than a migration plan. Improving the waiting discipline in place usually buys more than a rewrite.
  • Standardisation requirements. Regulated environments that require a standards-based tool.

#Where it does not

For a new web-only suite, Playwright gives you auto-waiting, tracing, parallelism and sharding out of the box, and a Selenium suite spends a great deal of code re-implementing the first two. The honest comparison is not capability — it is how much of your suite is waiting logic you had to write yourself, and how confident you are that all of it is right.

Structure either one with page objects; the pattern originated here.

Common questions

Is Selenium obsolete?
No. It is the only W3C-standardised browser automation protocol, which is why every browser vendor and every device cloud supports it. For a greenfield web suite Playwright is usually the better tool; for a large estate with an existing grid, many languages, or real-device requirements, Selenium remains the pragmatic choice.
What is the difference between implicit and explicit waits?
An implicit wait tells the driver to retry element lookup for up to N seconds globally. An explicit wait waits for a specific condition at a specific point. Mixing the two produces unpredictable compound timeouts — pick explicit waits and set the implicit wait to zero.
What is Selenium Grid for?
Running tests against browsers on other machines — many browsers in parallel, browsers you cannot install locally, or real devices. Grid 4 runs as a single jar for small setups and as distributed components (router, distributor, session queue, nodes) for large ones.

Runnable samples for this page

last test results ↗

Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.

Was this page useful?