Skip to content
QA Workflow Assistant

Blog

Selenium Waits Explained: Implicit, Explicit, and Fluent Waits

Understand Selenium waits—implicit, explicit, and fluent—with practical Java examples that reduce flaky UI tests and make synchronization intentional.

QA Workflow Assistant8 min read
  • selenium
  • waits
  • test-automation
  • flaky-tests
  • qa

Selenium waits are how you tell WebDriver what “ready” means. Get them wrong and you collect flaky tests: passes on a warm laptop, fails in CI, green after a blind rerun. Get them right and your suite fails for product reasons—not because a button needed another 200 milliseconds.

This guide explains implicit, explicit, and fluent waits in Selenium, shows Java examples, and maps waits to Page Objects and regression practice. For broader context, see the automation testing guide, Selenium WebDriver tutorial, and Java Selenium tutorial.

Why waits exist

Browsers are asynchronous. Clicking “Save” may trigger XHRs, re-render a React tree, swap routes, or open a modal after an animation. WebDriver can find an element in the DOM before it is visible, enabled, or stable.

Sleeping with Thread.sleep(5000) papers over races and slows every run. Selenium waits poll for a condition with a timeout. That is the difference between synchronization and superstition.

Think in signals, not seconds. A human does not wait “three seconds”; they wait until the spinner disappears, the Save button enables, or the success toast appears. Your explicit wait should name that same signal. When the product does not expose a clear signal, lobby for a data-testid or fix the flow—do not paper over it forever with longer sleeps.

Page load timeouts vs element waits

Selenium also offers page load and script timeouts:

driver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(30));
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(20));

These bound navigation and async script execution. They do not replace explicit waits for in-page widgets. A document that reached complete can still be waiting on client-side data. Use page load timeouts as a safety net for hung navigations; use explicit waits for UI readiness.

Implicit waits

An implicit wait tells WebDriver how long to keep trying when it cannot immediately find an element with findElement.

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));

What it does well

  • Simple default for very small scripts.
  • Slightly less boilerplate for naive find-and-click flows.

What goes wrong

  • Global and sticky — applies to every find, including checks that an element is absent.
  • Stacks poorly with explicit waits — mixing both often creates confusing effective timeouts.
  • Hides design problems — people crank the number instead of waiting for the real condition (clickable, text present, URL changed).

Modern guidance: prefer explicit waits for real suites. If you leave an implicit wait, keep it at zero or a very small value and document why.

Explicit waits

An explicit wait polls until a named condition is true or the timeout expires. You scope it to a situation.

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
 
import java.time.Duration;
 
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement submit = wait.until(
    ExpectedConditions.elementToBeClickable(By.id("submit"))
);
submit.click();

Common conditions

ConditionTypical use
visibilityOfElementLocatedElement should appear
elementToBeClickableReady for interaction
invisibilityOfElementLocatedSpinner / overlay gone
textToBePresentInElementLocatedStatus message shown
urlContains / titleContainsNavigation finished
frameToBeAvailableAndSwitchToItEnter iframe safely
numberOfElementsToBeMoreThanList finished loading

Choose the condition that matches the user-visible readiness signal. Waiting only for “present in DOM” is often too weak for SPAs.

Custom timeout per risk

Not every wait needs ten seconds. Login form appearance might use 5s; a report export might need 60s. Prefer intentional timeouts over one global hammer.

WebDriverWait shortWait = new WebDriverWait(driver, Duration.ofSeconds(5));
WebDriverWait longWait = new WebDriverWait(driver, Duration.ofSeconds(60));
 
shortWait.until(ExpectedConditions.visibilityOfElementLocated(By.id("email")));
longWait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("[data-testid='export-ready']")));

Fluent waits

FluentWait is the configurable engine behind many explicit waits. You control timeout, polling interval, ignored exceptions, and the condition.

import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.FluentWait;
 
import java.time.Duration;
import java.util.function.Function;
 
FluentWait<WebDriver> fluentWait = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(20))
    .pollingEvery(Duration.ofMillis(400))
    .ignoring(NoSuchElementException.class);
 
WebElement banner = fluentWait.until(new Function<WebDriver, WebElement>() {
    public WebElement apply(WebDriver d) {
        return d.findElement(By.cssSelector("[data-testid='toast-success']"));
    }
});

Or with a lambda:

WebElement banner = fluentWait.until(
    d -> d.findElement(By.cssSelector("[data-testid='toast-success']"))
);

Use fluent waits when you need custom polling or ignored exceptions. For everyday cases, WebDriverWait + ExpectedConditions stays clearer for reviews.

Implicit vs explicit vs fluent (quick compare)

KindScopeStrengthRisk
ImplicitGlobal find timeoutEasy defaultMasks absence checks; mixes badly
Explicit (WebDriverWait)One conditionClear intentEasy to overuse vague conditions
FluentCustom polling / ignoresFlexibleVerbose if overused

Rule of thumb: explicit by default, fluent when you need control, implicit near zero in serious frameworks.

Anti-patterns that create flakes

  1. Thread.sleep everywhere — slow and still racy.
  2. Huge implicit wait + explicit wait — timeouts become unpredictable.
  3. Waiting for presence only — then clicking a covered element.
  4. No wait after navigation — asserting on the previous page’s leftovers.
  5. Waiting on animations with sleeps — wait for the end state instead.
  6. Swallowing TimeoutException and continuing — failures become mysteries later.

When debugging, ask: what would a careful human wait to see before the next step? Encode that.

Waits inside Page Objects

Centralize readiness in page objects so tests stay readable. Pattern:

public class DashboardPage {
    private final WebDriver driver;
    private final WebDriverWait wait;
    private final By heading = By.cssSelector("h1");
 
    public DashboardPage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }
 
    public DashboardPage waitUntilLoaded() {
        wait.until(ExpectedConditions.visibilityOfElementLocated(heading));
        wait.until(ExpectedConditions.textToBePresentInElementLocated(heading, "Dashboard"));
        return this;
    }
 
    public boolean isLoaded() {
        waitUntilLoaded();
        return driver.findElement(heading).getText().contains("Dashboard");
    }
}

This pairs cleanly with the Page Object Model: pages own synchronization for their screen; tests own business assertions.

Expected conditions for AJAX and SPAs

For SPAs, prefer signals you control:

  • data-testid markers that appear when data finished loading
  • Network-level setup via API before UI (often better than waiting on spinners)
  • Disappearance of a known loading overlay
By spinner = By.cssSelector("[data-testid='loading']");
wait.until(ExpectedConditions.invisibilityOfElementLocated(spinner));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("[data-testid='results-table']")));

If the only spinner selector is a generic CSS class shared by five widgets, tighten the locator—or fix the app to expose test IDs.

Timeouts, failures, and messages

A timeout should fail loudly with context:

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.withMessage("Submit button never became clickable on checkout step 2");
wait.until(ExpectedConditions.elementToBeClickable(By.id("place-order")));

Good messages shorten triage. Attach screenshots in CI on TimeoutException. Treat recurring timeouts as defects in synchronization or environment—same discipline as other flaky tests.

Relating waits to suite design

Waits are not a substitute for good candidate selection. Unstable third-party widgets may not belong in the merge gate. Put critical synchronized journeys in smoke and regression test cases; keep brittle edges in a quarantined pack or cover them with edge case testing notes and manual exploration until the product exposes better hooks.

API-first setup also reduces UI waiting: create the order via API, open the confirmation URL, wait only for the confirmation heading.

Click interception and stale elements

Two errors show up constantly when waits are weak or misplaced:

ElementNotInteractable / click intercepted — the node exists but a loader, cookie banner, or sticky header sits on top. Wait for the overlay to go invisible, then wait for clickable, then click. Sometimes you need to scroll into view first.

StaleElementReferenceException — you kept a WebElement reference across a re-render. Prefer locating by By inside the wait condition so each poll gets a fresh node. Page Factory fields are a common source of surprise here; plain locators inside methods behave more predictably. Structure guidance: Page Object Model.

wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector("[data-testid='route-loader']")));
WebElement save = wait.until(ExpectedConditions.elementToBeClickable(By.id("save")));
save.click();

Minimal “do this instead” cheat sheet

TemptationPrefer
Thread.sleep(3000)elementToBeClickable / visibility condition
Implicit 30s globalImplicit 0 + explicit per step
Wait for div presentWait for role/test id visible and enabled
Rerun until greenFix condition; quarantine if needed
One 120s wait for everythingRight-sized waits per operation
Reuse a stored WebElement after navigationRe-find via By inside until(...)

FAQ

Should I disable implicit waits completely?

For most maintainable Selenium frameworks, yes—set implicit wait to zero and use explicit waits. That keeps timing predictable.

Is FluentWait required?

No. Use it when you need custom polling or ignored exceptions. Otherwise WebDriverWait is enough.

Why does my explicit wait still flake?

Often the condition is wrong (presence vs clickable), the locator matches multiple nodes, an overlay intercepts clicks, or parallel tests fight over data. Fix the signal, not only the timeout number.

Do Playwright and Cypress need the same patterns?

They auto-wait more aggressively, but the idea is identical: wait for a meaningful condition, not a fixed sleep. Selenium makes the condition explicit in your code—which is why this topic matters so much for WebDriver suites.

Where should waits live in the repo?

Prefer page objects or small wait helpers, not copy-pasted WebDriverWait blocks in every test. See Page Object Model.

How do waits relate to CI failures?

CI is slower and colder-cache than local runs. Condition-based waits with honest timeouts beat inflated sleeps. Keep smoke lean so wait budget stays visible—strategy in the automation testing guide.

Conclusion

Selenium waits are a design choice: implicit as a blunt global, explicit as the daily driver, fluent when you need knobs. Prefer clear conditions, right-sized timeouts, and page-level readiness methods. That combination cuts flakes without turning every test into a sleep festival.

Continue with the Selenium WebDriver tutorial or Java Selenium tutorial for setup, and the Page Object Model guide for structure. When you are turning Jira stories into structured cases before automation, try QA Workflow Assistant—details in Docs and Pricing.

Stay ahead in QA

Get practical QA guides, Jira & Xray tutorials, testing checklists, AI testing insights, and occasional product updates.

No spam. Unsubscribe anytime.