Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Most Selenium “wrong element” clicks have one of two causes: your locator matched a different node than intended, or Selenium found the right node but another element covered its click point. Diagnose which case you have before changing code. A third, closely related problem is timing: JavaScript may still be showing an overlay, replacing the node, or enabling the control after the page reports that loading is complete.
What Selenium is actually clicking
WebDriver’s normal element click is not a request to run an arbitrary JavaScript handler. Selenium scrolls the element into view when needed and targets its in-view center. If another element occupies that point, the browser can reject the action with ElementClickInterceptedException. Sticky navigation bars, cookie banners, modal dialogs, loading masks, newsletter prompts, chat launchers and unfinished animations are common blockers.
A different failure occurs when a valid locator matches the wrong node. For example, a selector may return a hidden duplicate, a table cell instead of the input inside it, or the first of several “Save” buttons. In that case Selenium may click successfully—but not the control you meant.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Document-ready or navigation completion is not the same as application readiness. Frameworks often render a shell first, then fetch data, replace nodes, animate panels or enable buttons. Treat the click as a state transition that needs an explicit, observable condition.
Diagnose the failure before changing the fix
Check the page, window and frame
- Confirm
driver.current_urlis the expected URL. - Make sure you are in the intended tab or window. Switch with
driver.switch_to.window(handle)when a new tab opened. - Reset and select the correct iframe before locating controls:
driver.switch_to.default_content(), thendriver.switch_to.frame(...). - After navigation or a frame change, treat previously stored
WebElementobjects as suspect and locate them again.
Count and inspect locator matches
Use a broad diagnostic query temporarily, then print each match’s tag, text, key attributes and visibility:
#1 Best Overall
from selenium.webdriver.common.by import By
matches = driver.find_elements(By.CSS_SELECTOR, "button[data-action='save']")
print("matches:", len(matches))
for i, element in enumerate(matches):
print(i, element.tag_name, repr(element.text),
element.get_attribute("outerHTML")[:300],
"displayed=", element.is_displayed(),
"enabled=", element.is_enabled())
One visible, enabled match is a useful starting point—not proof that its center is unobstructed. If there are duplicates, constrain the selector with a stable attribute, the correct parent container, or the actionable child element.
Read the exception literally
An intercepted-click message often identifies the “other element” that would receive the click. Inspect that element’s class, text and position. A modal, spinner or banner named there points to an obstruction fix; a click on an unexpected sibling points to a locator fix.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Use a unique, actionable locator
Prefer attributes intended for automation, such as a stable data-testid or data-action, over brittle generated class names. Scope the selector to the component that owns the action and select the control itself:
save = driver.find_element(
By.CSS_SELECTOR,
"form[data-testid='profile-form'] button[data-action='save']"
)
For a label-and-input pair, do not click the label’s container if the input is the real target. For repeated rows, identify the row by a stable value and then find its button:
row = driver.find_element(
By.XPATH, "//tr[.//td[@data-column='email' and normalize-space()='[email protected]']]"
)
row.find_element(By.CSS_SELECTOR, "button[data-action='edit']").click()
XPath text matching can be useful when text is stable, but exact text often changes with localization or whitespace. Avoid positional selectors such as :nth-child(3) unless the order is part of the application contract.
Rank #2
Wait for the target and the obstruction
element_to_be_clickable means the element is visible and enabled. It does not guarantee that a different element is not covering its center. Wait for known overlays separately, then reacquire the target immediately before clicking:
Recommended Free Tools
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)
target_locator = (By.CSS_SELECTOR, "button[data-action='save']")
overlay_locator = (By.CSS_SELECTOR, ".loading-overlay")
wait.until(EC.invisibility_of_element_located(overlay_locator))
wait.until(EC.element_to_be_clickable(target_locator))
button = driver.find_element(*target_locator) # fresh reference
button.click()
wait.until(EC.visibility_of_element_located(
(By.CSS_SELECTOR, "[data-testid='save-confirmation']")
))
Replace selectors and the post-click condition with the site’s actual DOM. If a consent dialog must be dismissed rather than waited out, wait for its button, click it, then wait for the dialog to become invisible. If the overlay is optional and may not exist, Selenium’s invisibility condition can still succeed when no matching element is present.
Do not use a blind sleep as the main strategy
time.sleep(3) may be too short on a slow run and waste time on a fast run. Condition-based waits synchronize with the state you need. Also avoid routinely mixing implicit and explicit waits: their polling and timeout interactions can produce unpredictable delays. Choose explicit waits for this workflow and keep the implicit wait at its default unless you have a deliberate, tested policy.
Handle scrolling, sticky headers and animations
Selenium may scroll an out-of-viewport element into view, but a fixed header can then cover the newly positioned center. Scroll the target to a deliberate location and allow the layout to settle:
Rank #3
button = wait.until(EC.presence_of_element_located(target_locator))
driver.execute_script(
"arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});",
button,
)
wait.until(EC.element_to_be_clickable(target_locator))
driver.find_element(*target_locator).click()
If the site exposes an animation-complete class or removes a transition overlay, wait for that concrete state instead of adding a delay. You can inspect the target’s rectangle while debugging:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
rect = driver.execute_script("""
const r = arguments[0].getBoundingClientRect();
return {left:r.left, top:r.top, width:r.width, height:r.height,
centerX:r.left+r.width/2, centerY:r.top+r.height/2};
""", button)
print(rect)
Do not “fix” an intercepted click by defaulting to JavaScript arguments[0].click(). That bypasses the browser’s hit-testing and can hide a real usability defect; use it only when you intentionally need a DOM-level event and have verified that behavior is equivalent.
Recover from rerenders and stale elements
A modern page can replace a node after you locate it. The old reference may raise StaleElementReferenceException, or a replacement may make an earlier reference unsuitable. Keep locators as the durable identity and reacquire the element after navigation, frame changes, waits that trigger rerendering, or any action that updates the component.
button_locator = (By.CSS_SELECTOR, "button[data-action='save']")
wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR, ".loading-overlay")))
wait.until(EC.element_to_be_clickable(button_locator))
driver.find_element(*button_locator).click()
After the click, wait for the result that proves the application handled it: a URL change, a confirmation element, an error message, or a state attribute. A successful WebDriver command only means the input event was delivered; it does not mean the asynchronous operation finished.
Rank #4
A complete troubleshooting decision guide
| Symptom | Likely cause | Targeted action |
|---|---|---|
| A sibling, hidden duplicate or wrong row is activated | Ambiguous locator | Count matches, inspect them, and scope to a stable parent and actionable child. |
ElementClickInterceptedException names another element |
Overlay, modal, banner or animation covers the center | Dismiss it or wait for its invisibility; then perform a normal click. |
| Failure is intermittent after page activity | JavaScript state transition or rerender | Wait for the actual state and reacquire the element immediately before clicking. |
| It works only after manual scrolling | Fixed header or viewport placement | Scroll to a clear position and verify the center is not covered. |
NoSuchElementException or a control is missing |
Wrong page, frame, window, late appearance or changed locator | Verify context, switch correctly, wait for presence and update the selector. |
StaleElementReferenceException |
DOM replacement or navigation | Discard the object and locate it again from its locator. |
Build a maintainable click helper
Centralize the policy so tests remain readable. The helper below waits for a known obstruction, scrolls, reacquires and optionally waits for a post-click condition:
Free tools Windows power users keep installed
One-click scans. No signup required.
from selenium.common.exceptions import ElementClickInterceptedException
from selenium.webdriver.support import expected_conditions as EC
def click_reliably(driver, target, *, overlay=None, timeout=10,
after=None):
wait = WebDriverWait(driver, timeout)
if overlay is not None:
wait.until(EC.invisibility_of_element_located(overlay))
element = wait.until(EC.presence_of_element_located(target))
driver.execute_script(
"arguments[0].scrollIntoView({block:'center'});", element)
wait.until(EC.element_to_be_clickable(target))
try:
driver.find_element(*target).click()
except ElementClickInterceptedException:
# Capture diagnostics here; do not silently fall back to JavaScript.
raise
if after is not None:
wait.until(after)
click_reliably(
driver,
(By.CSS_SELECTOR, "button[data-action='save']"),
overlay=(By.CSS_SELECTOR, ".loading-overlay"),
after=EC.visibility_of_element_located(
(By.CSS_SELECTOR, "[data-testid='save-confirmation']")
),
)
Keep the timeout appropriate to the application and environment; the ten seconds shown is an example, not a performance guarantee. Log the URL, frame, window, locator, match count and obstruction details when a helper fails.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is a clean image or PDF rather than interactive browser testing, ScreenshotNeo provides a single-request screenshot API. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
See the ScreenshotNeo API documentation for all options. A cURL request:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
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(`${res.status} ${res.statusText}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
There is a free plan of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Sign up free for ScreenshotNeo.
Best Value
FAQ
Why does element_to_be_clickable still end in an intercepted click?
It checks visibility and enabled status only. A modal, header or other element can still cover the target’s center, so wait for that specific obstruction as well.
Should I click with JavaScript instead?
Usually no. A JavaScript click bypasses normal hit-testing and can conceal the overlay or layout problem that a real user would encounter.
What should I do after switching frames?
Switch into the correct frame before locating the control, and reacquire any element references created in the previous context.
Is the ten-second wait mandatory?
No. It is an illustrative timeout. Set it from the application’s normal state-transition time and your test environment, then keep the condition specific.
Frequently Asked Questions
Why does element_to_be_clickable still end in an intercepted click?
It checks visibility and enabled status only. A modal, header or other element can still cover the target’s center, so wait for that specific obstruction as well.
Should I click with JavaScript instead?
Usually no. A JavaScript click bypasses normal hit-testing and can conceal the overlay or layout problem that a real user would encounter.
What should I do after switching frames?
Switch into the correct frame before locating the control, and reacquire any element references created in the previous context.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

