ScreenshotNeo

BlogHow-to

How to Fix a Flaky Selenium Test Suite

Diagnose Selenium test failures by their pattern, then fix synchronization, shared state, or browser and driver issues with practical examples.

By the ScreenshotNeo team4 October 20269 min read

A flaky Selenium test passes sometimes and fails at other times because the test, browser, application, or test environment reaches an unexpected state. Start by preserving the first failure and identifying its pattern. Then fix the cause it points to: wait for the application state you need, isolate test data and browser state, or investigate a browser or driver difference. Increasing every timeout or adding retries can hide the evidence without fixing the cause.

Selenium’s troubleshooting guide says poor synchronization is its most common Selenium-related error cause. Its waiting guide also explains that navigation reaching a document ready state does not guarantee that JavaScript-driven application updates are finished. A browser command can therefore race an update and fail intermittently. Selenium troubleshooting · Selenium waits.

1. Capture the failure before changing the test

Record enough context to compare a failure with a pass. The first failure is especially useful because retries and later test activity can change browser state.

  • Test name, exact failed command, full exception, and relevant stack trace.
  • Browser name and version, driver details, Selenium version, and operating system.
  • Whether it fails running alone, only after another test, only in a parallel run, or only in CI.
  • The current URL and relevant application state, plus browser and driver logs when available.
  • Whether the same operation behaves differently in another browser.

Selenium’s troubleshooting guidance recommends using command logging and comparing browser behavior when investigating failures. The classifications below are clues, not proof that a given exception always has one cause.

Failure pattern Likely area to investigate Useful next step
Element missing, not clickable, or stale near an update Synchronization, locator timing, or a changing page Wait for the relevant element or state transition, then locate or interact with the element.
Passes alone but fails after another test Shared data, cookies, storage, or incomplete cleanup Run independently with fresh test data and a test-owned driver.
Fails only in parallel Shared accounts, files, records, ports, or other resources Give each test isolated resources and compare with a sequential run.
Fails consistently in one browser or driver combination Browser-specific behavior, driver behavior, or version differences Reproduce the same operation in another browser and capture version and driver context.
Fails only in CI Environment differences, timing, configuration, or resource contention Compare logs, versions, configuration, and the failing command with a local run.

2. Fix synchronization by waiting for the condition

A page load event is not an application-ready signal. After navigation, JavaScript may still render a control, replace an element, or change whether it is visible. Wait for the condition required by the next action or assertion.

For example, this Python test waits for a submit button to become clickable and then waits for a success message after the click. It uses Selenium’s explicit wait API; replace the example URL and selectors with those from your application.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait


def test_submit_order():
    driver = webdriver.Chrome()
    try:
        driver.get("https://example.com/checkout")
        wait = WebDriverWait(driver, 10)

        submit = wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
        )
        submit.click()

        confirmation = wait.until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, ".order-confirmation"))
        )
        assert "Order received" in confirmation.text
    finally:
        driver.quit()

The 10-second value is an example, not a universal recommendation. Choose a timeout based on the application behavior and suite constraints. An explicit wait polls for a condition and returns when it succeeds or times out. A fixed sleep always consumes its full duration and does not establish that the expected state occurred.

Choose a wait condition that matches the next action

  • presence_of_element_located: the element exists in the DOM; it may not be visible.
  • visibility_of_element_located: the element exists and is visible.
  • element_to_be_clickable: the element is visible and enabled for interaction.
  • url_contains or url_to_be: navigation or a route change is the behavior under test.
  • text_to_be_present_in_element: an existing element should update with expected text.
  • A custom condition: the application exposes a meaningful state that built-in conditions do not represent.

For a stale element after a page update, wait for the update and locate the element again instead of continuing to use a reference to the old DOM node. For a click that triggers navigation, wait for the resulting URL or destination element before asserting. Do not wait for an unrelated condition that merely tends to happen around the same time.

Do not mix implicit and explicit waits

Selenium warns that mixing implicit and explicit waits can produce unpredictable timeout behavior. Keep wait behavior clear. A common approach is to leave the implicit wait at its default and use explicit waits for specific transitions. Avoid setting a global implicit wait and then assuming an explicit wait’s timeout is a simple upper bound.

A long sleep can be useful once as a diagnostic experiment: if it makes the failure disappear, timing is worth investigating. It is not a durable fix. Replace it with a wait for the condition the test actually needs.

3. Make tests independent and short

A test that relies on another test’s setup or leaves browser state behind can change behavior based on order. Each test should arrange the data it needs, exercise one focused behavior, make its assertion, and clean up what it owns.

  • Create unique records or accounts where tests could otherwise collide.
  • Do not depend on a preceding test to log in, create data, or set a preference.
  • Give each test a clear driver lifecycle and call quit() in cleanup, including after assertion failures.
  • Keep end-to-end tests discrete; move checks that do not require a real browser to a lighter test layer.
  • When a failure appears order-dependent, run the test by itself and after the suspected predecessor to confirm the pattern.

Selenium’s guidance on test practices discusses keeping browser coverage focused, while its isolation guidance emphasizes independent tests and proper driver cleanup. These practices also make parallel execution easier to reason about. Selenium test practices · Avoid sharing state.

4. Investigate browser, driver, and CI differences

If failures cluster around one browser or environment, compare evidence before changing infrastructure. Re-run the same short scenario in another browser where practical, record browser and driver versions, and compare local and CI configuration and logs. A consistent browser-specific signature points to a different investigation than an order-dependent failure.

Selenium Manager can help manage drivers for supported Selenium setups, but matching the driver does not remove application races or shared state. If the team needs parallel execution across machines or broad browser coverage, Selenium Grid is designed to route WebDriver commands to remote browser instances. Grid adds execution capacity; it does not repair a test’s wait race or state leak. Selenium Manager · Selenium Grid.

5. Treat retries as diagnostic evidence

A pass on retry confirms that the result was intermittent; it does not identify why. Keep the original failure visible in reporting, record retry counts, and inspect the failing command and environment. A retry policy may be useful operationally in some suites, but a green retry should not erase the signal or be counted as proof that the test is reliable.

Do not adopt a universal retry count or timeout value. The right response depends on the failure pattern and the cost of delaying or misreporting a real regression.

6. Troubleshooting common Selenium failures

Symptom or error Possible cause What to try
NoSuchElementException The element has not rendered yet, the locator is wrong, or the test is on an unexpected page. Confirm the URL and locator, then wait for the element’s required state.
StaleElementReferenceException The DOM changed after the element was located. Wait for the relevant update and locate the element again; do not keep using the stale reference.
TimeoutException The condition never became true, the locator is incorrect, or the chosen timeout is too short for expected behavior. Inspect the page and condition at timeout. Fix the locator or application state assumption before considering a timeout adjustment.
Click intercepted or element not interactable An overlay, animation, hidden state, or layout change prevents interaction. Wait for the overlay to disappear or the target to become interactable; verify that the test targets the intended control.
Works alone, fails in suite State or data is shared, or cleanup is incomplete. Make setup independent, use isolated data, and ensure the driver is quit for every test.
Works locally, fails in CI Browser versions, configuration, timing, resources, or test data differ. Capture versions and logs from both environments and compare the first failing command.
Fails in one browser only Browser or driver behavior may differ, or the application behavior is browser-specific. Reproduce a minimal case across browsers and retain the version context before attributing the issue.

These errors have multiple possible causes. Use the failure context to choose the next experiment rather than treating the table as a one-to-one diagnosis.

7. Or skip the browser setup

If the immediate need is to capture a page image for a report, visual review, or downstream workflow, a screenshot API can handle the browser capture. For Selenium behavior tests, keep the Selenium test and fix its synchronization or isolation; a screenshot does not replace an interactive test.

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));

Cookie and consent banners, newsletter popups, and chat widgets are removed before capture, and each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

8. A practical fix-first checklist

  1. Save the first failure’s command, exception, browser and driver context, and run conditions.
  2. Determine whether it is timing-related, order-dependent, parallel-only, browser-specific, or CI-only.
  3. For timing, replace sleeps and premature commands with explicit waits for the next meaningful state.
  4. Remove mixed implicit and explicit wait behavior.
  5. For order or parallel failures, isolate test data and resources and give each test its own driver lifecycle.
  6. For browser or CI patterns, compare versions, logs, and a minimal reproduction before changing infrastructure.
  7. Keep retry failures visible and use them to find the intermittent condition.

FAQ

Should I increase the timeout for every wait?

No. First inspect whether the condition is correct and whether the test is waiting for the state it needs. A longer timeout can help only when the state is valid but legitimately takes longer; it will not fix a wrong locator, shared state, or a condition that never occurs.

Are fixed sleeps ever appropriate?

They can help briefly test a synchronization hypothesis. In a normal test path, a condition-based wait gives clearer behavior and avoids sleeping after the condition is already satisfied.

Does running tests on Selenium Grid fix flaky tests?

Grid enables remote and distributed browser execution. Use it when that coverage or execution model is needed; diagnose synchronization and isolation separately.

When should a check move out of Selenium?

When the behavior can be verified without a real browser, use a lighter test layer and reserve Selenium for browser behavior that needs it. This keeps end-to-end scenarios focused.