Fix: Selenium Not Working — WebDriver Errors, Element Not Found, and Timeout Issues
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.91Or 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 documentOr 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 120Selenium 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 includedIf automatic management fails (corporate proxy, restricted network), install webdriver-manager:
pip install webdriver-managerfrom 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 geckodriverFor Edge:
from selenium import webdriver
driver = webdriver.Edge() # Selenium Manager handles msedgedriverCommon causes of driver errors even with Selenium Manager:
- Chrome installed via Snap on Ubuntu — Selenium Manager can’t find the Snap-installed Chrome. Install Chrome via
.debinstead - Corporate proxy — Selenium Manager can’t download the driver. Set
SE_MANAGER_PROXYenv var - 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 matchesIf 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 documentYou 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:
raiseFix 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/chromiumfrom 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 presentFix 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 rendererSet 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 pageSet 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 onCommon 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 processUse 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:4444from 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.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Apache Airflow Not Working — DAG Not Found, Task Failures, and Scheduler Issues
How to fix Apache Airflow errors — DAG not appearing in UI, ImportError preventing DAG load, task stuck in running or queued, scheduler not scheduling, XCom too large, connection not found, and database migration errors.
Fix: Dash Not Working — Callback Errors, Pattern Matching, and State Management
How to fix Dash errors — circular dependency in callbacks, pattern matching callback not firing, missing attribute clientside_callback, DataTable filtering not working, clientside JavaScript errors, Input Output State confusion, and async callback delays.
Fix: dbt Not Working — ref() Not Found, Schema Mismatch, and Compilation Errors
How to fix dbt errors — ref() model not found, profile not found, database relation does not exist, incremental model schema mismatch requiring full-refresh, dbt deps failure, Jinja compilation errors, and test failures.
Fix: Gradio Not Working — Share Link, Queue Timeout, and Component Errors
How to fix Gradio errors — share link not working, queue timeout, component not updating, Blocks layout mistakes, flagging permission denied, file upload size limit, and HuggingFace Spaces deployment failures.