Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

ElementNotInteractableException means Selenium found a DOM element, but that element could not perform the requested action in its current state. In headless Chrome, diagnose the element before changing browser flags: confirm the locator, use an action the element supports, wait for the application state you need, and check visibility, viewport position, and overlays. Only then verify the headless configuration and Chrome/ChromeDriver versions.

What the exception actually means

Selenium’s documentation defines this exception as an attempt to interact with an element that is “not interactable in its current state.” Presence in the DOM is not enough. A matching node may be hidden with CSS, disabled, outside the usable page state, covered by another element, or not the control you intended to select.

Headless mode can expose layout or timing differences, but it is not established as a universal cause of this exception. Treat a headed-versus-headless difference as a diagnostic clue, not as proof that removing headless mode fixes the application.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use this diagnostic order

  1. Confirm the page and locator. Make sure navigation and preceding actions completed. Check that the selector identifies the intended element and, ideally, only one match. A broad selector can return a hidden template control while a visible duplicate is the real target.
  2. Match the operation to the element. send_keys belongs on a text field or another keyboard-interactable control. clear requires an editable, resettable control. Do not type into a wrapper, label, icon, or container merely because it contains the visible input.
  3. Check displayed state and viewport. An element can exist but be hidden, have zero usable size, or be outside the current view. Selenium attempts to scroll an out-of-viewport element into view, but scrolling cannot make a display:none, disabled, or otherwise unavailable control interactable.
  4. Wait for the condition the next action needs. A completed navigation does not prove that JavaScript has created, enabled, or revealed the control. Wait for visibility, clickability, a selector, or an application-specific state.
  5. Separate obstruction from non-interactability. If another element covers the click point, Selenium reports ElementClickInterceptedException. Investigate cookie banners, modals, sticky headers, animations, and other overlays rather than treating the problem as a missing element.
  6. Check headless setup last. Use the documented --headless=new argument where appropriate and verify that Chrome and ChromeDriver have matching major versions. These are configuration checks, not a substitute for element diagnosis.

A reliable headless Chrome baseline

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1365,900")
driver = webdriver.Chrome(options=options)
try:
    driver.get("https://example.com")
    print(driver.title)
finally:
    driver.quit()

A fixed window size makes responsive breakpoints more predictable. If your site presents a mobile layout at the default headless size, a selector or overlay that works on desktop may no longer be present or visible.

Locator and element-type checks

Make the selector specific

Prefer a stable ID, name, accessible role, or a narrowly scoped CSS selector. Then inspect how many elements match before acting:

from selenium.webdriver.common.by import By

matches = driver.find_elements(By.CSS_SELECTOR, "form#login input[name='email']")
print("matches:", len(matches))
for item in matches:
    print(item.tag_name, item.is_displayed(), item.is_enabled(), item.get_attribute("type"))

If the count is zero, the page or frame is wrong, or the control has not been created yet. If it is greater than one, refine the locator instead of selecting the first match blindly.

Use the control that owns the value

For a text field, locate the input or textarea, not its label or parent:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
email = driver.find_element(By.NAME, "email")
email.clear()
email.send_keys("[email protected]")

For a native checkbox or button, use click(). For a custom widget, identify the element that the application makes keyboard- or pointer-interactable; a decorative span may not be the correct target.

Wait for the right state, not an arbitrary delay

Use an explicit wait tied to the next operation. Selenium’s waiting guidance says not to mix implicit and explicit waits because their polling behavior can produce unpredictable timing.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 20)
email = wait.until(
    EC.visibility_of_element_located((By.NAME, "email"))
)
wait.until(lambda d: email.is_enabled())
email.clear()
email.send_keys("[email protected]")

For a click, wait for clickability:

submit = wait.until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
)
submit.click()

Visibility means Selenium can see the element; clickability additionally checks that it is enabled. Neither condition guarantees that an unrelated overlay will not intercept the center point, so inspect the page when the exception changes to a click-intercepted error.

Wait for application state

When a framework renders after navigation, wait for a meaningful signal such as a loading indicator disappearing, a button becoming enabled, or a results container appearing. A fixed sleep can be shorter than a slow run and unnecessarily long on a fast run.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frames, windows, and stale page context

A correct locator still fails if the element belongs to an iframe. Switch into the frame before locating it:

frame = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "iframe.payment")))
driver.switch_to.frame(frame)
card = wait.until(EC.visibility_of_element_located((By.NAME, "cardnumber")))
card.send_keys("4111111111111111")
driver.switch_to.default_content()

Likewise, after opening a new tab, switch to its window handle. After navigation or a reactive re-render, reacquire an element rather than reusing a reference to a node that has been replaced.

Viewport, overlays, and scrolling

Selenium scrolls an out-of-view target as part of normal interaction, but sticky headers, cookie consent dialogs, chat widgets, and animations can still cover the center. First wait for the overlay to disappear or close it through its real UI. If the page needs a deliberate scroll, use it to bring the target into a stable position:

target = wait.until(EC.visibility_of_element_located((By.ID, "details")))
driver.execute_script(
    "arguments[0].scrollIntoView({block: 'center', inline: 'nearest'});",
    target,
)
wait.until(lambda d: target.is_displayed() and target.is_enabled())
target.click()

Do not make a JavaScript DOM click your default fix. It bypasses the normal user-like interaction path and can hide a real visibility, overlay, or timing defect. Use it only when the application explicitly requires a non-user DOM operation and you understand the trade-off.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common symptoms and fixes

Symptom Likely cause Fix
Element is found but send_keys fails Wrong node, such as a wrapper or label Locate the editable input or textarea; inspect tag and type.
Element count is greater than one Selector also matches a hidden template or duplicate Scope the selector and verify visibility before acting.
Works headed, fails headless Different viewport, responsive layout, timing, or overlay Set a window size, capture diagnostics, and wait for the required state.
Click reports intercepted Another element covers the center Close or wait out the overlay, then scroll and retry.
Element appears after a delay JavaScript has not rendered or enabled it Use an explicit wait for visibility, enabled state, or an application signal.
Every interaction fails after switching pages Wrong frame or window context Switch to the correct iframe or window, then reacquire the element.
Session fails before the test starts Chrome and ChromeDriver major versions do not match Install compatible major versions and verify the actual binaries being launched.

Capture evidence when the failure is intermittent

Record the current URL, title, viewport, locator, element count, displayed/enabled values, and a screenshot at the failure point. That evidence distinguishes a selector mistake from a race condition or overlay. Keep the browser log and, where permitted, the page HTML so you can see whether the expected control existed at the time of failure.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a clean page image rather than Selenium interaction, ScreenshotNeo makes one GET request for a screenshot or PDF. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for the other 63 options, including full-page lazy-image loading, CSS-selector element capture, device presets, custom CSS and JavaScript, waits, request blocking, cookies and headers, geolocation, PDF controls, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture, usage reporting, and the OpenAPI specification. A free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Cost and reliability considerations

  • Explicit waits improve reliability by synchronizing with the application rather than a guessed delay.
  • A fixed viewport reduces responsive-layout surprises, but test the viewport your users actually receive.
  • Retry only after collecting state. Blind retries can conceal a deterministic locator or overlay defect.
  • Keep Chrome and ChromeDriver major versions aligned; configuration changes should follow element-level checks.
  • For image capture workloads, ScreenshotNeo bills only clean shots and exposes verdict and billing headers, so failed loads and cache hits do not consume paid captures.

Frequently Asked Questions

Is this exception unique to headless Chrome?

No. The exception describes the element’s current state. Headless mode can change layout or timing, but the documented diagnosis is to verify the element, action, visibility, timing, and obstruction first.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I remove the --headless argument?

Use headed mode as a comparison while diagnosing, not as the permanent fix. Keep headless after correcting the underlying selector, state, viewport, or synchronization issue.

Why did the error change to ElementClickInterceptedException?

That usually means Selenium reached the target but another element covered its center point. Investigate overlays, sticky navigation, modals, and animations separately.

What does readyState=complete guarantee?

It indicates that the navigation lifecycle reached that state; it does not guarantee that JavaScript has rendered, revealed, or enabled the control your next action needs.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.