Outdated Selenium patterns and their current replacements
Selenium 2 and 3 idioms that coding agents and old tutorials still produce, with the current form of each.
A large share of Selenium code is now written with the help of a coding agent, and much of it is wrong in the same handful of ways.
The reason is the training data. Models learned Selenium from more than a decade of blog posts, forum answers, and tutorials, and most of that material describes Selenium 2 and Selenium 3. Those APIs were removed years ago. Worse, some of the most-repeated patterns in that material — sleeping to wait for the page, downloading driver binaries by hand, copying XPath out of DevTools — were never good practice even when they compiled. An agent will reproduce all of it, confidently, and with a plausible explanation attached.
None of this is a reason to keep agents away from your test suite. It is a reason to give an agent what you would give a new team member: the version you are on, the place the current documentation lives, and the conventions the project expects. This page shows how to do that, and what specifically to correct.
An agent that can read the current docs stops guessing. Point it at these, in this order:
| Resource | Use it for |
|---|---|
| llms.txt | A curated index of this site, for an agent to orient itself. Start here. |
| This documentation | The current behaviour of every feature, in every binding. |
| The examples directory | Real, runnable code. |
| API docs for Java, Python, .NET, Ruby, and JavaScript | Checking whether a method actually exists, and what it takes. |
| The Selenium changelogs | What changed between your version and whatever the model remembers. |
llms.txt follows the
llmstxt.org convention:
a single plain-text file listing the documentation in a sensible reading order,
so an agent can find the right page without crawling the site or guessing at URLs.
It is worth pointing at explicitly, because it is curated rather than exhaustive.
The Selenium 2 and 3 era legacy documentation
and the CDP pages are deliberately
kept out of the main index —
both are accurate, and both are the worst possible input for a model about to write new code.
The examples directory deserves particular attention. Every code tab on this site is a link into that repository, and the code in it is executed against current Selenium releases. It is the single most reliable body of Selenium example code to point an agent at, because unlike a blog post it cannot silently rot.
A useful instruction to give an agent is a negative one: if an API cannot be found in the current documentation or API reference, it does not exist, however familiar it looks.
Agents follow conventions far more reliably when the conventions are written in a file
rather than repeated in chat.
Add something like the following to your project’s AGENTS.md, CLAUDE.md, or rules file,
and adjust the version and commands to match your project.
## Browser automation
This project uses Selenium 4.49.0.
### Docs
- Index: https://www.selenium.dev/llms.txt
- Reference: https://www.selenium.dev/documentation/
- Examples: https://github.com/SeleniumHQ/seleniumhq.github.io/tree/trunk/examples
- Fetch the relevant page before using an API you are not certain about.
- Do not use the legacy or CDP pages as a basis for new code.
- Do not use APIs from Selenium 3 or earlier. If an API is not in the
current documentation or API reference, it does not exist.
### Drivers and browsers
- Selenium Manager downloads and caches the drivers. Do not add a
driver-manager dependency, do not download drivers, and do not set
a path to a driver binary.
- Browser configuration goes in that browser's Options class. Do not
use DesiredCapabilities and do not pass raw capability maps.
- Headless is a browser argument: `--headless=new`.
### Waiting
- Never sleep in a test.
- Use an explicit wait, and wait for the condition the next line
actually depends on.
- Do not set an implicit wait and also use explicit waits in the
same session.
- When a test is flaky, find the condition that was not yet true.
Do not increase a timeout.
### Locators
- Prefer `id` and `name`, then a CSS selector on a stable attribute
such as `data-test`.
- Do not write absolute XPath and do not use generated class names.
- Declare locators separately from the code that finds the element.
### Sessions
- One fresh session per test. Always quit the driver in teardown -
`quit`, not `close`.
- Do not share a driver between tests or hold one in a global.
### Events and network
- Use WebDriver BiDi for console logs, JavaScript errors, and network
interception. Do not use the Chrome DevTools Protocol.
### Running tests
- All tests: <your command>
- A single test: <your command>
- If you are unsure whether a locator or a flow works, write a
throwaway script and run it against the application instead of
guessing.
Then add the rules for your binding. These are the removals and replacements that models get wrong most often.
java.time.Duration, not a (long, TimeUnit) pair.findElementBy* and findElementsBy* helpers were removed.
Use driver.findElement(By.id("...")).DesiredCapabilities is replaced by ChromeOptions, FirefoxOptions, and so on.merge returns a new object rather than mutating the caller; assign the result.setHeadless(true) was removed. Use addArguments("--headless=new").Browser enum rather than the older BrowserType interface.find_element_by_id and every other find_element_by_* method was removed in 4.3.
Use driver.find_element(By.ID, "...").executable_path and desired_capabilities keyword arguments were removed in 4.10.
Pass service= and options= instead.options.headless = True was removed. Use options.add_argument("--headless=new").driver.switch_to_alert() was removed. Use driver.switch_to.alert.webdriver-manager or call ChromeDriverManager().install().AddAdditionalCapability is replaced by AddAdditionalOption.DesiredCapabilities is replaced by ChromeOptions, FirefoxOptions, and so on.ChromeDriver constructor.Close() closes one window; Quit() ends the session. Teardown needs Quit().:desired_capabilities was removed. Pass options: instead.Selenium::WebDriver::Chrome.driver_path= is unnecessary.options.add_argument('--headless=new').webdrivers gem. Selenium Manager is built in.driver.quit().new Builder().forBrowser(Browser.CHROME).build().driver.wait(until.elementLocated(...), timeout).webdriver-manager npm package is a Protractor-era tool and is unrelated to Selenium.
Do not install it.java.time.Duration.findElementBy* helpers were removed. Use driver.findElement(By.id("...")).DesiredCapabilities is replaced by ChromeOptions, FirefoxOptions, and so on.setHeadless(true) was removed. Use addArguments("--headless=new").The rules above are short on purpose. This section is the reasoning behind them, and it is worth reading so you can recognise the bad output when you see it.
The most common single artefact of stale training data is driver management:
a third-party driver manager, a hard-coded path to chromedriver, or a PATH setup step.
Since 4.6, Selenium Manager discovers the installed browser,
resolves the matching driver, downloads it, and caches it —
and since 4.11 it will download the browser too, if there isn’t one.
Starting a session takes no path and no extra dependency.
When an agent adds a driver manager, it isn’t adding redundancy. It is adding a second, slower, less reliable source of truth for which driver version to use.
DesiredCapabilities and free-form capability maps predate the W3C WebDriver standard.
Non-standard capabilities now have to be vendor-prefixed, and unprefixed ones can fail
the session outright.
The browser Options classes produce a valid
payload by construction, which is why they are the only supported route.
The upgrade guide
has before-and-after code for every binding.
Sleeping is the most-repeated pattern in the training data and the most damaging. A fixed sleep is either too short, and the test fails, or too long, and the suite crawls. It is the main cause of flaky Selenium tests.
Use an explicit wait for the condition that the next line depends on — element clickable, text present, spinner gone — not for a duration. And do not mix implicit and explicit waits in the same session: as the waits documentation explains, the two combine unpredictably, so a 10-second implicit wait and a 15-second explicit wait can time out after 20.
Watch for the second-order version of this mistake: when a test fails intermittently, an agent will very often “fix” it by raising a timeout or adding a sleep. That hides the race rather than removing it. Insist that it identify which condition was not yet true.
Agents are fond of absolute XPath and generated class names,
partly because so much tutorial code contains them.
Both break on the next front-end change.
Prefer id and name, then CSS on a stable attribute.
See Locator strategies and the
locator suggestions
in our encouraged test practices.
This is also the place where an agent is most likely to invent something. It cannot see your application, so unless it has actually loaded the page it is pattern-matching on what a login form usually looks like. Giving it a way to check — see below — matters more here than anywhere else.
For console logs, JavaScript errors, and network interception, models reach for the Chrome DevTools Protocol, because that is what the last few years of examples used. CDP is Chromium-only and has no stable API between browser versions. WebDriver BiDi is the W3C standard that replaces it, and it works across browsers. CDP support in Selenium exists as a stopgap, not as the recommended path.
Agents frequently produce Grid 3 invocations —
-role hub, -role node, and a JSON node config file.
Grid 4 uses subcommands:
java -jar selenium-server-<version>.jar standalone, or hub and node.
See Grid getting started.
An agent that can only write code is guessing about your application. An agent that can open it can check.
The lightest-weight option needs nothing new: tell the agent it may write a throwaway Selenium script, run it, and print what it finds. This is genuinely effective — it can navigate to the page, dump the candidate elements, try a locator, and report back — and it exercises the same stack the real test will use, which no external tool can claim.
There are also community-maintained Model Context Protocol servers that expose Selenium as a set of tools an agent can call directly, so it can open a browser, click, type, and take screenshots inside a conversation. These are third-party projects rather than part of the Selenium project, so evaluate them as you would any other dependency.
Either way, the rule to write down is the same: verify locators against the running application instead of inferring them.
Selenium’s exceptions are specific, and an agent that is handed the real exception — rather than “the test failed” — will usually fix the right thing.
NoSuchElementException, StaleElementReferenceException,
ElementClickInterceptedException, SessionNotCreatedException, and the rest.
Pointing an agent at the page for the exception it just hit is a short path to a correct fix.ElementClickInterceptedException — a cookie banner, an overlay — than any stack trace.Selenium 2 and 3 idioms that coding agents and old tutorials still produce, with the current form of each.
Learn more or view the full list of sponsors.