Selenium Expected Conditions: Examples and How to Use Them
Learn how Selenium Expected Conditions work, choose the right wait for elements and browser state, and handle timeouts with practical examples.
Selenium Expected Conditions are checks that an explicit wait repeatedly evaluates until a browser state is ready or the timeout expires. In Python, for example, use WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, "exampleId"))). The condition returns a useful value when it succeeds, such as a WebElement for a visibility check. Expected Conditions are not standalone sleeps: pair them with explicit waits. Selenium’s waits guide describes the approach; use the API reference for the binding and version in your project.
1. The explicit-wait pattern
An Expected Condition is a callable predicate over browser state. until polls it until it returns a truthy result, then returns that result. If it never succeeds before the timeout, Selenium raises TimeoutException. The example below follows the Python API documented by Selenium.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
# Install Selenium with: python -m pip install selenium
# Ensure a compatible browser is installed. Selenium Manager can handle
# driver setup for supported configurations.
driver = webdriver.Chrome()
try:
driver.get("https://www.selenium.dev/selenium/web/dynamic.html")
driver.find_element(By.ID, "reveal").click()
wait = WebDriverWait(driver, timeout=10)
revealed = wait.until(
EC.visibility_of_element_located((By.ID, "revealed"))
)
revealed.send_keys("Ready after the element became visible")
finally:
driver.quit()
The official documentation uses a reveal control followed by a visibility wait. The ten-second timeout here is an example, not a universal recommendation. Choose a limit that fits the application and test environment.
What the wait returns
presence_of_element_locatedandvisibility_of_element_locatedreturn the matching element when successful.- Text, title, URL, and state checks commonly return booleans.
until_notwaits for a falsey result; it is useful for waiting until a condition stops holding.
2. Choose a condition that matches the state you need
| Need | Condition | What success means |
|---|---|---|
| Element has been added to the DOM | presence_of_element_located(locator) |
Found in the DOM; it may be hidden. |
| Element is visible and has nonzero dimensions | visibility_of_element_located(locator) |
Displayed and usable for visual interaction. |
| At least one matching element is visible | visibility_of_any_elements_located(locator) |
Returns visible matches. |
| All matching elements are present or visible | presence_of_all_elements_located(locator) or visibility_of_all_elements_located(locator) |
All currently matching elements meet the selected condition. |
| Text appears in an element | text_to_be_present_in_element(locator, text) |
The displayed element text contains the requested text. |
| Element is ready for a click attempt | element_to_be_clickable(locator) |
Visible and enabled. It does not guarantee the application action will succeed. |
| Loading indicator disappears | invisibility_of_element_located(locator) |
Hidden or absent; a stale reference also counts as no longer visible. |
| Old node was removed by a rerender | staleness_of(element) |
That particular element is no longer attached. |
| Frame is ready | frame_to_be_available_and_switch_to_it(locator) |
Switches into the frame on success. |
| Alert appears | alert_is_present() |
Returns and switches to the alert. |
| New window appears | new_window_is_opened(current_handles) |
Detects an increase in window handles. |
| Title or URL matches | title_is, title_contains, url_to_be, url_contains |
Use exact or substring matching intentionally. |
The Python API also documents attribute and selection state conditions, along with all_of, any_of, and none_of combinators. Consult the Python Expected Conditions reference for exact signatures and return behavior.
3. Locator conditions versus WebElement conditions
A locator such as (By.ID, "save") tells Selenium how to find an element. A WebElement is an object already returned by a lookup. Locator based conditions can perform a fresh lookup during each poll, which is usually useful for pages that replace nodes during rerendering. Element based conditions keep inspecting the particular element you supplied; if the page detaches it, stale element behavior can occur.
# Re-find by locator during polling
save_button = wait.until(
EC.element_to_be_clickable((By.ID, "save"))
)
# Inspect one element already obtained
existing = driver.find_element(By.ID, "status")
visible_existing = wait.until(EC.visibility_of(existing))
Prefer a locator when the target may be recreated. Keep an existing element when the identity of that exact node matters, such as waiting for an old node to become stale after a rerender.
4. Waiting for multiple conditions or application state
When several states must hold together, Python’s all_of expresses an AND-like check. Use any_of when either of multiple outcomes is acceptable, and none_of when none may hold. These are Python API forms; do not assume their names or signatures are identical in other language bindings.
ready = wait.until(EC.all_of(
EC.visibility_of_element_located((By.ID, "results")),
EC.element_to_be_clickable((By.ID, "continue")),
))
# Accept either a success notice or a validation error
outcome = wait.until(EC.any_of(
EC.visibility_of_element_located((By.ID, "success")),
EC.visibility_of_element_located((By.ID, "validation-error")),
))
A custom function or lambda can check application state too. Keep each poll focused on observing state; avoid changing the application on every evaluation, because polling may invoke the condition repeatedly.
def results_loaded(driver):
rows = driver.find_elements(By.CSS_SELECTOR, "table.results tbody tr")
return rows if rows else False
rows = wait.until(results_loaded)
5. Timeout, polling, and implicit waits
The Python WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None) API documents timeout in seconds, a default poll interval of half a second, and NoSuchElementException as an ignored exception by default. Other exceptions generally propagate unless configured to be ignored. The wait ends when the condition is truthy, an unignored exception occurs, or the timeout is reached.
from selenium.common.exceptions import NoSuchElementException
wait = WebDriverWait(
driver,
timeout=12,
poll_frequency=0.25,
ignored_exceptions=(NoSuchElementException,),
)
button = wait.until(EC.element_to_be_clickable((By.ID, "continue")))
Usually keep ignored exceptions narrow. Ignoring broad exceptions can conceal broken locators, invalid sessions, or programming mistakes until they surface as a less informative timeout. Selenium warns that mixing implicit and explicit waits can lead to unpredictable combined timing. For tests built around Expected Conditions, leave the implicit wait at its default and use explicit waits for the states that need synchronization.
6. Language support: Python, Java, .NET, and Ruby
Expected Conditions are binding specific. The examples above use Python imports and condition names. Selenium’s guide documents Python and Java APIs, says .NET stopped supporting Expected Conditions in Selenium 4 to reduce maintenance and redundancy, and notes that Ruby commonly uses blocks, procs, and lambdas. Check the binding’s current official documentation before copying syntax across languages.
Java has its own WebDriverWait and ExpectedConditions APIs; do not paste the Python EC imports into Java. For .NET and Ruby, write an explicit wait using the idioms supported by the installed binding version rather than assuming the Python condition catalog exists.
7. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
TimeoutException |
The condition never became true, the locator is wrong, or the page did not reach the assumed state. | Check the locator and current page state; confirm the relevant frame or window; wait for the actual application signal. |
| Presence succeeds but interaction fails | Presence only proves DOM attachment, not visibility or enabled state. | Wait for visibility or clickability as appropriate. |
| Clickability succeeds but click is intercepted or has no effect | Another layer may cover the element, layout may shift, or application logic may not be ready. | Wait for the overlay to become invisible, then retry the intended interaction once the page state is stable. |
StaleElementReferenceException |
A rerender detached the element object being checked or used. | Use a locator based condition so Selenium can re-find the node, or intentionally wait for the old element to become stale before locating its replacement. |
| Frame contents cannot be found | The driver is still in the top-level document or wrong frame. | Wait for frame_to_be_available_and_switch_to_it, then locate the inner element. |
| Wait duration is much longer than expected | Implicit and explicit waits are both active, so lookups inside polling add time. | Use one deliberate synchronization strategy; avoid combining implicit and explicit waits. |
| Unexpected exception during polling | The exception is not in the wait’s ignored set. | Fix the underlying selector or state issue; add an ignored exception only when its transient nature is understood. |
8. Performance, reliability, and cost notes
Explicit waits reduce wasted fixed delays because they stop as soon as the target state is observed. Poll frequency trades lookup traffic against detection delay; very aggressive polling can add repeated remote WebDriver commands without making a slow application ready sooner. Choose reasonable timeouts for the application and CI environment, and make failure messages identify the state the test expected.
Reliability comes from waiting on a meaningful state rather than guessing how long a page takes. Presence, visibility, clickability, text, and URL each represent different contracts; use the narrowest one that reflects the next action. A successful condition proves only that predicate, not that every downstream operation will work.
Selenium itself does not price waits per poll in the cited documentation. Operational cost comes from the runtime and infrastructure executing the browser tests, so no general per-wait price can be stated from these sources.
9. Capture screenshots when a test fails
A screenshot can preserve the visible state that led to a failed assertion or timeout. With Selenium, capture it from the live browser session before quitting the driver:
try:
wait.until(EC.visibility_of_element_located((By.ID, "results")))
except Exception:
driver.save_screenshot("failure.png")
raise
For a website screenshot outside an interactive test session, ScreenshotNeo is a screenshot API and MCP server for developers. Its API accepts a URL and returns an image or PDF; the full options and setup are in the ScreenshotNeo documentation.
10. Or skip the browser setup
If the job is to capture a URL rather than drive a browser test, ScreenshotNeo takes one GET request. Create an API key, then run this cURL example (replace the target URL as needed):
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 removes cookie banners, newsletter popups, and chat widgets before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
11. FAQ
Should I use a fixed sleep instead?
Use a condition when the next step depends on a specific browser state. A fixed sleep always waits its full duration and does not establish that the state was reached.
Does clickable mean a click is guaranteed to work?
No. Selenium’s condition checks visibility and enabled state; overlays, layout changes, or application behavior can still prevent the intended result.
Can an Expected Condition change the page?
Some conditions have an effect on success: for example, the frame condition switches into the frame and the alert condition switches to the alert. Keep custom polling checks free of repeated side effects.
Are condition names identical in every Selenium language?
No. APIs and support differ by binding and Selenium version. Use the official documentation for the language in the project.
Where should I check for version-specific Python signatures?
Use the official Python API reference linked above and match it to the Selenium version installed in the project.


