ElementNotInteractableException and Element Click Intercepted in Selenium

10 min readupdated July 24, 2026

Your test finds the button. It is there in the page. Then Selenium refuses to click it:

ElementNotInteractableException: element not interactable

Or its close relative:

ElementClickInterceptedException: element click intercepted:
Element <button id="submit"> is not clickable at point (450, 620).
Other element would receive the click: <div class="cookie-banner">

Both mean the same thing at heart: Selenium found your element, but a real user could not have used it right now. The tool is not being difficult. It is telling you something true about your page.

The most common fix people reach for is a JavaScript click, and it is almost always the wrong one. Here is what to do instead.

The two errors are not the same

Getting this distinction right saves you looking in the wrong place, so it is worth thirty seconds.

ElementNotInteractableException means the element itself is not in a usable state. It exists in the page, but it is hidden, has no size, or is not displayed. Nothing is on top of it. The problem is the element.

ElementClickInterceptedException means the element is fine, but something else would receive the click. An overlay, a sticky header, a cookie banner, a toast message. The problem is what is in front of it.

The second one is generous with information. It names the element that would receive the click:

Other element would receive the click: <div class="cookie-banner">

Read that line first. It usually identifies your culprit before you have looked at anything else. In older Selenium versions this appeared as "element is not clickable at point (x, y)", which is the same problem with a less helpful name.

Why Selenium checks at all

Selenium follows the WebDriver standard, which requires the driver to check that an element can genuinely be interacted with before acting on it. Is it displayed? Does it have a size? Is it the element that would actually receive the click at that position?

That is deliberate, and it is on your side. Imagine the alternative. If Selenium clicked regardless, your test would pass while a cookie banner covered the entire checkout button. Your suite would report success on a page no customer could use.

A test that passes on a broken page is worse than no test. These exceptions exist to prevent exactly that, which is the reason bypassing them deserves more caution than it usually gets.

The six causes, most common first

1. A banner, header or toast is in the way. Cookie consent banners are the champion here. Sticky headers and footers come next, especially on smaller viewports where the page has less room. A success toast from the previous step is another frequent one, because it appears exactly when your test moves on.

2. You clicked during an animation. A modal slides in over 300ms. Selenium finds the button at its starting position, then the element moves, and the click lands where the button used to be. This one is intermittent, which is why it gets blamed on flakiness rather than on animation.

3. The element is off-screen. It exists, but it is below the fold and never scrolled into view.

4. Your selector matched a hidden copy. Responsive layouts often render a mobile menu and a desktop menu, hiding one with CSS. Your selector matched the hidden one. This produces ElementNotInteractableException and confuses people badly, because they can see the element on screen. They are looking at the other one.

5. The element is disabled or not ready. A submit button that stays disabled until the form validates.

6. A closed dialog left an invisible overlay behind. The dialog is gone visually, but its backdrop is still in the page covering everything. This is a real application bug, and your test just found it.

The fixes, in the order to try them

First: wait for the blocker to leave

If a banner or overlay is the cause, wait for it to actually be gone. Not for a number of seconds. For the thing itself.

// Java
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.invisibilityOfElementLocated(
    By.cssSelector(".cookie-banner")));
// JavaScript
const banner = await driver.findElement(By.css('.cookie-banner'));
await driver.wait(until.elementIsNotVisible(banner), 10000);

Better still, remove the cause instead of waiting for it every time. If a consent banner blocks every test, handle it once in setup rather than in fifty tests:

// set the consent cookie before the page loads, so the banner never appears
await driver.get('https://example.com');
await driver.manage().addCookie({ name: 'consent', value: 'accepted' });
await driver.navigate().refresh();

That is faster than dismissing it, and it cannot fail halfway.

Second: wait for the element to be clickable

elementToBeClickable checks the element is both visible and enabled. It is the right default for buttons that appear or become active over time.

WebElement save = wait.until(
    ExpectedConditions.elementToBeClickable(By.id("save")));
save.click();
const save = await driver.wait(until.elementLocated(By.id('save')), 10000);
await driver.wait(until.elementIsVisible(save), 10000);
await driver.wait(until.elementIsEnabled(save), 10000);
await save.click();

Be aware of one limit, because it catches people out: elementToBeClickable does not check whether something covers the element. It checks visible and enabled. So it solves cause 5 and part of cause 1, and it will not save you from a cookie banner.

Third: scroll it into view

const el = await driver.findElement(By.id('submit'));
await driver.executeScript('arguments[0].scrollIntoView({ block: "center" })', el);
await el.click();

block: "center" matters more than it looks. The plain scrollIntoView() can land the element flush against the top of the viewport, directly underneath a sticky header, which turns a scroll problem into an interception problem. Centring it avoids both edges.

Fourth: check you matched the element you meant

If you suspect a hidden duplicate, ask the page how many there are:

const matches = await driver.findElements(By.css('.menu-link'));
console.log(matches.length);
for (const m of matches) console.log(await m.isDisplayed());

Two matches with one false confirms it. Fix the selector to name the visible one, usually by scoping to the container it lives in, rather than making the test work around the ambiguity.

About the JavaScript click

This is the workaround everyone finds, and it deserves an honest explanation rather than a warning.

await driver.executeScript('arguments[0].click()', element);

It works because it skips the check. It dispatches a click event directly on the element, bypassing the browser's view of what is on top, what is visible and what a user could reach. The exception disappears immediately, which is exactly why it is tempting.

Here is what you traded away. Your test can now click a button hidden behind a full-screen overlay, and pass. It can click something with zero height, and pass. The failure you silenced was a genuine report that a user could not do this. If a cookie banner starts covering your checkout button in production, this test stays green.

There is a second, quieter problem: a JavaScript click is not a real click. It skips the mouse events that come before it, so mousedown, mouseup and any hover behaviour never fire. Components that rely on those, which is many dropdown and drag interactions, behave differently under it than under a real user.

When it is legitimate: an element you cannot control is permanently in the way, in a third-party widget you cannot change, and you have confirmed a real user is unaffected. Use it knowingly, and leave a comment saying why. Never as a default.

What not to do

  • Thread.sleep or a fixed delay. It passes on your machine and fails on a slower build server, or it wastes seconds on every run when the page was ready immediately.
  • Raising every timeout. If contention or an overlay is the real cause, longer timeouts make failures slower to report without making anything more reliable.
  • A blanket retry around every click. It hides genuine application bugs, and this error family is unusually good at finding real ones.

The habit worth building

When you see either exception, ask one question: could a person have clicked this, at this moment, on this screen?

If the answer is no, your test found something real, and the fix belongs in the test's timing or in the application. If the answer is yes, then either you matched the wrong element or you acted before the page settled. Both have proper fixes, and neither of them is a JavaScript click.

That question also keeps your suite honest, because it keeps your tests limited to things a user can actually do.

Remember this

ElementNotInteractableException is about the element. ElementClickInterceptedException is about what is covering it, and the error names the covering element for you. Wait for the blocker to leave, remove it in setup if it blocks everything, scroll with block: "center", and check you matched the visible copy. Keep the JavaScript click for cases you can explain out loud.

The Selenium track goes deeper on waiting properly, which is where most of these failures actually start: waiting and stale elements.

Related: Cypress element covered by another element and waiting compared across all three tools.

now prove it

Replace the fixed sleep

Reading is the easy part. Write the test yourself, then we break the app on purpose to check it would really catch the bug.

Solve this problem →

Keep reading