Locators and waits

Choosing stable locators, and the difference between implicit and explicit waits that causes most flaky Selenium suites.

Finding elements

StrategyExampleUse it when
idBy.ID, 'submit-btn'The id is unique and stable - the best choice
CSS selectorBy.CSS_SELECTOR, 'ul.rows > li:first-child'There is no usable id and the structure is meaningful
XPath//button[normalize-space()='Save']You must match visible text or walk up to a parent
Link textBy.LINK_TEXT, 'Sign in'Anchor elements with stable labels
Class nameBy.CLASS_NAME, 'card'Rarely - classes churn whenever styling changes
Tag nameBy.TAG_NAME, 'tr'Collecting many elements, never a single one
from selenium.webdriver.common.by import By

# stable: a hook the application owns
driver.find_element(By.CSS_SELECTOR, "[data-testid='checkout']").click()

# brittle: derived from layout and position
driver.find_element(By.XPATH, "/html/body/div[3]/div[2]/button[1]").click()

Prefer a hook the application controls, such as an id or a data-testid attribute, over anything derived from layout or styling. If no stable hook exists, adding one to the application is cheaper than maintaining the selector forever.

Implicit versus explicit waits

An implicit wait tells the driver to poll for a fixed time on every find_element call. An explicit wait polls one specific condition and gives up with a clear error. Mixing the two produces timeouts that are hard to reason about.

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)

# wait for one condition on one element, then continue
element = wait.until(EC.element_to_be_clickable((By.ID, "confirm")))
element.click()

wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".toast")))
wait.until(EC.number_of_windows_to_be(2))
  • Keep the implicit wait at zero and express every wait explicitly - one mechanism, one timeout.
  • Wait for the condition you actually need: clickable, visible, present, or a count.
  • Never use a fixed sleep. It is either too slow or too short, and it never becomes correct.
  • Set a longer timeout for the first navigation, when the application is still warming up.
💡
Flakiness is usually a race, not a slow machine. If a test fails one run in twenty, find the missing condition instead of raising the timeout - a longer timeout only makes the failure rarer and later.

FAQ

Should I ever use an implicit wait?
Generally no. Keep it at zero and express each wait explicitly; the failure messages are clearer and the timing is predictable.
Why does find_element raise instead of returning None?
find_element raises NoSuchElementException, while find_elements returns an empty list. Use the plural form when absence is a valid outcome.

WebDriver setup A real test example

Last refreshed 2026-09-18.