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

Use Selenium’s plural lookup and test the returned list: bool(driver.find_elements(By.CSS_SELECTOR, "#target")). A non-empty list means at least one matching node exists in the current DOM; an empty list means no match was found at that instant. Use a bounded explicit wait when JavaScript may add the element later, and choose a visibility condition when “exists” really means “displayed.”

The immediate existence check

find_elements() is the simplest branch-style test because it returns a collection rather than throwing when there are no matches. Selenium returns an empty list for zero matches, so Python truth testing gives a clear result.

from selenium import webdriver
from selenium.webdriver.common.by import By

 driver = webdriver.Chrome()
 driver.get("https://example.com")

exists = bool(driver.find_elements(By.CSS_SELECTOR, "#target"))
if exists:
    print("Element exists in the current DOM")
else:
    print("No matching element was found")

 driver.quit()

Remove the leading space before driver lines if you paste this into a file; it is shown only to keep the code block visually separated here. In normal Python, the executable version is:

from selenium import webdriver
from selenium.webdriver.common.by import By

 driver = webdriver.Chrome()
 driver.get("https://example.com")

 matches = driver.find_elements(By.CSS_SELECTOR, "#target")
 if matches:
     print("Element exists in the current DOM")
 else:
     print("No matching element was found")

 driver.quit()

The lookup is a snapshot. It answers whether the locator matched when Selenium executed that command; it does not promise that the node will remain attached after the page changes.

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

Choosing between find_elements and find_element

Goal Code Behavior
Branch if any match exists bool(driver.find_elements(By.ID, "target")) Returns True for one or more matches and False for none.
Use one expected element driver.find_element(By.ID, "target") Returns the first matching WebElement; a missing match raises NoSuchElementException.
Wait for a node to enter the DOM WebDriverWait(driver, 10).until(EC.presence_of_element_located(locator)) Waits until a match is present; presence does not imply visibility.
Wait until it is displayed WebDriverWait(driver, 10).until(EC.visibility_of_element_located(locator)) Waits for a displayed element with nonzero height and width.

Use the singular method when the next operation needs the element. Catch the specific exception if absence is an expected branch:

from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By

try:
    element = driver.find_element(By.ID, "target")
except NoSuchElementException:
    element = None

if element is None:
    print("Nothing to use")
else:
    element.click()

The plural form is usually cleaner for a yes/no check because normal absence is represented by data (an empty list), not control flow through an exception.

Waiting for an element added by JavaScript

Modern pages often render a shell first and insert controls after an API response, route transition, or user action. A one-time lookup can therefore report “missing” even though the element is expected moments later. Use an explicit wait tied to the state you need.

Wait for DOM presence

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

locator = (By.CSS_SELECTOR, "#target")
element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located(locator)
)
print(element.tag_name)

WebDriverWait.until() keeps polling until the condition returns a truthy value. If the ten-second limit expires, Selenium raises TimeoutException. The documented default polling interval is 0.5 seconds, and NoSuchElementException is ignored while the condition is being evaluated.

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.

Wait for visibility instead

element = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located(locator)
)
element.click()

Presence means the node is in the DOM. It may still be hidden, have zero dimensions, or be covered by another UI layer. Visibility is the appropriate condition when the test requires a user-visible control. A visible element can still be unsuitable for a particular action, so validate that action separately.

Turn a wait into a boolean

For a condition that may legitimately never occur, catch the timeout and return a Boolean:

from selenium.common.exceptions import TimeoutException

 def exists_after_wait(driver, locator, seconds=5):
     try:
         WebDriverWait(driver, seconds).until(
             EC.presence_of_element_located(locator)
         )
         return True
     except TimeoutException:
         return False

This distinguishes “not found before the deadline” from an unexpected programming or browser error. Keep the timeout bounded and choose it according to the application’s normal response time.

Locator strategies that make existence checks reliable

The Python WebDriver API supports ID, name, XPath, CSS selector, class name, tag name, link text, and partial link text. Prefer a stable attribute owned by the application rather than a generated class or a long positional XPath.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

ID and CSS

driver.find_elements(By.ID, "target")
driver.find_elements(By.CSS_SELECTOR, "form#checkout input[name='email']")

CSS is useful for expressing relationships and attributes while remaining readable. Escape special characters when an ID or class is not a valid CSS identifier.

XPath

driver.find_elements(
    By.XPATH,
    "//button[@type='submit' and normalize-space()='Continue']"
)

XPath can match text and relationships that CSS cannot, but text-sensitive selectors may break when labels change or whitespace is localized.

Search from a WebElement

panel = driver.find_element(By.CSS_SELECTOR, "section.settings")
rows = panel.find_elements(By.CSS_SELECTOR, "[data-row]")
if rows:
    print(f"Found {len(rows)} rows in the panel")

A descendant lookup narrows the search and avoids accidentally matching a similar node elsewhere on the page.

What “exists” does—and does not—prove

  • DOM presence: a locator matched at lookup time.
  • Visibility: requires a visibility condition; presence alone is insufficient.
  • Enabled state: check element.is_enabled() after locating it.
  • Interactability: overlays, off-screen positioning, and event handlers can still prevent an action.
  • Future attachment: a framework re-render can detach the node and make a saved reference stale.

If the page updates between locating and acting, locate again inside the wait or action path instead of assuming an old reference remains valid. Use the condition that represents the user outcome you are testing, not merely the existence of an HTML node.

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

Implicit and explicit waits

Selenium provides both implicit and explicit waits. An explicit wait states the event your test is waiting for—such as presence or visibility—and keeps that timing visible at the call site. Avoid designing a test around an assumed combined-timeout formula when both mechanisms are enabled; their interaction depends on the installed Selenium version and driver behavior. Keep synchronization deliberate, and consult the waits documentation for your exact package version when changing global timeout settings.

Complete example with a safe decision path

from selenium import webdriver
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

 driver = webdriver.Chrome()
 try:
     driver.get("https://example.com/account")
     locator = (By.CSS_SELECTOR, "[data-testid='account-menu']")

     if not driver.find_elements(*locator):
         print("Not present immediately; waiting for the application to render it")

     try:
         menu = WebDriverWait(driver, 10).until(
             EC.visibility_of_element_located(locator)
         )
     except TimeoutException:
         menu = None

     if menu is None:
         print("The account menu was not visible within 10 seconds")
     else:
         print("Ready to interact:", menu.text)
 finally:
     driver.quit()

The immediate check is useful for a fast branch, while the explicit wait handles expected asynchronous rendering. In a test assertion, replace the print statements with the assertion behavior your test framework requires.

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

Troubleshooting common failures

The check always returns an empty list

  • Confirm the browser is on the expected URL and frame.
  • Inspect the rendered DOM, not only the original response HTML.
  • Verify capitalization, punctuation, and CSS escaping in the locator.
  • If the control is inside an iframe, switch to that frame before searching.
  • If it appears after an interaction, perform that interaction and use an explicit wait.

find_element raises NoSuchElementException

The locator had no match at that instant. Catch the exception only when absence is an expected outcome; otherwise fix the locator, page state, frame context, or synchronization.

The presence wait succeeds but clicking fails

Presence does not establish visibility or action readiness. Change to visibility_of_element_located, wait for an application-specific state, and check for overlays or disabled controls.

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

A previously found element becomes unusable

A re-render may detach the old node. Find it again after the update rather than relying on the earlier WebElement reference.

The wait times out intermittently

Capture the current URL and page state when it fails, verify that the selector is stable, and set a timeout that matches the application’s documented behavior. Do not replace synchronization with an arbitrary long sleep; a condition-based wait returns as soon as the required state exists.

Or skip the browser setup

If your goal is a rendered image rather than an interactive Selenium test, ScreenshotNeo provides a single HTTP request for a PNG, JPEG, WebP, or PDF. Its cleaner accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status.

See the parameter reference in the ScreenshotNeo documentation. cURL:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. Every feature is on every plan; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

Frequently Asked Questions

Does an empty list mean the selector is invalid?

No. It means no node matched at that moment; the selector may be valid while the page is on a different state or the element has not rendered yet.

Should I use a fixed sleep instead of WebDriverWait?

A condition-based explicit wait is generally more precise because it proceeds as soon as the required state is reached and fails with a defined timeout.

Can I check several possible locators?

Yes. Evaluate each locator with find_elements and stop at the first non-empty result, but keep the alternatives intentional so a changed page does not silently match the wrong element.

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

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.