Selenium
Selenium is a browser automation toolkit, and it is older than most of the applications it tests — the reason it is still here is not inertia. It is a W3C standard with libraries attached: your test speaks a published protocol, browser vendors implement the other end, and that arrangement gives you something no single-vendor tool offers — any language, any real browser, on machines you control. This page covers the protocol, the waiting model people get wrong, Grid, and an honest answer to when you should still choose it.
- It is first
- A W3C standard
- Differentiator
- Any language, any browser
- Waiting is
- Yours to write
A protocol first, a library second
Understanding Selenium starts with the fact that it is not one program. Your test calls a client library in your language; the library sends WebDriver commands as ordinary HTTP requests carrying JSON; a driver supplied by the browser vendor receives them and does whatever that browser requires; the browser performs the action and the answer comes back the same way. Four layers, and the interesting one is the middle.
That middle layer is a published W3C specification, not a private interface, and it is why the two ends can be swapped independently. A team can write tests in Java while another writes them in Python against the same browsers, because both bindings emit the same commands. The same test can drive Chrome, Firefox, Edge or Safari, because each vendor ships a driver implementing the same specification. No company can deprecate the protocol out from under you, and no browser can be locked out of your suite by a tool vendor’s roadmap.
Waiting: the part everyone gets wrong first
Because the protocol answers questions rather than pushing events, a Selenium test that does not wait explicitly will act on a page that is not ready. There are three ways to handle that, and they are not equally good — one of them is a trap that has cost the industry an enormous number of hours.
| Approach | What it does | Verdict |
|---|---|---|
| Sleep for a fixed time | Stops the test for a chosen duration, regardless of what the page is doing. | Never. Too short on a loaded machine, wasted on a fast one, and it multiplies across the suite. |
| Implicit wait | A global setting that makes every element lookup retry for up to N seconds before failing. | Avoid. It looks convenient and it makes "assert this is absent" take the full timeout every time — and mixing it with explicit waits produces timeouts nobody can predict. |
| Explicit wait for a condition | Polls until a named condition holds — element visible, element clickable, text present, count changed. | The correct answer, every time. It states what you are waiting for, so a timeout message names the real problem. |
The second discipline that decides whether a Selenium suite ages well is how elements are located. Prefer an id or a purpose-added test attribute; accept a stable name or link text; treat a long CSS path as a warning and an XPath expression that walks up and across the DOM as a defect waiting for the next redesign. The rule is the same one that applies everywhere: locate by what the element IS, not by where it currently happens to sit.
Grid — the capability that is genuinely hard to replace
Grid puts a router in front of the same protocol. Tests send their commands to one address; the router holds a pool of nodes, each advertising which browsers and versions it can provide on which operating system, and hands every session to a node that matches what the test asked for. Nothing about the test changes — the only difference is the address it connects to.
A real browser matrix
Real Safari on real macOS, a specific Chrome version, an old Edge a customer still runs. Emulation cannot produce these; a machine running them can.
Scale you own
Hundreds of parallel sessions inside your own network, which matters when the environment under test cannot be reached from a vendor’s cloud.
Language freedom
Java, Python, JavaScript, C# and Ruby suites share one grid. In a large organisation this is often the deciding constraint.
Commercial device clouds sell the same idea as a service, with real mobile devices attached and no machines to maintain. The trade is the ordinary one: you stop running infrastructure and start paying per minute, and your test environment has to be reachable from outside. Either way, the capability being bought is the one Selenium is built around — real browsers, at scale, on an open protocol.
Choosing it in 2026, honestly
For a greenfield JavaScript or TypeScript suite, Playwright will usually give a better result with less work: waiting is handled, isolation is cheap, and a failure arrives with a replayable trace. Saying so is not a dismissal of Selenium — it is what makes the cases where Selenium wins meaningful rather than defensive.
Selenium is the right call when
- Tests must be written in Java, C#, Python or Ruby by the engineers who own the product.
- The browser matrix includes real Safari, real Edge or specific older versions a customer actually runs.
- The environment under test lives inside a network that no external service can reach.
- Longevity matters more than convenience — an open standard outlives any vendor’s product decisions.
Prefer a newer tool when
- The suite is new, written in TypeScript, and nothing forces a language or browser constraint.
- Flakiness is the current pain, since built-in actionability checks remove its largest cause.
- You want traces, videos and network logs from CI without assembling that yourself.
- A test needs two isolated users or several origins in one case without extra machinery.
Anti-patterns worth naming
- A global implicit wait combined with explicit waits, which produces unpredictable timeouts the documentation itself warns about.
- Long XPath expressions generated by a browser tool, which break on the next redesign and explain nothing when they do.
- A page object layer that grows into a second application, with inheritance, conditionals and business logic inside it.
- Sharing one browser session across tests to save startup time, which brings back exactly the order dependency you were avoiding.
- Treating a grid as a way to hide a slow suite, so a forty-minute run becomes a nightly report nobody reads.
When to use it
Use it when
- Teams that must write tests in Java, C#, Python or Ruby, where an official binding is a hard requirement.
- A browser matrix with real Safari, real Edge or specific older versions that customers are pinned to.
- Environments inside a private network, where a self-hosted grid is the only way to reach the system under test.
- Long-lived suites where standing on an open standard is worth more than the convenience of a newer API.
Avoid it when
- A new TypeScript suite with no language or browser constraint, where a modern runner does more for less effort.
- Teams without the appetite to write waiting, reporting and isolation themselves, since the protocol supplies none of it.
- Suites where per-test setup cost dominates, because a session is a browser launch rather than a cheap context.
- Anywhere an implicit wait has been left in place, until it is removed — no other change will make the suite predictable.
Found this useful?
Share it with someone who is working on the same problem.