Skip to content

Fix: Selenium Not Working — WebDriver Errors, Element Not Found, and Timeout Issues

FixDevs ·

Quick Answer

How to fix Selenium errors — WebDriverException session not created, NoSuchElementException element not found, StaleElementReferenceException, TimeoutException waiting for element, headless Chrome crashes, and driver version mismatch.

The Error

You try to start a browser and Selenium refuses:

selenium.common.exceptions.WebDriverException: Message: session not created:
This version of ChromeDriver only supports Chrome version 120
Current browser version is 124.0.6367.91

Or the element is clearly visible on the page but Selenium can’t find it:

selenium.common.exceptions.NoSuchElementException: Message: no such element:
Unable to locate element: {"method":"css selector","selector":"#login-button"}

Or you find an element, interact with it, and get a stale reference error:

selenium.common.exceptions.StaleElementReferenceException: Message: stale element
reference: stale element not found in the current active document

Or the test times out waiting for a page to load:

selenium.common.exceptions.TimeoutException: Message: Timed out waiting for page load.

Selenium controls a real browser — Chrome, Firefox, Edge — through a WebDriver binary that must match the browser version exactly. The browser renders pages asynchronously, so elements may not exist yet when Selenium looks for them. This guide covers every common failure mode.

Why This Happens

Selenium issues fall into three categories: driver setup (WebDriver version mismatch, missing binary, wrong PATH), timing (elements not yet rendered, stale references after page navigation), and element location (wrong selector, element inside an iframe, element hidden or overlapped).

The timing category causes the most confusion. A browser renders HTML progressively — JavaScript loads asynchronously, AJAX calls fire after page load, and single-page apps rebuild the DOM on navigation. If Selenium tries to find an element before the JavaScript creates it, the element doesn’t exist yet.

Fix 1: WebDriver Version Mismatch — Use Selenium Manager

WebDriverException: session not created: This version of ChromeDriver
only supports Chrome version 120

Selenium 4.6+ includes Selenium Manager — it automatically downloads the correct driver for your installed browser. If you’re on Selenium 4.6+, you don’t need to manage drivers manually:

from selenium import webdriver

# Selenium 4.6+ — driver management is automatic
driver = webdriver.Chrome()   # Downloads matching ChromeDriver automatically
driver.get("https://example.com")
driver.quit()

Check your Selenium version:

pip show selenium
# Version: 4.20.0 — Selenium Manager included

If automatic management fails (corporate proxy, restricted network), install webdriver-manager:

pip install webdriver-manager
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# Manually manage driver version
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)

For Firefox:

from selenium import webdriver

driver = webdriver.Firefox()   # Selenium Manager handles geckodriver

For Edge:

from selenium import webdriver

driver = webdriver.Edge()   # Selenium Manager handles msedgedriver

Common causes of driver errors even with Selenium Manager:

  1. Chrome installed via Snap on Ubuntu — Selenium Manager can’t find the Snap-installed Chrome. Install Chrome via .deb instead
  2. Corporate proxy — Selenium Manager can’t download the driver. Set SE_MANAGER_PROXY env var
  3. Outdated Selenium — versions before 4.6 don’t have Selenium Manager. Upgrade: pip install --upgrade selenium

Fix 2: NoSuchElementException — Element Not Found

NoSuchElementException: Unable to locate element: {"method":"css selector","selector":"#submit-btn"}

The element either doesn’t exist yet (page still loading), is inside an iframe, or the selector is wrong.

Step 1: Use explicit waits instead of find_element directly:

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

driver = webdriver.Chrome()
driver.get("https://example.com")

# WRONG — element may not exist yet
button = driver.find_element(By.ID, "submit-btn")   # NoSuchElementException

# CORRECT — wait up to 10 seconds for the element to appear
wait = WebDriverWait(driver, 10)
button = wait.until(EC.element_to_be_clickable((By.ID, "submit-btn")))
button.click()

Never use time.sleep() for waiting — it’s fragile and slow:

import time

# WRONG — waits 5 seconds even if element appears in 0.5s
time.sleep(5)
button = driver.find_element(By.ID, "submit-btn")

# CORRECT — waits up to 10s, returns as soon as element appears
button = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.ID, "submit-btn"))
)

Common expected conditions:

from selenium.webdriver.support import expected_conditions as EC

# Wait for element to be present in DOM (may be hidden)
EC.presence_of_element_located((By.CSS_SELECTOR, ".modal"))

# Wait for element to be visible (present AND displayed)
EC.visibility_of_element_located((By.ID, "results"))

# Wait for element to be clickable (visible AND enabled)
EC.element_to_be_clickable((By.XPATH, "//button[text()='Submit']"))

# Wait for element to disappear (loading spinner gone)
EC.invisibility_of_element_located((By.CLASS_NAME, "spinner"))

# Wait for text to appear in element
EC.text_to_be_present_in_element((By.ID, "status"), "Complete")

# Wait for URL to change
EC.url_contains("dashboard")

Step 2: Check if the element is inside an iframe:

# Elements inside iframes are invisible to the main page's DOM
# Switch to the iframe first, then find the element

# By iframe element
iframe = driver.find_element(By.TAG_NAME, "iframe")
driver.switch_to.frame(iframe)
button = driver.find_element(By.ID, "submit-btn")   # Now visible
button.click()

# Switch back to main page
driver.switch_to.default_content()

Step 3: Verify your selector in browser DevTools. Open F12 → Console → type:

document.querySelector("#submit-btn")   // CSS selector
document.querySelectorAll(".item")      // All matches

If the selector returns null in DevTools, the selector is wrong — not a Selenium issue.

Fix 3: StaleElementReferenceException — Element Changed

StaleElementReferenceException: stale element not found in the current active document

You found an element, the page refreshed or the DOM changed (AJAX update, SPA navigation), and now the reference points to a deleted DOM node.

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

# WRONG — element goes stale after page navigation
items = driver.find_elements(By.CSS_SELECTOR, ".product")
for item in items:
    item.click()   # StaleElementReferenceException after first click
    driver.back()  # Page reloads, all item references are stale

# CORRECT — re-find elements after each navigation
product_count = len(driver.find_elements(By.CSS_SELECTOR, ".product"))
for i in range(product_count):
    items = driver.find_elements(By.CSS_SELECTOR, ".product")   # Fresh reference
    items[i].click()
    # Process product page...
    driver.back()
    WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, ".product"))
    )

For SPAs that update the DOM without full page loads:

from selenium.common.exceptions import StaleElementReferenceException

def click_with_retry(driver, locator, retries=3):
    for attempt in range(retries):
        try:
            element = WebDriverWait(driver, 10).until(
                EC.element_to_be_clickable(locator)
            )
            element.click()
            return
        except StaleElementReferenceException:
            if attempt == retries - 1:
                raise

Fix 4: Headless Chrome — Running Without a Display

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless=new")   # New headless mode (Chrome 112+)
options.add_argument("--no-sandbox")      # Required in Docker/CI
options.add_argument("--disable-dev-shm-usage")   # Prevent /dev/shm crashes in Docker

driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
print(driver.title)

# Take screenshot for debugging
driver.save_screenshot("debug.png")

driver.quit()

--headless=new vs --headless:

The old --headless flag used a separate rendering path that behaved differently from regular Chrome. --headless=new (Chrome 112+) uses the same rendering engine — fewer compatibility issues and identical behavior to headed mode.

Common headless issues:

options = Options()
options.add_argument("--headless=new")

# Set window size — headless defaults to 800x600 which can hide elements
options.add_argument("--window-size=1920,1080")

# Disable GPU (required on some systems)
options.add_argument("--disable-gpu")

# User-agent — some sites block headless Chrome's default user agent
options.add_argument(
    "user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
    "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
)

For general Python package installation failures when setting up Selenium in CI/CD or Docker, see pip could not build wheels.

Docker setup:

FROM python:3.12-slim

RUN apt-get update && apt-get install -y \
    chromium \
    chromium-driver \
    && rm -rf /var/lib/apt/lists/*

RUN pip install selenium

# Set Chrome binary location for Selenium
ENV CHROME_BIN=/usr/bin/chromium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.binary_location = "/usr/bin/chromium"
options.add_argument("--headless=new")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")

driver = webdriver.Chrome(options=options)

Fix 5: Clicking Elements — Intercepted, Hidden, or Overlapped

ElementClickInterceptedException: element click intercepted:
Element <button> is not clickable at point (x, y).
Other element would receive the click: <div class="overlay">

Another element (modal, cookie banner, overlay) is covering the button.

Fix 1: Close the overlay first:

# Dismiss cookie consent
try:
    cookie_btn = WebDriverWait(driver, 5).until(
        EC.element_to_be_clickable((By.ID, "accept-cookies"))
    )
    cookie_btn.click()
except:
    pass   # No cookie banner present

Fix 2: Scroll the element into view:

from selenium.webdriver.common.action_chains import ActionChains

element = driver.find_element(By.ID, "target-button")

# Scroll into view
driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", element)

# Then click
element.click()

Fix 3: Click via JavaScript (bypasses overlay interception):

element = driver.find_element(By.ID, "target-button")
driver.execute_script("arguments[0].click();", element)

Pro Tip: JavaScript clicks bypass Selenium’s visibility and interception checks entirely. Use them only when the element is genuinely clickable but Selenium’s click detection is wrong (e.g., a sticky header overlapping the element). For testing, prefer native Selenium clicks — they simulate real user behavior more accurately.

Fix 4: Wait for the overlay to disappear:

# Wait for loading overlay to vanish
WebDriverWait(driver, 15).until(
    EC.invisibility_of_element_located((By.CLASS_NAME, "loading-overlay"))
)

# Now click
driver.find_element(By.ID, "target-button").click()

Fix 6: Handling Dropdowns, Alerts, and New Windows

Select dropdowns:

from selenium.webdriver.support.ui import Select

dropdown = Select(driver.find_element(By.ID, "country"))

dropdown.select_by_visible_text("Japan")
dropdown.select_by_value("JP")
dropdown.select_by_index(3)

# Get selected option
print(dropdown.first_selected_option.text)

# Get all options
for option in dropdown.options:
    print(option.text, option.get_attribute("value"))

JavaScript alerts:

# Accept an alert
alert = driver.switch_to.alert
print(alert.text)   # Read alert message
alert.accept()      # Click OK

# Dismiss (click Cancel)
alert.dismiss()

# Type into a prompt
alert.send_keys("my input")
alert.accept()

New tabs/windows:

# Store original window handle
original_window = driver.current_window_handle

# Click link that opens a new tab
driver.find_element(By.LINK_TEXT, "Open in new tab").click()

# Switch to the new tab
WebDriverWait(driver, 10).until(lambda d: len(d.window_handles) > 1)
new_window = [w for w in driver.window_handles if w != original_window][0]
driver.switch_to.window(new_window)

# Do work in new tab...
print(driver.title)

# Close new tab and switch back
driver.close()
driver.switch_to.window(original_window)

Fix 7: Page Load and Network Timeouts

TimeoutException: Message: timeout: Timed out receiving message from renderer

Set page load timeout:

driver.set_page_load_timeout(30)   # Max 30 seconds for page load

try:
    driver.get("https://slow-site.com")
except TimeoutException:
    print("Page took too long to load")
    driver.execute_script("window.stop();")   # Stop loading, work with partial page

Set implicit wait (global default wait for find_element):

driver.implicitly_wait(5)   # Wait up to 5s for any find_element call
# Applied to ALL find_element calls from this point on

Common Mistake: Mixing implicit waits and explicit waits (WebDriverWait). They can interact unpredictably — a 10s implicit wait plus a 10s explicit wait can result in up to 20s total wait time. Use explicit waits only for predictable behavior:

# WRONG — mixing implicit and explicit waits
driver.implicitly_wait(10)
WebDriverWait(driver, 10).until(EC.presence_of_element_located(...))

# CORRECT — explicit waits only, no implicit wait
WebDriverWait(driver, 10).until(EC.presence_of_element_located(...))

Wait for AJAX to complete:

# Wait for jQuery AJAX calls to finish
WebDriverWait(driver, 30).until(
    lambda d: d.execute_script("return jQuery.active == 0")
)

# Wait for network idle (no pending fetch requests)
WebDriverWait(driver, 30).until(
    lambda d: d.execute_script(
        "return window.performance.getEntriesByType('resource')"
        ".filter(r => !r.responseEnd).length === 0"
    )
)

Fix 8: Best Practices for Reliable Selenium Code

Always use a try/finally block or context manager:

from selenium import webdriver

# WRONG — if an error occurs, browser stays open (memory leak)
driver = webdriver.Chrome()
driver.get("https://example.com")
# ... error here, driver never quit

# CORRECT — always quit
driver = webdriver.Chrome()
try:
    driver.get("https://example.com")
    # ... your code
finally:
    driver.quit()   # Closes browser AND frees driver process

Use Page Object Model for maintainable tests:

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

class LoginPage:
    URL = "https://example.com/login"
    USERNAME = (By.ID, "username")
    PASSWORD = (By.ID, "password")
    SUBMIT = (By.CSS_SELECTOR, "button[type='submit']")
    ERROR_MSG = (By.CLASS_NAME, "error-message")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def login(self, username, password):
        self.driver.get(self.URL)
        self.wait.until(EC.visibility_of_element_located(self.USERNAME)).send_keys(username)
        self.driver.find_element(*self.PASSWORD).send_keys(password)
        self.driver.find_element(*self.SUBMIT).click()

    def get_error(self):
        return self.wait.until(
            EC.visibility_of_element_located(self.ERROR_MSG)
        ).text

# Usage
page = LoginPage(driver)
page.login("admin", "wrong_password")
assert "Invalid credentials" in page.get_error()

Still Not Working?

Selenium vs Playwright vs Puppeteer

If you’re starting a new project:

  • Playwright — modern, auto-wait, multi-browser, better async support. Recommended for new projects. See Playwright not working.
  • Puppeteer — Node.js, Chrome-focused, good for scraping. See Puppeteer not working.
  • Selenium — largest ecosystem, supports the most browsers and languages, most community resources.

Anti-Bot Detection

Many sites detect and block Selenium. Signs: immediate CAPTCHA, 403 responses, or empty pages. Selenium sets navigator.webdriver = true by default:

options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)

driver = webdriver.Chrome(options=options)
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")

Note: Bypassing bot detection may violate a site’s terms of service. Use responsibly and only on sites you have authorization to automate.

Selenium Grid for Parallel Tests

# Start Selenium Grid Hub
java -jar selenium-server-4.20.0.jar hub

# Start a node
java -jar selenium-server-4.20.0.jar node --hub http://localhost:4444
from selenium import webdriver

options = webdriver.ChromeOptions()
driver = webdriver.Remote(
    command_executor="http://localhost:4444/wd/hub",
    options=options,
)

For browser testing patterns with other frameworks, see Cypress not working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles