How to Handle Errors and Exceptions in Selenium with Python
Diagnose common Selenium exceptions in Python, replace brittle sleeps with explicit waits, and recover safely with practical examples and troubleshooting steps.
Selenium exceptions tell you which browser operation failed; they do not always identify the root cause. Read the exception and traceback, check the page and browsing context, then wait for the exact state your next operation needs. Use a narrow try/except only when your code has a safe recovery path.
This guide covers common Selenium WebDriver exceptions in Python, condition-based waits, safe retry patterns, and practical debugging. The examples use Selenium’s Python API. Check the official exception reference, waits guide, and WebDriverWait API for version-specific details.
1. Start with the exception and traceback
Find the last line of the traceback to identify the exception, then locate the WebDriver command that raised it. An exception narrows the search; it does not prove one cause. For example, a missing element can mean a wrong selector, the wrong browsing context, or content that has not appeared yet.
- Record the exception class and message.
- Identify the failing command and locator, if any.
- Check which page, frame, and window are active.
- Decide what state must be true before the next operation: present, visible, clickable, stale, or another condition.
- Wait for that state or correct the underlying selector or flow.
- If the state never arrives, let the failure surface with useful diagnostic context.
2. Common Selenium exceptions and what to check
| Exception | What it indicates | First checks |
|---|---|---|
NoSuchElementException |
The element could not be found. | Check the selector, current page or context, and whether dynamic content has appeared. Wait for presence if the element is expected asynchronously. |
TimeoutException |
A command or wait did not finish within the allowed time. | Find which condition timed out. Check whether the selector, browsing context, or expected transition is correct before increasing the timeout. |
StaleElementReferenceException |
The referenced element is no longer current in the DOM. | After the relevant page change, locate the element again. Do not keep using an old reference. |
ElementClickInterceptedException |
Another element obscured the click target. | Inspect overlays and layout changes. Wait for the obstruction to go away or for the target to become clickable. |
ElementNotInteractableException |
The requested interaction cannot proceed in the element’s current state. | Check visibility, enabled state, and whether the intended interaction is possible now. |
NoSuchWindowException |
The requested window does not exist. | Check the window handle and whether the window remains open. |
UnexpectedAlertPresentException |
An unexpected alert appeared during a command. | Handle the alert or correct the flow that produced it. |
SessionNotCreatedException |
WebDriver could not create a session. | Inspect browser and driver startup, session configuration, and environment-specific startup details. |
These descriptions follow Selenium’s exception reference. Treat them as clues for diagnosis, not a universal recovery policy.
3. Use explicit waits for the state you need
A page can reach its document readyState while JavaScript is still changing the page. Navigation completion therefore does not guarantee that a particular element is ready. A fixed sleep pauses for a predetermined duration; an explicit wait polls for a condition and continues as soon as that condition succeeds.
Choose a condition that matches the next operation:
- Presence: the element is in the DOM; useful when locating is enough.
- Visibility: the element is displayed; useful before reading visible content or interacting.
- Clickability: use the expected clickable condition before clicking.
- Staleness: wait for an old element reference to leave the DOM after a transition.
- Alert presence or text visibility: wait for those specific states where relevant.
Python’s WebDriverWait documents a default polling interval of 0.5 seconds and ignores NoSuchElementException by default while waiting. It offers until and until_not; if the condition does not resolve before the timeout, it raises TimeoutException. These defaults do not guarantee identical timing across sites or browser operations.
Runnable Python example
Install Selenium with python -m pip install selenium, have a supported browser installed, and save this as selenium_wait_example.py. Selenium’s current driver management can obtain a compatible driver when the environment supports it. Update the example URL and selector for the page you control.
import logging
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
URL = "https://example.com"
HEADING = (By.TAG_NAME, "h1")
def main():
driver = webdriver.Chrome()
wait = WebDriverWait(driver, timeout=10)
try:
driver.get(URL)
try:
heading = wait.until(EC.visibility_of_element_located(HEADING))
except TimeoutException:
logger.exception(
"Heading did not become visible; url=%s locator=%r",
driver.current_url,
HEADING,
)
raise
print(heading.text)
finally:
driver.quit()
if __name__ == "__main__":
main()
The nested handler logs context and re-raises because there is no safe alternate action in this example. The outer finally closes the browser even if navigation or the wait fails.
Presence, visibility, and clickability
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
wait = WebDriverWait(driver, 10)
# Exists in the DOM; it may still be hidden.
item = wait.until(EC.presence_of_element_located((By.ID, "result")))
# Displayed and available for reading.
label = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".status")))
# Wait for the expected click state before clicking.
submit = wait.until(EC.element_to_be_clickable((By.ID, "submit")))
submit.click()
Presence alone does not establish visibility or clickability. Pick the weakest condition that is sufficient for the next step, and no weaker.
Wait for a transition, then reacquire stale elements
When an action replaces part of the page, the old element reference can become stale. Wait for the old reference to become stale, then locate the replacement.
from selenium.common.exceptions import TimeoutException, StaleElementReferenceException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
wait = WebDriverWait(driver, 10)
locator = (By.CSS_SELECTOR, "#results")
old_results = wait.until(EC.presence_of_element_located(locator))
try:
driver.find_element(By.ID, "refresh").click()
wait.until(EC.staleness_of(old_results))
new_results = wait.until(EC.visibility_of_element_located(locator))
except (TimeoutException, StaleElementReferenceException):
logger.exception("Results did not transition as expected")
raise
This pattern is appropriate only if the action is expected to replace the old node. If the page updates the same node, wait for a meaningful text or state change instead. Selenium’s expected conditions also include combinations such as all_of, any_of, and none_of; use them when a real workflow requires multiple conditions.
4. Catch exceptions narrowly and recover deliberately
Catch the specific exception around the command expected to raise it. Continue only if there is a defined safe next step. Catching Exception around an entire test can hide the actual failure and leave later actions operating on an unknown page state.
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
optional_banner = (By.ID, "optional-banner")
try:
driver.find_element(*optional_banner).click()
except NoSuchElementException:
# This banner is optional; absence is an expected state for this flow.
logger.info("Optional banner was not present")
For a required element, absence is not a successful outcome. Wait for it or allow the test to fail. Avoid retrying every exception: repeating a click or form submission can create duplicate actions, and persistent locator or context errors will not become correct through repetition.
5. Troubleshooting checklist
| Symptom | Likely direction | Practical fix |
|---|---|---|
| Element lookup fails immediately | Selector mismatch, wrong page/frame, or asynchronous content. | Verify the selector and context. If content is expected later, wait for presence or the state needed next. |
| Wait times out | The condition never became true, or the assumed transition is wrong. | Inspect the locator, current URL, page state, and condition. Increase timeout only when the operation legitimately needs more time. |
| Click is intercepted | An overlay or another element covers the target. | Identify the obstruction and wait for it to disappear; then wait for clickability. |
| Element is not interactable | The element is hidden, disabled, or otherwise not ready for the requested action. | Check visibility and enabled state; wait for the appropriate state or correct the flow. |
| Element became stale | A DOM update invalidated the stored reference. | Wait for the relevant update, then find the element again. |
| Window handle fails | The selected window is closed or the handle is not current. | Inspect available handles and select an existing one. |
| Alert disrupts a command | A browser alert is open unexpectedly. | Wait for and handle the alert, or fix the preceding action that opened it. |
| Session cannot start | Browser startup or session configuration failed. | Read the full startup error and check browser availability, driver setup, and session configuration for the environment. |
6. Reliability, performance, and timeout choices
- Wait for a state, not a guessed duration. Explicit waits can proceed as soon as their condition succeeds; sleeps always consume their set delay and can still be too short.
- Set timeouts to the operation. A short UI transition and a slow external page may need different limits. A larger timeout delays failure; it does not correct a wrong locator or impossible condition.
- Keep ignored exceptions limited. The wait already ignores
NoSuchElementExceptionby default. Ignore additional exceptions only when they are transient for that condition and retrying is safe. - Keep retries bounded and idempotent. A retry can be safe for waiting on a read-only page state. Repeating a purchase, submission, or other state-changing action may not be safe.
- Preserve diagnostic context. Include the operation, locator, current URL, and traceback when reporting a failure. Avoid logging secrets or sensitive page data.
- Close sessions reliably. Use
finallyor a test fixture teardown so failures do not leave browser processes open.
7. Common mistakes
- Using
time.sleep()as the default synchronization strategy instead of expressing the required state. - Treating presence as proof that an element is visible or clickable.
- Reusing a
WebElementafter a page transition that replaced its DOM node. - Increasing every timeout before verifying the locator and browsing context.
- Catching broad exceptions and allowing a test to appear successful despite an unexpected failure.
- Retrying a non-idempotent action without checking whether the first attempt already took effect.
8. Or skip the browser setup
If your task is to capture a page rather than interact with it, ScreenshotNeo provides a website screenshot API and MCP server. A single request returns a PNG, JPEG, WebP, or PDF. See the ScreenshotNeo API documentation for request options.
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,
)
r.raise_for_status()
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}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Cookie banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, and failed loads are never billed. An 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 for 1,000 free screenshots a month.
9. FAQ
Should I catch every Selenium exception?
No. Catch a specific exception only where you have a safe, defined recovery. Let unexpected failures surface with their traceback.
Does a successful page load mean the element is ready?
No. JavaScript can change the page after document loading. Wait for the element state your next command requires.
Should I increase the wait timeout when a wait fails?
Only after checking that the condition and locator are correct and the operation reasonably needs more time. A longer timeout cannot fix an impossible condition.
What is the fastest way to debug a flaky interaction?
Start with the failing command and exception, then verify context and wait for the relevant state. For clicks, inspect overlays and target readiness; for stale references, reacquire after the transition.


