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..
20+ years shipping production backend systems. Notes here come from systems that actually shipped.
- ✓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
- 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
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.
Clearing Cookie Banners and Popups in Shared Setup
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.
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.
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.
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.
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.
Cookie Banner Covered Checkout for 200 Nightly Tests
- 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.
| File | Command / Code | Purpose |
|---|---|---|
| io_thecodeforge | from selenium.common.exceptions import TimeoutException | Clearing Cookie Banners and Popups in Shared Setup |
| io_thecodeforge | from selenium.webdriver.common.by import By | Scroll Into View So the Target Owns Its Point |
| io_thecodeforge | from selenium.webdriver.common.by import By | Outlasting Spinners, Animations, and Lazy Overlays |
| io_thecodeforge | from selenium.webdriver.common.by import By | JavaScript Clicks |
Key takeaways
Common mistakes to avoid
5 patternsRewriting locators when the locator was never broken
Dismissing banners inline in every test
Using sleep to outlast loaders and animations
Defaulting to JavaScript clicks for every blocked click
Scrolling targets to the viewport edge
Interview Questions on This Topic
What does ElementClickInterceptedException actually tell you?
Frequently Asked Questions
20+ years shipping production backend systems. Notes here come from systems that actually shipped.
That's Selenium. Mark it forged?
5 min read · try the examples if you haven't