Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
NoSuchElementException means Selenium found no matching element in the current page and browsing context at the instant it searched. It does not, on its own, mean headless Chrome is broken. First confirm that the browser reached the expected page, that your locator matches the live DOM, and that you waited for JavaScript to create the element. Then check whether the element is in an iframe or shadow root, or whether the page replaced it during a rerender.
What the exception means—and what it does not
Selenium’s find_element(...) call searches the current browsing context for a match. If it cannot find one at that moment, it raises selenium.common.exceptions.NoSuchElementException. The Selenium Python API documentation explains that an element may not yet be on screen when the find operation runs because the page is still loading, and points to WebDriverWait as a way to wait for an element.
The exception identifies the failed lookup, not the underlying cause. It can mean the script is on a different URL than expected, the selector does not match the current markup, a previous navigation or click did not complete as assumed, or JavaScript has not rendered the target yet. The search can also be happening in the wrong frame or outside a shadow root. Headless mode is a difference worth investigating if headed Chrome succeeds, but the exception alone does not establish a headless-Chrome defect.
Start with a condition-based wait
Use an explicit wait for the state your next operation actually needs. The timeout below is an example, not a universal setting; choose one appropriate to the site and operation. Selenium’s Python WebDriverWait polls every 0.5 seconds by default and ignores NoSuchElementException while polling. A fixed sleep can waste time when the element is ready early and still fail when it takes longer than the chosen delay.
#1 Best Overall
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = webdriver.ChromeOptions()
options.add_argument("--headless")
# Choose a deliberate viewport if responsive layout affects the target.
options.add_argument("--window-size=1440,1000")
driver = webdriver.Chrome(options=options)
try:
driver.get("https://example.com")
# Replace with a locator verified against this page's current DOM.
locator = (By.CSS_SELECTOR, "main .target")
element = WebDriverWait(driver, 15).until(
EC.visibility_of_element_located(locator)
)
print(element.text)
finally:
driver.quit()
This is a debugging pattern, not a tested fix for an unknown page. Use presence_of_element_located if you only need a DOM node, even if it is not visible. Use visibility_of_element_located when the element must be visible, and a clickability condition when you intend to click it. Being present or visible does not necessarily mean a control is ready to click.
Diagnose the failing run in order
-
Confirm the browser reached the page you expect
Log the URL and title immediately after navigation and after any click, form submission, or redirect that should change page state:
print("URL:", driver.current_url) print("Title:", driver.title)If either differs from the expected result, investigate the navigation or earlier action before changing the locator. A login redirect, error page, consent overlay, or blocked request can put the browser somewhere other than the intended page.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Inspect the DOM produced by headless Chrome
Save the source from the failing session and compare it with the markup in which you found the selector. The page may render different content before and after a click, login, or other state change. A quick check can tell you whether a candidate element exists at all:
print(driver.page_source[:5000])For a large document, save the full source to a local file or query a narrowly scoped candidate selector. The browser’s live DOM after the same interactions is more useful than markup copied from a different session or a static initial response. If possible, capture a screenshot too; it helps distinguish an unexpected page or overlay from a selector problem.
-
Check the selector and locator strategy
Verify the selector against the current DOM, including capitalization, punctuation, attributes, and whether the target is actually an element rather than text. Pair each selector syntax with the correct Selenium strategy:
Rank #3
By.IDfor an ID value, without a leading#.By.NAMEfor a name attribute value.By.CSS_SELECTORfor CSS, such asmain .target.By.XPATHfor an XPath expression.
Prefer a stable ID, name, or meaningful CSS selector when the page provides one. An absolute XPath that depends on incidental nesting is easy to invalidate when the page changes. Selenium’s locator guidance recommends checking that the locator is being used correctly and that the desired element is in the page.
Recommended: Update Every Outdated Driver on Your PC in One Scan - Free →Recommended: Fix Windows Errors and Clear Junk Files in Minutes - Free Scan →Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Wait for the right state, not just navigation
A navigation wait reaching a page-load
readyStatedoes not guarantee that JavaScript-created content is ready. Wait for a condition tied to the target or the next action:locator = (By.ID, "results") results = WebDriverWait(driver, 15).until( EC.presence_of_element_located(locator) )If the target is present but hidden, wait for visibility instead. If you are about to click, wait for the appropriate clickability condition. Avoid mixing implicit and explicit waits without understanding their timing interaction; an explicit wait around each lookup gives a clearer, condition-specific timeout.
-
Switch to the correct browsing context
Selenium searches the current context. For content inside an iframe, locate the frame, switch into it, then search for the target. Switch back to the top-level page when finished:
frame = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, "iframe.results-frame")) ) driver.switch_to.frame(frame) try: result = WebDriverWait(driver, 10).until( EC.visibility_of_element_located((By.ID, "result")) ) finally: driver.switch_to.default_content()For an element inside a shadow DOM, first locate the host element and then query through its shadow root. A normal page-level locator does not automatically pierce that boundary. These are possibilities to check, not conclusions implied by
NoSuchElementException.Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsSpecial offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy. -
Re-find elements after a rerender
Modern pages may remove and rebuild nodes when data arrives or state changes. If a previously found element becomes stale after such a change, wait for the updated state and locate it again rather than reusing an old element reference. The stale-element condition is distinct from a missing-element lookup, but dynamic replacement can be part of the sequence that leads to either failure.
-
Compare headed and headless runs as evidence
Record the URL, title, viewport, screenshot, page source, Chrome and Selenium versions, and any available console or network errors for both runs. Check whether the two runs differ in responsive layout, authentication state, overlays, or timing. These are diagnostic observations, not proof that any one of them caused the failure.
Choose the fix by symptom
| What you observe | What to try |
|---|---|
| No matching node in the current DOM | Verify the URL and page state, then correct the locator or wait for the node to be created. |
| Node exists but is hidden | Wait for visibility if the next step requires display; presence alone only establishes a DOM match. |
| Node is inside an iframe | Switch to the frame before locating the target, then return to default content as needed. |
| Node is inside a shadow root | Locate the host and search through its shadow root. |
| Page replaces the node after an update | Wait for the new state and locate the replacement instead of reusing an old reference. |
| Headed works, headless does not | Compare URL, DOM, viewport, authentication, overlays, and timing before attributing the difference to headless mode. |
| Chrome session will not start | Investigate startup diagnostics and Chrome/ChromeDriver compatibility separately; a version mismatch is relevant to session-creation failures, not the default explanation for a lookup failure in an already working session. |
Common mistakes that keep the error coming back
- Adding a longer sleep without checking state: it may mask a race on one run but offers no assurance the page is ready on a slower run. Wait for the element condition instead.
- Changing Chrome flags before verifying the page: first determine whether the browser reached the expected URL and whether the element is present in its DOM.
- Using a selector copied from a different page state: inspect after the same navigation and interactions the script performs.
- Using visibility when only presence is needed, or presence before clicking: match the expected condition to what the next line of code will do.
- Treating a session-start failure as a locator failure: if Chrome never starts, troubleshoot driver/browser compatibility; if the session is running and a lookup raises the exception, first inspect page, locator, wait, and context.
Or skip the browser setup
If your immediate goal is a visual record of what a page looks like in a headless capture—not to locate, click, or extract a DOM element—you can request a screenshot from ScreenshotNeo with one GET request:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
See the ScreenshotNeo API documentation for request options. ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status in headers. It also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.
Sign up free for 1,000 screenshots a month—no card required.
Version and case-specific limits
Selenium documentation checked on September 30, 2026 describes the general lookup and wait behavior; the Python exception API surfaced as Selenium 4.49.0, and the troubleshooting page was last modified September 3, 2026. Those references explain common WebDriver behavior, but cannot identify the cause on a particular site. That requires the failing URL, code, exception details, browser and Selenium versions, and observations from the actual run.
Frequently Asked Questions
Does `NoSuchElementException` mean the selector is invalid?
Not necessarily. The selector may be valid but searched too early, on the wrong page, or in the wrong browsing context.
Is headless Chrome itself the confirmed cause?
No. A difference between headed and headless runs needs to be diagnosed by comparing the actual page, DOM, viewport, state, and timing.
Recommended Free Tools
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.

