Home › Testing › ElementClickInterceptedException: Fix Blocked Clicks
Beginner 5 min · September 23, 2026

ElementClickInterceptedException: Fix Blocked Clicks

Something covers your target: dismiss the overlay, scroll it into view, wait for clickability, and use a JS click only as a last resort..

N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Production
production tested
September 27, 2026
last updated
2,085
articles · all by Naren
Before you start⏱ 9 min
  • ✓A working Selenium script that opens a page and clicks a button
  • ✓Basic CSS selectors to recognize banner and overlay classes
  • ✓Familiarity with running a single pytest test for fast iteration
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • ElementClickInterceptedException means another element sits on top of your target at the click point: banners, modals, sticky headers, or loaders
  • Read the message: it names the covering element's tag and classes, which usually identifies the overlay in one glance
  • Dismiss or wait out the cover first: close cookie banners, wait for spinners to vanish, then scroll the target into view
  • Wait for element_to_be_clickable, and reserve JavaScript clicks for cases where a real click can never land
✦ Definition~90s read
What is Selenium ElementClickInterceptedException?

ElementClickInterceptedException is thrown when Selenium determines the click point of your target is covered by a different element. Unlike a missing element, the target was found and displayed — but at click time, the topmost node at those coordinates belongs to something else: a cookie consent banner, a modal backdrop, a sticky nav bar, an animation mid-flight, or a loading spinner.

★
Imagine tapping a doorbell while someone holds a cushion over it — your finger hits cushion, not bell.

Selenium aborts rather than clicking the wrong thing, and the message quotes the intercepting element's markup so you can identify it.

The usual suspects form a short lineup. Cookie and consent banners dock at the bottom exactly where submit buttons live. Chat widgets and newsletter popups slide in seconds after load, right as your script clicks. Sticky headers and footers overlap content after scrolling positions the target beneath them.

Spinners and skeleton screens briefly cover everything during data fetches. Each one is a normal page behavior colliding with test timing — the page is not broken, and neither is your locator.

The fix order matters: remove or outlast the cover, position the target, prove clickability, and only then consider bypassing the real click. Dismiss banners, wait for invisibility of loaders, scroll the target to the viewport center, and wait for element_to_be_clickable.

A JavaScript click through execute_script skips every check that makes clicks trustworthy, so it sits last — valid for permanently covered controls like hidden file inputs, never as a default. Real clicks test what users can do; bypasses test what the DOM contains.

Plain-English First

Imagine tapping a doorbell while someone holds a cushion over it — your finger hits cushion, not bell. The bell works fine; something is just in the way. ElementClickInterceptedException is Selenium telling you exactly that: it aimed at your button, but a cookie banner, popup, or loading spinner was covering the spot. The fix is rarely about the button at all. You move the cushion first — close the banner, wait for the spinner to finish — and then ring the bell normally.

Your script scrolls to the Buy button and clicks. Selenium refuses: ElementClickInterceptedException, complaining that another element would receive the click. You stare at the screenshot and the button looks perfectly clickable. But one layer up, a cookie banner, a chat widget, or a sticky header is parked over that exact pixel — invisible in a quick glance, decisive in the click math.

Beginners misread this error as a broken locator and rewrite selectors for an hour. The locator is fine; Selenium found the target without trouble. The click failed because real clicks land on the topmost element at a point, and something else owns that point. Every fix that ignores the covering layer — harder scrolls, double clicks, longer sleeps — misses the actual obstacle.

This guide teaches the covering-layer mindset. You will learn to read the exception message for the identity of the blocker, clear the common overlays in order, scroll and wait so the target owns its click point, and recognize the rare cases where a JavaScript click is the honest answer. By the end, blocked clicks become a five-minute diagnosis instead of an afternoon of selector roulette.

The Message Names Your Blocker — Read It First

The exception text is a gift most beginners skip. It says which element would receive the click and quotes part of its markup — tag, classes, sometimes an id. Those classes usually confess the purpose: cookie-consent, chat-widget, newsletter-modal, loading-overlay. One careful read replaces an hour of guessing about locators that were never broken.

Build the habit of copying the quoted fragment into a page-source search. Save driver.page_source around the failure and grep for the quoted classes to find the overlay's full markup, its z-index, and the script that shows it. Check whether it appears on every load or only on fresh profiles, slow networks, or specific regions — each pattern points at a different owner, from consent tooling to marketing popups to loading states.

Treat the message as the diagnosis and everything else as confirmation. When the blocker is a banner you control, the fix is a dismiss step. When it belongs to a third party, the fix is timing or test-environment configuration. Either way the answer was in the first three lines of the traceback. Teams that teach message-first reading watch this exception drop from afternoon-long episodes to ten-minute fixes, and every ticket starts with the blocker's name attached.

📊 Production Insight
A team rewrote selectors for two days while the message clearly quoted a chat-widget div from the start. One engineer finally read it and closed the widget in setup. Rule: quote the intercepting element's classes into the ticket before touching any locator.
🎯 Key Takeaway
The message quotes the covering element's markup — read that before anything else.
Class names usually reveal the overlay's purpose in one glance.
Confirm with page-source search and a failure screenshot.

Consent banners are the number-one blocker because WebDriver starts every run with a clean profile: no cookies, no stored consent, banner every time. Handling them inside individual tests multiplies maintenance by the test count and guarantees someone forgets. The banner belongs in shared setup — a fixture or base-class step that runs before every test, accepts consent when present, and silently continues when absent.

The shape is a short conditional: a brief wait for the accept button, click it if found, then wait for the banner's invisibility. Keep the presence timeout short, around three seconds, so regions without the banner do not pay a delay on every test. Never let banner handling throw when the banner is missing — absence is a valid outcome, and a helper that fails on absence becomes its own flake source.

The snippet shows a consent helper plus a generic popup closer you can reuse for newsletter and promo modals. Call both from your login or navigation fixture so every test inherits a clean viewport. When marketing adds a new popup, you update one helper instead of fifty tests. Shared overlays get shared handling — that single decision removes the largest source of intercepted clicks in most suites.

io_thecodeforge/overlay_setup.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

CONSENT_ACCEPT = (By.ID, "accept-cookies")
CONSENT_BANNER = (By.ID, "cookie-banner")
PROMO_CLOSE = (By.CSS_SELECTOR, ".promo-modal .close")

def accept_consent(driver, timeout=3):
    try:
        accept = WebDriverWait(driver, timeout).until(
            EC.element_to_be_clickable(CONSENT_ACCEPT))
        accept.click()
        WebDriverWait(driver, 10).until(
            EC.invisibility_of_element_located(CONSENT_BANNER))
    except TimeoutException:
        pass  # banner absent in this region: carry on

def close_promo(driver, timeout=3):
    try:
        WebDriverWait(driver, timeout).until(
            EC.element_to_be_clickable(PROMO_CLOSE)).click()
    except TimeoutException:
        pass
📊 Production Insight
Fifty tests each dismissed the banner inline until a redesign changed the button id and all fifty broke at once. Moving dismissal into one fixture reduced the next redesign fix to a single line. Rule: overlays are handled once in setup, never inline in tests.
🎯 Key Takeaway
Fresh profiles mean banners on every run — handle them in shared setup.
Click accept when present, continue silently when absent.
One helper per overlay type beats scattered inline dismissal.

Scroll Into View So the Target Owns Its Point

Selenium auto-scrolls before clicking, but its default scroll can park the target at the viewport edge — exactly where sticky headers and footers live. The explicit fix is scrollIntoView with the block-center option, which centers the element vertically and keeps it clear of docked chrome. After scrolling, always follow with a clickability wait: scrolling triggers lazy loading and sticky repositioning that need a beat to settle.

Sticky elements deserve special attention because they move with the scroll. A header that docks on scroll-down will cover whatever the naive scroll placed at the top edge. Centering dodges most of these, and where it does not, collapse or close the sticky element first. Some teams add a small manual offset scroll after centering for pages with oversized fixed bars — pragmatic and honest as long as it stays in a helper.

The snippet shows the scroll-then-wait-then-click sequence as one reusable helper. Notice the click itself stays a real WebDriver click, preserving every fidelity guarantee. Positioning fixes belong before waiting fixes: a target that owns its point and has settled rarely needs anything stronger. Most intercepted clicks end here, with no JavaScript involved at all.

io_thecodeforge/scroll_click.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def scroll_center(driver, element):
    driver.execute_script(
        "arguments[0].scrollIntoView({block: 'center'});", element)

def scroll_and_click(driver, locator, timeout=10):
    target = driver.find_element(*locator)
    scroll_center(driver, target)
    WebDriverWait(driver, timeout).until(
        EC.element_to_be_clickable(locator))
    driver.find_element(*locator).click()  # fresh find, real click

def dismiss_sticky(driver):
    driver.execute_script(
        "document.querySelectorAll('.sticky-promo').forEach(e => e.remove());")
📊 Production Insight
A suite scrolled targets to the top edge where a sticky nav covered them on every page. Switching the helper to block-center cleared hundreds of failures. Rule: center targets vertically; top-edge scrolling feeds them to sticky headers.
🎯 Key Takeaway
Center the target with scrollIntoView block-center, not the default edge scroll.
Follow every scroll with a clickability wait for lazy content to settle.
Keep the click itself a real WebDriver click to preserve fidelity.

Outlasting Spinners, Animations, and Lazy Overlays

Transient covers need waiting, not dismissal. Spinners, skeleton screens, and slide-in panels appear on schedule and vanish on their own — the test's job is to outlast them deterministically. The tool is invisibility_of_element_located on the loader, followed by element_to_be_clickable on the target. This two-beat wait mirrors what users experience: first the loading ends, then the control becomes usable.

Animations cause a subtler variant where the target itself moves under the click point mid-flight. Carousels, expanding accordions, and smooth-scroll effects shift coordinates between Selenium's position check and the actual click. The clickability wait absorbs short motion, and for longer choreography, waiting on the animation's end state — a settled class or a visible successor element — beats any fixed pause.

Never answer motion with sleep. A one-second pause passes on fast machines and fails on loaded CI runners, creating flakes that track infrastructure noise instead of product behavior. The snippet shows the loader-then-target wait pair plus an animation-settle variant. Name your loaders as locators alongside your targets so every test can outlast them without inventing waits inline. Deterministic waits turn timing from an enemy into a non-event.

io_thecodeforge/outlast_loaders.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

SPINNER = (By.CSS_SELECTOR, ".loading-spinner")
SUBMIT = (By.ID, "submit-order")

def after_loading(driver, target, loader=SPINNER, timeout=15):
    WebDriverWait(driver, timeout).until(
        EC.invisibility_of_element_located(loader))
    return WebDriverWait(driver, timeout).until(
        EC.element_to_be_clickable(target))

def place_order(driver):
    after_loading(driver, SUBMIT).click()

def settle_carousel(driver, timeout=10):
    WebDriverWait(driver, timeout).until(
        EC.attribute_contains((By.ID, "hero"), "class", "settled"))
📊 Production Insight
A checkout test slept two seconds for a spinner that took four on Monday mornings, failing weekly like clockwork. Replacing the sleep with an invisibility wait ended the Monday curse. Rule: waits end when states change, never when clocks run out.
🎯 Key Takeaway
Outlast loaders with invisibility waits, then prove clickability.
Absorb animation with state-based waits, not fixed pauses.
Keep loader locators beside target locators for reuse.

JavaScript Clicks: the Last Resort With a Cost

A JavaScript click through execute_script bypasses every check that makes clicks meaningful: visibility, coveredness, pointer events, and scrolling. It fires the DOM click handler directly, which passes even when no human could ever press the control. That power makes it the right tool in narrow cases and a dangerous default everywhere else — a suite full of JS clicks can go green on a page users cannot operate.

The legitimate cases share one trait: a real click can never land. Hidden file inputs styled through a label, controls permanently beneath a transparent overlay the product team accepts, and canvas-adjacent elements with zero clickable area all qualify. In each case the JS click compensates for a construct outside normal pointer interaction, and the test documents why the bypass exists with a comment naming the reason.

The snippet shows the bypass with its required warning comment, plus the preferred escalation before it: dismiss, scroll, wait, then only bypass. Flag every JS click in review and demand the justification. A codebase where JS clicks need approval stays honest; one where they are the first answer stops testing the user experience and starts testing the DOM. Keep the real click as the default and the script click as the documented exception.

io_thecodeforge/js_click.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

HIDDEN_UPLOAD = (By.ID, "file-input")

def real_click_first(driver, locator, timeout=10):
    element = WebDriverWait(driver, timeout).until(
        EC.element_to_be_clickable(locator))
    element.click()
    return element

def js_click_last_resort(driver, locator):
    # Bypass: input is permanently hidden behind a styled label;
    # no pointer click can ever reach it. Reviewed 2026-09.
    element = driver.find_element(*locator)
    driver.execute_script("arguments[0].click();", element)
⚠ JS Clicks Skip Every User-Facing Check
A script click passes on controls no human could press, hiding real usability bugs. Use it only where a real click can never land, and leave a comment naming the reason for the next reviewer.
📊 Production Insight
A suite used JS clicks everywhere and stayed green for a month while a modal trapped all real users outside the checkout flow. One real-click regression test would have caught it on day one. Rule: every JS click needs a review-approved reason in a comment.
🎯 Key Takeaway
JS clicks bypass visibility and coveredness checks — power with a honesty cost.
Valid only where real clicks can never land, with a documented reason.
Keep real clicks the default so tests prove what users can do.

Proving the Fix at the Click Point

Confirmation beats hope: after any fix, prove the target owns its click point. Re-run the exact failing test, not a simplified copy — simplified copies skip the banner, the timing, or the scroll position that caused the incident. Then run the neighboring tests that share the page, since overlay fixes in shared setup touch every flow that passes through it.

For stubborn cases, assert the geometry directly. Read the target's rect through execute_script and compare against the suspected overlay's bounds, or check elementFromPoint at the target's center returns the target itself. These one-line probes turn I think it is clear into the point belongs to the target, which is the standard the fix must meet.

Lock the fix with a regression test shaped like the original collision: banner shown, scroll position set, action attempted. If the product later moves the banner or the button, this test fails at the collision instead of letting twenty downstream tests fail mysteriously. Geometry assertions feel fussy until they catch the next overlap in seconds. The click point is the contract — verify it, then ship. A green run without point ownership proved nothing; a green run with it proves the user can actually press the control.

📊 Production Insight
A fix that closed one banner passed locally but failed in CI where a second promo overlay appeared. An elementFromPoint assertion in the regression test caught the second layer immediately. Rule: assert the click point owner, not just the absence of the first overlay.
🎯 Key Takeaway
Re-run the exact failing test plus neighbors sharing the page.
Assert click-point ownership with elementFromPoint for stubborn cases.
Write regression tests shaped like the original collision.
● Production incidentPOST-MORTEMseverity: high

Cookie Banner Covered Checkout for 200 Nightly Tests

Symptom
All 200 checkout tests failed on the same step with ElementClickInterceptedException naming a div with cookie-consent classes. The failures started overnight across every browser, and the banner was visible in every failure screenshot — parked directly over the payment button.
Assumption
The team assumed a deploy had moved the payment button because the error pointed at the click step. They spent a day checking locators and recent front-end commits. The real change was a consent-management rollout to the test region that nobody had flagged to QA — the button never moved, it just gained an overlapping neighbor.
Root cause
The new banner rendered on every fresh profile and the suite had no step to accept or dismiss it. WebDriver profiles start clean, so consent never persisted between runs, and the banner covered the payment button's click point on every single test. The exception message named the banner's classes from the first failure, but nobody read past the word click.
Fix
A consent-handling step was added to the shared login fixture: click accept when the banner appears, proceeding silently when it does not. The payment click was wrapped in an element_to_be_clickable wait so transient animations settle first. All 200 tests passed the next run, and the fixture now owns banner duty for every suite.
Key lesson
  • Read the intercepting element in the message first — its classes usually name the exact overlay in seconds.
  • Fresh WebDriver profiles never carry consent, so every banner must be handled in shared setup, not per test.
  • Own overlays in one fixture: a single dismiss step beats two hundred scattered workarounds.
Production debug guideFive steps that name the covering layer and clear it.5 entries
Symptom · 01
The message quotes an intercepting element you don't recognize
→
Fix
Copy the quoted tag and class names and search the page source: driver.page_source saved to /tmp/page.html, then grep for those classes. That locates the overlay's markup and usually its purpose — consent, chat, promo. Screenshot at failure time and circle what sits over your target to confirm.
Symptom · 02
A cookie or consent banner covers the target
→
Fix
Handle it in shared setup, not the failing test. Locate the accept button, click it, then wait for invisibility_of_element_located on the banner before continuing. Guard with a short try so suites pass in regions where the banner never renders.
Symptom · 03
A spinner or skeleton screen intercepts the click
→
Fix
Never sleep through loaders — wait for invisibility_of_element_located on the spinner's locator with a ten-second timeout. Then wait for element_to_be_clickable on your target. If the spinner has no stable locator, wait for the target's clickability only, which implicitly outlasts most loading states.
Symptom · 04
The target sits under a sticky header or footer after scrolling
→
Fix
Scroll it to the viewport center with execute_script scrollIntoView using block center, then re-check its position. If the header still overlaps, close or collapse the sticky element, or scroll with an offset. Confirm with element_to_be_clickable before clicking.
Symptom · 05
Nothing visible covers the target, yet clicks still intercept
→
Fix
Suspect zero-size or transparent layers: modals with opacity zero, backdrops that never hid, or animations mid-flight. Wait for the animation to settle with a clickability wait, and inspect for backdrop elements in the DOM. Use a JS click only after proving a real click can never land.
ElementClickInterceptedException Causes Compared
Root CauseHow to ConfirmFixPrevention
Cookie or consent banner over the targetMessage quotes consent classes; banner in screenshotAccept in shared setup; wait for banner invisibilityOne consent helper in the login fixture for all suites
Spinner or skeleton screen mid-loadClick fails only on slow runs; loader visible in videoWait for invisibility of the loader, then clickabilityName loader locators beside targets; never sleep
Sticky header or footer after scrollingTarget at viewport edge under docked chromeScroll to center with scrollIntoView block centerCenter-scrolling click helper used by every test
Animation moving the target mid-clickPasses on retry; carousel or accordion nearbyWait for the settled state or clickability to holdState-based waits on animation end markers
⚙ Quick Reference
4 commands from this guide
FileCommand / CodePurpose
io_thecodeforgeoverlay_setup.pyfrom selenium.common.exceptions import TimeoutExceptionClearing Cookie Banners and Popups in Shared Setup
io_thecodeforgescroll_click.pyfrom selenium.webdriver.common.by import ByScroll Into View So the Target Owns Its Point
io_thecodeforgeoutlast_loaders.pyfrom selenium.webdriver.common.by import ByOutlasting Spinners, Animations, and Lazy Overlays
io_thecodeforgejs_click.pyfrom selenium.webdriver.common.by import ByJavaScript Clicks

Key takeaways

1
The exception names the covering element
read its classes before touching locators.
2
Handle banners and popups once in shared setup, tolerating their absence.
3
Scroll targets to viewport center and follow with a clickability wait.
4
Outlast loaders with invisibility waits; never sleep through motion.
5
Reserve JavaScript clicks for controls a real click can never reach, with comments.
6
Prove fixes at the click point and lock them with collision-shaped regression tests.

Common mistakes to avoid

5 patterns
×

Rewriting locators when the locator was never broken

Symptom
New selectors find the same element and fail identically, burning hours while the covering banner sits untouched in every screenshot.
Fix
Read the intercepting element in the message first. Fix or dismiss the covering layer; touch the locator only if the find itself throws NoSuchElementException.
×

Dismissing banners inline in every test

Symptom
Fifty copies of banner code drift apart, and a redesign breaks them all differently, producing a week of scattered red tests.
Fix
Move dismissal into one shared fixture helper that tolerates absence. Tests inherit a clean viewport and redesigns need a one-line fix.
×

Using sleep to outlast loaders and animations

Symptom
Passes on fast laptops, fails on loaded CI runners; Monday-morning slowness becomes Monday-morning red builds with no product change.
Fix
Replace each sleep with invisibility_of_element_located on the loader plus element_to_be_clickable on the target. Waits end on states, not clocks.
×

Defaulting to JavaScript clicks for every blocked click

Symptom
The suite goes green while real users cannot press the control, because script clicks bypass every check that models human interaction.
Fix
Escalate honestly: dismiss, scroll, wait, and only then bypass with a review-approved comment naming why a real click can never land.
×

Scrolling targets to the viewport edge

Symptom
Default scrolls park buttons under sticky headers, converting every click on long pages into an interception failure.
Fix
Scroll with block center so targets land mid-viewport, then wait for clickability. Collapse or remove sticky promos that still overlap.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What does ElementClickInterceptedException actually tell you?
Q02JUNIOR
Why do cookie banners hit test suites harder than manual testing?
Q03JUNIOR
How should scrolling be done before a click?
Q04SENIOR
When is a JavaScript click acceptable?
Q05SENIOR
How do you prove a blocked-click fix is correct?
Q01 of 05JUNIOR

What does ElementClickInterceptedException actually tell you?

ANSWER
That Selenium found your target but another element owns its click point — a banner, modal, sticky bar, or loader sits on top. The message quotes the covering element's markup, so the first step is reading it to identify the layer. The locator is fine; the fix clears or outlasts the cover, not the selector.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Why does my locator work in DevTools but the click fails?
02
Should I just always use JavaScript clicks to avoid this?
03
The banner only appears sometimes. How do I handle that?
04
Does double-clicking or clicking twice help?
05
What about full-page screenshots showing nothing over the target?
06
Can sticky headers be fixed without removing them?
N
Naren Founder & Principal Engineer

20+ years shipping production backend systems. Notes here come from systems that actually shipped.

Follow
✓ Verified
production tested
September 27, 2026
last updated
2,085
articles · all by Naren
🔥

That's Selenium. Mark it forged?

5 min read · try the examples if you haven't

←
Previous
Selenium StaleElementReferenceException
2 / 5 · Selenium
Next
Selenium NoSuchElementException — Waits, Not sleep()
→