World’s largest virtual agentic engineering & quality conference
Flaky Selenium tests pass and fail without code changes. Learn what causes flaky tests, how to find and fix them, and how to write stable Selenium tests.

Harita Ravindranath
Author

Himanshu Sheth
Reviewer
Published on: April 8, 2022
Last Updated on: August 17, 2026
A flaky Selenium test passes on one run and fails on the next with no change to the code.
Teams invest heavily in automated tests, backed by continuous testing, CI/CD, and DevOps, to catch bugs early.
Flakiness undoes that investment by turning a green-or-red result into a coin toss.
This guide covers what causes flaky tests in Selenium, how to detect them, how to fix each cause with real code, and how to write stable tests that stay reliable.
TL;DR
A flaky Selenium test returns both pass and fail on unchanged code, so a red build stops meaning a real bug. Which fix works depends on the actual root cause, timing, locators, data, or environment, so the job is to detect the pattern and fix that cause, not to paper over it with reruns.
A flaky Selenium test passes or fails on the same code with no change to the build. It defeats automation because a red build no longer reliably signals a real bug in the application under test (AUT).
Determinism is the property that lets a test detect bugs: it gives the same result whenever the code has not changed.
A flaky test breaks that rule. It passes once, fails the next time, then passes again. That inconsistency defeats the purpose of automation testing.
Flakiness is not a nuisance you can ignore. It hurts in four concrete ways:
Selenium itself is deterministic. Flakiness comes from the test and its surroundings: timing, unstable locators, shared test data, the environment, parallelism, weak assertions, and poor test logic.
The Selenium WebDriver bindings are a thin wrapper over a deterministic protocol, standardized by the Selenium 4 W3C WebDriver protocol. So the randomness enters elsewhere.
The seven usual sources break down like this:
| Cause | Why It Turns a Test Flaky |
|---|---|
| Timing and synchronization | The test acts before an asynchronous element is ready, so it fails intermittently on slow loads. |
| Unstable locators | Locators tied to page structure or auto-generated IDs break on the smallest UI change. |
| Test data and shared state | Shared data means one test changes what another depends on, so the second fails on rerun. |
| Environment and infrastructure | A local box competes for CPU, memory, and network, so a test fails from limits, not a real bug. |
| Parallelism and concurrency | Too many concurrent tests create contention and shared-session clashes the app never expected. |
| Weak assertions | Assertions that only check existence pass or fail on render timing and let real defects slip through. |
| Poor test logic | Large, order-dependent tests and fixed sleeps fluctuate whenever the app is slower than the guess. |
Detect flaky tests by running the suite repeatedly in CI and watching which tests fail intermittently. Failure-frequency analysis ranks tests by how often they fail, so the chronic offenders surface first.
These exceptions are the usual signature of a flaky failure. Each points at a specific cause:
| Exception | What it usually means |
|---|---|
| NoSuchElementException | The element is not in the DOM yet, or the locator is unstable. Almost always a timing or locator problem. |
| StaleElementReferenceException | A cached element reference went stale because the page re-rendered. Re-locate the element through a wait. |
| TimeoutException | The element did not reach the expected state within the wait window. The app was slower than the timeout. |
| ElementClickInterceptedException | Another element (overlay, sticky header) covered the target at click time. |
| InvalidElementStateException | The element was not in a state to accept the action, often a race with a disabled or animating control. |
Each exception traces back to a cause from the previous section, so the fix is almost always better synchronization in Selenium WebDriver or a more stable locator.
A single red run tells you nothing. Rerun the suite many times in CI, ideally daily, and record which tests fail without a code change.
Doing this by hand does not scale. TestMu AI's Test Intelligence surfaces flakiness through failure-frequency analysis, ranking tests by how often they fail across the whole history.
That lets you fix the worst offenders first. Its AI triage then labels each failure as app logic, network, device state, or test script, so you know whether the cause is the test or the environment.
Treat that output as a strong lead to verify, not a final verdict.
Fix flaky Selenium tests at the root cause: replace sleeps with explicit waits, swap brittle XPath for stable locators, isolate test data, and quarantine the rest while you fix them.
Work one test at a time so you find the true root cause and ship a permanent fix. The examples below run against the Selenium Playground.
Replace fixed Thread.sleep calls with an explicit wait that blocks until a specific condition is true. Use Selenium waits over hard-coded pauses.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
driver.get("https://www.testmuai.com/selenium-playground/simple-form-demo");
driver.findElement(By.id("user-message")).sendKeys("Stable Selenium test");
driver.findElement(By.id("showInput")).click();
// Block until the result actually renders, then read it
WebElement output = wait.until(ExpectedConditions
.textToBePresentInElementLocated(By.id("message"), "Stable Selenium test"));We ran this exact flow on the TestMu AI cloud against the Simple Form Demo. The wait holds until the message renders, then the assertion passes deterministically.
Handling stale element exceptions in Selenium works the same way: after an AJAX update, re-locate the element through the wait instead of reusing a cached one:
// Bad: the cached reference goes stale when the DOM re-renders
WebElement el = driver.findElement(By.id("submit"));
el.click(); // StaleElementReferenceException
// Good: re-locate through the wait every time
wait.until(ExpectedConditions.elementToBeClickable(By.id("submit"))).click();Prefer a dedicated test attribute, then a stable ID, then a short relative XPath. Reach for an absolute path only when nothing else exists.
// Best: a dedicated test hook the UI framework will not rename
driver.findElement(By.cssSelector("[data-testid='email']"));
// Good: a stable id
driver.findElement(By.id("user-message"));
// Acceptable: a short relative XPath anchored to a stable attribute
driver.findElement(By.xpath("//input[@name='email']"));Keep one locator per element in a Page Object so a UI change is a one-line fix. For stable patterns, keep our XPath locators cheat sheet and guide to CSS selectors handy.
Give each test the data it needs and tear it down afterward. Generate unique users or records per run instead of sharing a fixed account.
This is what lets a suite survive parallel execution without one test corrupting another. Isolation removes the whole class of shared-state flakiness.
Retry is a stopgap while you fix the root cause. Use IRetryAnalyzer in TestNG to reattempt a failed test a fixed number of times:
public class RetryAnalyzer implements IRetryAnalyzer {
private int count = 0;
private static final int MAX_RETRY = 2;
@Override
public boolean retry(ITestResult result) {
return count++ < MAX_RETRY; // retry a failed test up to twice
}
}
// Attach it to a known-flaky test while you work on the real fix
@Test(retryAnalyzer = RetryAnalyzer.class)
public void checkoutFlow() { /* ... */ }Retry hides intermittent failures, so it buys time but never fixes the cause. Pair it with tracking and remove it once the test is stable.
Move a known-flaky test out of the blocking suite into a separate quarantine group. The gate stays trustworthy while you investigate.
Keep running the quarantine group so it stays visible, and set a rule that tests do not linger there. Quarantine is a holding cell, not a graveyard.
A stable Selenium test is deterministic, isolated, and resilient to timing and DOM change. Build it with stable locators, explicit waits, independent tests, and a consistent environment.
Fixing flaky tests is reactive; writing stable ones is proactive. That is where most of the long-term payoff lives.
A good framework keeps this cheap to maintain. Start from the Page Object Model and keep your test scripts in Selenium small, then build on these habits:
TestMu AI automation cloud cloud targets the two most common causes directly, and removes the environment noise on top:
On Selenium, enable SmartWait or Auto Healing per session, since the two are mutually exclusive. Treat Auto Healing as a maintenance aid, not a correctness guarantee for strict regression.
See the docs to set up SmartWait and enable Auto Healing.
Note: Stop chasing flaky failures across local machines. Run stable Selenium tests on TestMu AI's cloud grid and let Test Intelligence surface your flakiest tests automatically. Start free.
Detect flaky tests by rerunning the suite and ranking failures by frequency. Then fix each root cause: explicit waits for timing, stable locators for brittleness, isolated data for shared state.
Use retry and quarantine only as stopgaps while the real fix lands. Flakiness comes from many directions, so no single change removes it all.
You will not eliminate it completely, but these practices steadily shrink it. Moving to the W3C-compliant Selenium 4 removes another source of randomness.
For a playbook across other frameworks, see our guides on strategies to handle flaky tests and Playwright flaky tests. Then run your first stable build on the TestMu AI cloud grid.
Author
Harita Ravindranath is a Full Stack Developer and Project Manager at Tokhimo Inc., with 7 years of experience in the tech industry. She has completed her graduation in B-tech in Electronics and Communication Engineering. She has 5+ years of hands on expertise in JavaScript based technologies like React.js, Next.js, TypeScript, Node.js, and Express.js, and has led 4 full stack projects from scratch. Harita also brings over 2 years of experience in Quality Engineering, including manual and automation testing using Selenium and Cypress, test strategy creation, and Agile/Scrum based development. With 4+ years in project leadership, she is skilled in managing CI/CD pipelines, cloud platforms, and ensuring high quality releases. Harita has authored 30+ technical blogs on web development and automation testing, and has worked on end to end testing for a major banking application covering UI, API, mobile, visual, and cross browser testing. She believes in building clean, efficient, and maintainable solutions by avoiding over engineering.
Reviewer
Himanshu Sheth is the Director of Marketing (Technical Content) at TestMu AI, with over 8 years of hands-on experience in Selenium, Cypress, and other test automation frameworks. He has authored more than 130 technical blogs for TestMu AI, covering software testing, automation strategy, and CI/CD. At TestMu AI, he leads the technical content efforts across blogs, YouTube, and social media, while closely collaborating with contributors to enhance content quality and product feedback loops. He has done his graduation with a B.E. in Computer Engineering from Mumbai University. Before TestMu AI, Himanshu led engineering teams in embedded software domains at companies like Samsung Research, Motorola, and NXP Semiconductors. He is a core member of DZone and has been a speaker at several unconferences focused on technical writing and software quality.
Did you find this page helpful?
More Related Blogs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance