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. 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();// 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. 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 locators —
with(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.
// 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
# 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: 4var 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 ↗- JavaScript (Selenium)
selenium/tests/tools/selenium-webdriver
Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.
Was this page useful?
Related topics
- PlaywrightThe default browser automation tool in 2026 — auto-waiting locators, tracing, sharding and fixtures — with the configuration that matters and the mistakes that still cause flakiness.
- CypressIn-browser test execution, automatic retry-ability and time-travel debugging — what Cypress's architecture buys, and the constraints that come with it.
- The Page Object ModelEncapsulating a screen behind a class so tests describe intent rather than selectors — what it fixes, the god-object failure mode, and modern alternatives.
- Cross-Browser TestingWhich browsers actually need testing in 2026, what still differs between engines, and a strategy that catches real defects without tripling your pipeline.
- Browser TestingWhat a browser test can prove that nothing below it can, the three engines that matter, headless versus headed, and the cost model that should shape your suite.
- Flaky TestsWhy tests fail intermittently, the six root causes and how to fix each one, how to detect flakiness deliberately, and what to do with a test you cannot fix today.
- Traces and Debugging CI FailuresA trace records every action, request, console message and DOM snapshot of a run — the single artefact that turns an unreproducible CI failure into a five-minute diagnosis.
- AppiumOne WebDriver-based API across iOS and Android — how the drivers map onto XCUITest and UIAutomator, locator strategies, and the cost of cross-platform tests.