ScreenshotNeo

BlogHow-to

How to Troubleshoot Selenium Test Failures in pytest

Find the failing Selenium command, classify the exception, and fix pytest browser tests with targeted waits, reliable fixtures, and useful evidence.

By the ScreenshotNeo team4 October 202612 min read

When a Selenium test fails in pytest, first rerun just that test and find the first failing WebDriver command in the traceback. Record whether failure occurs while creating the browser session, navigating, locating or interacting with an element, asserting a result, or cleaning up. Then check the smallest likely cause: synchronization, locator or browsing context, browser and driver setup, or test and fixture isolation.

Selenium says poor synchronization is its most common error source, but it also cautions that browser-driver problems can surface as Selenium errors. A test that passes once after a change is not proof of a fix: reproduce the narrow failure and verify it again. See Selenium’s troubleshooting guidance.

1. Reproduce one failure and identify its stage

Run the failing test by file and test name. Pytest’s :: selector targets one test function:

pytest -vv -ra --tb=long tests/test_checkout.py::test_order_confirmation

Use your project’s actual path and test name. -vv increases verbosity, -ra reports skips and other non-pass outcomes, and --tb=long keeps a detailed traceback. If output is still abbreviated by project configuration, add --full-trace. Preserve the first WebDriver exception and the command that raised it; a later quit() or fixture teardown error may be secondary.

Failure stage Evidence to collect First direction to investigate
Driver creation or session startup Exception before navigation, browser and Selenium versions, local or remote configuration Browser availability, driver discovery, compatibility, permissions, capabilities, remote endpoint
Navigation URL, page-load exception, current URL and title if available Navigation timeout, redirects, application response, browser-specific behavior
Lookup or interaction Locator, active window/frame, exception, expected page state Wrong context or locator; asynchronous render; hidden, disabled, covered, or stale element
Assertion Expected and actual values, relevant page state Application behavior, assertion target, test data or state
Teardown or suite-only failure Fixture scope, test order, shared state, cleanup traceback Driver lifecycle, leaked state, another test affecting this one

Record the exact test selector and command, full traceback, Python and Selenium versions, browser and browser version, whether it passes alone, whether it fails repeatedly, and whether it uses local or remote WebDriver. This evidence makes it possible to compare runs instead of guessing from one intermittent result.

2. Fix timing with a wait for the required state

A successful driver.get() does not guarantee that client-side JavaScript has finished rendering the element or state needed by the next command. Navigation waits for a page readiness state, while scripts may continue changing the page afterward. This race is a documented cause of flaky tests. Use an explicit wait for the condition your next action needs: Selenium waiting strategies.

Install Selenium in the project’s virtual environment if needed with python -m pip install selenium. This complete example opens a page, waits for a visible form field, submits it, waits for the result, checks it, and always closes the browser.

# tests/test_web_form.py
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_web_form_submission():
    driver = webdriver.Chrome()
    try:
        wait = WebDriverWait(driver, timeout=10)
        driver.get("https://www.selenium.dev/selenium/web/web-form.html")

        field = wait.until(
            EC.visibility_of_element_located((By.NAME, "my-text"))
        )
        field.send_keys("Selenium")
        wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "button"))
        ).click()

        message = wait.until(
            EC.visibility_of_element_located((By.ID, "message"))
        )
        assert message.text == "Received!"
    finally:
        driver.quit()

Run it with pytest -vv -ra --tb=long tests/test_web_form.py::test_web_form_submission. The example creates a driver directly so it is runnable without a project-specific fixture. In a real test suite, a fixture usually gives browser ownership a single, explicit lifecycle.

Choose the condition that matches the next action

What the test needs Typical condition What it establishes
An asynchronously inserted node presence_of_element_located The node exists in the DOM; it might still be hidden.
Text or a result the user can see visibility_of_element_located The element exists and is displayed.
A control to click element_to_be_clickable The element is visible and enabled according to Selenium’s condition.
A changed value or application state text_to_be_present_in_element, url_contains, or a focused custom predicate The particular observable result has appeared.
An element to disappear invisibility_of_element_located The matching element is absent or invisible.

Import conditions from selenium.webdriver.support.expected_conditions (commonly aliased EC) and pass a locator tuple such as (By.ID, "result"). A wait returns the condition’s successful value, often the element itself, so use the return value directly when practical. Presence alone does not prove visibility or clickability. A clickable condition also cannot guarantee that an overlay will not intercept the click a moment later; inspect the page state if that remains a problem.

For a condition Selenium does not provide, use a short predicate:

wait.until(lambda d: d.find_element(By.ID, "status").text == "Saved")

Keep the predicate safe to call repeatedly. If it raises an exception the wait does not ignore, it may fail immediately rather than continue polling. Selenium’s WebDriverWait supports a timeout, polling frequency, and ignored exceptions; customize these only for a condition where retrying that exception is appropriate.

Use sleeps only as a short diagnostic

A temporary time.sleep(2) can help test the timing hypothesis: if the same test starts passing, the page may not have been ready when the command ran. But a fixed delay can still be too short on a slow run and unnecessarily long on a fast one. Replace the diagnostic sleep with a wait for the actual state.

Avoid casually combining implicit and explicit waits. An implicit wait applies globally to element lookups, while an explicit wait polls a chosen condition; Selenium warns that combining them can make total wait duration unpredictable. If a test suite relies on explicit waits, keep the implicit wait at its default of zero unless you have deliberately chosen and documented another strategy.

3. Check locators, page state, and browsing context

For NoSuchElementException, ask these questions in order:

  1. Is the browser on the expected URL and page state? Did the prior click, navigation, or submission actually happen?
  2. Does the locator match the current DOM? Check its type and value: an XPath passed as a CSS selector, or a CSS selector passed as an ID, will not work as intended.
  3. Is the target inside an iframe or another window? Switch to the correct frame or window before locating it.
  4. Is it rendered asynchronously or only after an interaction? Wait for the appropriate expected state after the action.
  5. Did a page update replace the element after it was located? Relocate it after the update.

Prefer stable attributes such as an application-owned ID or test attribute over selectors tied to incidental layout. Make the selector specific enough to identify the intended control, and inspect the live page or browser developer tools when the locator appears correct but matches nothing. Selenium’s common error guide describes likely causes for missing elements, invalid selectors, stale references, and interaction errors.

StaleElementReferenceException means a previously found element reference no longer refers to an element in the current DOM or context. After navigation, refresh, rerender, frame switch, or window switch, locate the element again using its locator. Do not reuse an old element object just because an element with the same selector appears again.

For ElementNotInteractableException or a click that does not work, distinguish a node’s presence from being displayed, enabled, in view, and unobstructed. Wait for visibility or clickability as appropriate, verify the locator identifies the actual input or button, and check for an overlay or sticky header. If the target is below the fold, scrolling may help; JavaScript-clicking every element can conceal a real user-facing interaction problem and should not be the default workaround.

4. Separate browser and driver setup from test failures

If failure occurs before the first navigation, troubleshoot session startup before changing page waits or assertions. Check that the chosen browser is installed and usable in the test environment, that its process can launch, and that any configured driver path exists and is executable. For a remote session, confirm the remote URL and capabilities match the Grid or provider configuration.

Modern Selenium Python includes Selenium Manager, which handles browser and driver installation in supported configurations when a driver is instantiated. That means old instructions to download and pin a driver manually may not apply to a current setup. If automatic management does not fit an offline, restricted, or pinned environment, configure the browser and driver deliberately for that environment. See the Selenium Python documentation.

SessionNotCreatedException often points to a browser/driver compatibility or environment setup problem; inspect the nested driver message and versions rather than treating it as a page synchronization problem. Selenium notes that some reported errors originate in the underlying browser driver. As a diagnostic comparison, run the relevant command in another supported browser. If behavior differs, investigate that browser and driver combination, then verify the fix again in the browser that failed.

5. Make pytest fixtures own startup and cleanup

A fixture that yields a driver gives setup and cleanup a clear boundary. This example starts a fresh Chrome session for each test that requests it and calls quit() after the test, including when the test body raises an exception:

# conftest.py
import pytest
from selenium import webdriver


@pytest.fixture
def driver():
    browser = webdriver.Chrome()
    try:
        yield browser
    finally:
        browser.quit()
# tests/test_search.py
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_search_page_title(driver):
    driver.get("https://www.selenium.dev/")
    WebDriverWait(driver, 10).until(
        EC.title_contains("Selenium")
    )
    assert "Selenium" in driver.title

Pytest discovers conftest.py fixtures for tests beneath that directory. Keep a function-scoped browser when isolation is more important than startup cost. Broader fixture scopes can save startup time, but require deliberate resetting of cookies, local storage, windows, navigation, and other session state between tests. Selenium’s Python test guide documents fixture and fresh-driver patterns in its own project setup: Python Testing Guide.

For a suite-only or order-dependent failure, compare the test alone with the suite, then check shared fixtures, test dependencies, parallel execution, and incomplete cleanup. Try a fresh driver to see whether state leaks across tests. Avoid making a flaky test pass by retrying it repeatedly: retries can hide an isolation or synchronization defect without explaining it.

6. Preserve evidence and verify the fix

For each failing run, save:

  • The exact pytest selector and command, full traceback, and first failing WebDriver operation.
  • Python, Selenium, browser, and driver versions; whether the session is local or remote.
  • The URL, locator, active window or frame, and expected application state.
  • Whether the test passes alone, fails only in a suite, or changes behavior across supported browsers.
  • Relevant browser or Selenium command logs when the failure remains unclear.

Change one likely cause at a time. Rerun the narrow test several times under the condition that exposed the problem, then run the relevant surrounding tests. If you changed browser setup, verify startup and navigation. If you changed a wait, confirm it observes the intended state and still fails within a bounded timeout when that state never occurs.

7. Common errors and practical fixes

Error or symptom Likely cause Fix to try
NoSuchElementException Wrong locator or context, prior action did not produce the expected state, or asynchronous element has not appeared Confirm URL, window and frame; validate the locator; wait for the required state.
TimeoutException The wait condition never became true within its timeout Check the locator, condition, context, application state, and whether the expected transition occurred. Increase timeout only when measured load variation justifies it.
ElementNotInteractableException Wrong element, hidden or disabled control, or element not ready for the action Target the correct control and wait for visibility or clickability; inspect overlays and page state.
StaleElementReferenceException The DOM or browsing context changed after the element was found Find the element again after the update, navigation, or context switch.
InvalidSelectorException Malformed selector or selector supplied under the wrong strategy Check CSS versus XPath versus ID usage and validate the selector against the current DOM.
SessionNotCreatedException Browser/driver incompatibility, missing executable, permissions, or session configuration Inspect nested startup details, versions, browser availability, path and permissions, and capabilities.
InvalidSessionIdException or commands fail after cleanup The session ended, for example after quit() or closing the last window Check fixture scope and cleanup ownership; do not reuse a driver after teardown.
Passes alone, fails in suite Shared browser state, fixture scope, ordering, parallel interference, or leaked resources Reproduce in isolation, use a fresh driver, and make state reset and ownership explicit.
Passes in one browser only Browser-specific behavior, configuration, or driver issue Compare versions and configuration, inspect browser-specific behavior, and verify the repair in the original browser.

8. Performance, reliability, and cost tradeoffs

A condition-based wait stops as soon as its condition succeeds, so it avoids both the wasted time of an unnecessarily long sleep and the fragility of a sleep that is too short. Every browser session still has startup and navigation cost; function-scoped fresh drivers improve isolation but add startup work. Reusing a session can reduce that work while increasing the need for explicit state reset. Choose the scope based on the suite’s isolation needs and measure the effect in your own environment.

Keep timeouts bounded and specific. A longer timeout may accommodate known slow operations, but it also delays a genuine failure. Keep implicit waits out of a suite centered on explicit waits so nested timing remains understandable. Remote execution adds network and session variables; capture the remote session details and compare local versus remote behavior when the failure only appears in one environment.

There is no universal timeout value or failure-rate figure that applies to every application. Tune from observed application behavior, keep failure evidence, and avoid suppressing uncertain failures with blanket retries. Selenium and pytest are open-source software; infrastructure costs depend on where and how you run browsers.

9. Capture a page snapshot as debugging evidence

A screenshot can help document what the browser showed at a failure point, but it does not replace the traceback, browser logs, or state assertions. With a local driver, save evidence before cleanup when the test fails:

import pytest


def test_checkout(driver):
    try:
        # Navigate and perform the test steps here.
        assert driver.title == "Order confirmation"
    except Exception:
        driver.save_screenshot("pytest-failure.png")
        raise

For a page snapshot independent of a pytest browser session, ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. A screenshot can preserve the visible page for inspection, though it will not reproduce Selenium’s command history or prove why an assertion failed.

Or skip the browser setup

One GET request returns the screenshot. See the ScreenshotNeo API documentation for the API details.

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}`);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Sign up free for 1,000 screenshots a month, with no card required.

FAQ

Why does my Selenium test fail only sometimes?

A race between the test command and a changing page is a common cause. Reproduce one test, identify the first failing command, and wait for the state that command requires. Also check whether suite order or shared browser state changes the result.

Should I increase every Selenium timeout?

No. First verify the condition and page state. Increase a specific bounded timeout only when the expected operation legitimately takes longer in the relevant environment.

Does Selenium Manager mean I never configure a driver?

It handles browser and driver setup in supported modern Selenium configurations. Restricted, offline, pinned, or unusual environments may still need deliberate manual configuration.

Should I rerun a flaky test until it passes?

A passing retry does not establish that the cause is fixed. Use repeat runs to reproduce and verify, while correcting the synchronization, environment, or isolation issue that the evidence points to.