Fluent Wait in Selenium: An Automation Engineer's Guide
Learn with AI
X (Twitter)
Learn with AI
Most articles on Fluent Wait hand you the same five lines of boilerplate and move on. This one is written from the other side of it, after enough flaky suites to know where Fluent Wait actually saves you and where it quietly creates new problems.
If you have ever watched a test pass ten times and fail on the eleventh CI run for no reason you can reproduce locally, you already understand the problem Fluent Wait is built to solve. Pages are asynchronous. Elements arrive late. The DOM gets rebuilt under your feet. A good wait strategy is the difference between a suite people trust and one they learn to ignore.
Here is what we will cover:
- What Fluent Wait actually is, including the part most guides get wrong
- The real difference between implicit, explicit, and fluent waits
- Working examples in Java and Python, explained line by line
- The mistakes that make Fluent Wait flaky in practice
- When Fluent Wait is the wrong tool
- How Katalon Studio handles the same problem with Smart Wait, and the trade-off involved
What is Fluent Wait in Selenium?
Fluent Wait is a configurable wait that polls for a condition at a set interval until the condition is true or a timeout is reached, while ignoring exceptions you choose to tolerate during polling. You control three things: the maximum time to wait, how often to check, and which exceptions to swallow while checking.
Here is the part most guides skip. In Java, WebDriverWait is a subclass of FluentWait. When you write a standard explicit wait, you are already using a Fluent Wait under the hood, just with default settings (a 500 millisecond polling interval and no ignored exceptions beyond the ones the condition implies). Reaching for FluentWait directly is what you do when those defaults are not enough, for example when you need to slow the polling down for an expensive check or ignore a specific exception class while the page settles.
That framing matters because it tells you when to bother. If the defaults work, use WebDriverWait. Use FluentWait directly when you have a concrete reason to change the polling rhythm or the exception handling.
Fluent Wait vs implicit and explicit waits
The three wait types are not interchangeable, and the most common source of flaky timing is treating them as if they were. Here is how they actually differ.
| Wait type | Scope | What it does | Reach for it when |
|---|---|---|---|
| Implicit wait | Global, whole session | Sets a default poll time the driver applies to every findElement call before throwing NoSuchElementException | You want a simple baseline and nothing more. Set it once, then leave it alone. |
| Explicit wait | One element or condition | Waits for a specific ExpectedCondition (visible, clickable, text present) up to a timeout | You are waiting on a known condition for a known element. This is the default tool for most waits. |
| Fluent wait | One element or condition | An explicit wait with manual control over polling frequency and ignored exceptions | The defaults are not enough. You need a custom poll interval, a custom condition, or to ignore a specific exception while polling. |
A note on Thread.sleep(). It works, and every codebase has a few. The problem is that it always waits the full duration even when the element was ready in 200 milliseconds, so it trades reliability for wasted time on every run. Across a large suite that adds up fast. Treat it as a last resort, not a wait strategy.
Fluent Wait syntax in Java, explained
This is the canonical Java setup.
Wait<WebDriver> wait = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(20))
.pollingEvery(Duration.ofMillis(500))
.ignoring(NoSuchElementException.class)
.withMessage("Submit button never became clickable");
WebElement submit = wait.until(
ExpectedConditions.elementToBeClickable(By.id("submit"))
);
submit.click();
Breaking it down:
- withTimeout: sets the ceiling. After 20 seconds with no success, the wait throws a TimeoutException.
- pollingEvery: sets the rhythm. Here it checks every 500 milliseconds, which is also the default. Widen it for expensive checks, tighten it for snappy UIs.
- ignoring: tells the wait which exceptions to treat as "not ready yet" rather than "fail now." NoSuchElementException is the common one. StaleElementReferenceException is the other you will reach for constantly on dynamic pages.
- withMessage: the line almost nobody includes and everybody wishes they had at 2am. Without it, a timeout gives you a generic stack trace. With it, your failure says exactly what the test was waiting for. Add it to every non-trivial wait.
You can chain multiple ignoring calls or pass several exception types, which is the standard pattern for AJAX-heavy pages where elements get re-rendered mid-poll:
Wait<WebDriver> wait = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(20))
.pollingEvery(Duration.ofMillis(250))
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class);
Fluent Wait in Python: it is already built into WebDriverWait
Here is a point that trips up people moving between languages. Python's Selenium binding has no separate FluentWait class. You do not need one, because WebDriverWait already is the fluent implementation. It takes poll_frequency and ignored_exceptions as constructor arguments.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException
wait = WebDriverWait(
driver,
timeout=20,
poll_frequency=0.5,
ignored_exceptions=[NoSuchElementException, StaleElementReferenceException],
)
submit = wait.until(EC.element_to_be_clickable((By.ID, "submit")))
submit.click()
So if you are reading a Java tutorial and wondering where the Python FluentWait went, that is the answer. In Python, every WebDriverWait is a fluent wait. The default poll_frequency is 0.5 seconds, the same 500 milliseconds as Java. Set the arguments and you have full fluent behavior with no extra class.
A real example: waiting on an AJAX result
Toy examples wait for a button by id and call it a day. Real pages are messier. Here is a scenario closer to what breaks in practice: you submit a search, a spinner appears, results load asynchronously, and the rows you want get rendered and sometimes re-rendered as data streams in. A plain findElement will either miss the element or grab a stale reference.
Wait<WebDriver> wait = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(15))
.pollingEvery(Duration.ofMillis(300))
.ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class)
.withMessage("First search result never became clickable");
// Kick off the async search
driver.findElement(By.id("search")).sendKeys("fluent wait" + Keys.ENTER);
// Wait for the spinner to disappear, then for the first result to be ready
wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".loading-spinner")));
WebElement firstResult = wait.until(
ExpectedConditions.elementToBeClickable(By.cssSelector(".results .result:first-child a"))
);
firstResult.click();
Two things make this resilient. Ignoring StaleElementReferenceException lets the wait keep polling while the results list rebuilds instead of failing the instant the DOM changes. And waiting for the spinner to go before waiting for the result mirrors how a human reads the page, which is usually the right mental model for a wait.
The mistakes that make Fluent Wait flaky
This is the section that took the longest to learn and gets written about the least.
- Mixing implicit and explicit waits. This is the big one. The Selenium documentation itself warns against combining the two, because the wait times can compound in ways that are hard to predict. A 10 second implicit wait plus a 15 second explicit wait does not reliably give you 15 seconds. You can end up with waits far longer than either value, and timing that changes between drivers. Pick one model. If you use explicit or fluent waits, set your implicit wait to zero and leave it there.
- Polling too aggressively. A 50 millisecond poll on a condition that hits the DOM or runs JavaScript can hammer the browser and slow the whole test down rather than speeding it up. Match the poll interval to how expensive the check is. Cheap visibility check, poll fast. Expensive condition, poll slower.
- Ignoring the wrong exceptions. ignoring is a loaded gun. If you ignore an exception that actually signals a real failure, your test will sit there polling until it times out and then fail with a misleading message. Only ignore the exceptions you expect during normal polling, usually NoSuchElementException and StaleElementReferenceException.
- Skipping withMessage. A wait without a message is a debugging session you are scheduling for future you.
- Reusing one wait object with very different timeouts in mind. A wait is configured once. If different steps need very different timeouts, create separate wait objects with clear names rather than reaching for the same one and hoping.
When Fluent Wait is the wrong tool
Honesty is faster than discovery. Fluent Wait is not always the answer.
- When WebDriverWait defaults already work, using FluentWait directly is just extra code to maintain. Reach for it only when you need non-default polling or exception handling.
- When the real problem is a missing application signal, no wait fixes it. If an element never reaches a stable state because of a front-end bug, a longer timeout just delays the failure.
- When you are waiting on a backend job (an email to send, a queue to drain, a report to generate), a UI-level Fluent Wait is the wrong layer. Poll the API or the queue, not the DOM.
- When you find yourself writing the same wait around every single action, that is a signal. At that point you are hand-maintaining timing logic that a framework can handle for you, which is the right moment to look at the next section.
How Katalon Studio handles the same problem
Everything above is the manual approach, and sometimes the manual approach is exactly right. You get full control, and on a small or highly custom suite that control is worth the boilerplate. The cost shows up at scale: you are writing and maintaining wait logic around dynamic elements across hundreds of tests, and every one of those waits is a thing that can drift, break, or get copy-pasted slightly wrong.
Katalon Studio takes a different default. Its Smart Wait feature waits for the page to reach a static state, meaning the relevant front-end processes (pending AJAX calls, JavaScript, resource loading) have settled, before it performs the next action. Instead of you deciding element by element when the page is ready, Smart Wait makes "wait until the page actually stops changing" the baseline for every step.
You can still get explicit when you want it. Studio's built-in keywords cover the common conditions without the boilerplate:
WebUI.waitForElementVisible(findTestObject('Page/btn_submit'), 20)
WebUI.waitForElementClickable(findTestObject('Page/btn_submit'), 20)
WebUI.waitForElementPresent(findTestObject('Page/result_row'), 20)
And you can scope Smart Wait dynamically when a particular flow needs it:
WebUI.enableSmartWait()
// steps where you want the page-settled wait applied
WebUI.disableSmartWait()
Smart Wait is on by default under Project > Settings > Execution > WebUI. Since Katalon Studio 10, and through the current 11.x line, it runs over the browser's BiDi protocol on Chrome, Edge, and Firefox, so it no longer needs a separate browser extension there. Under the hood, Studio 11 runs on Selenium 4.39 and Java 21, so you are not trading away the engine you already know.
The honest framing is this. You can build every bit of this in raw Selenium with FluentWait, and for some suites you should. What Smart Wait buys you is not the removal of waiting logic, it is the removal of having to hand-write and hand-maintain the page-readiness wait for every dynamic element across a growing suite. If your team spends more time fixing brittle waits than writing new tests, that is the trade worth evaluating. You keep the ability to drop into explicit waits and into Groovy whenever a custom condition demands it.
And when a wait does fail, the part that usually eats your afternoon is reproducing it. Studio 11 ships a Tracer that records DOM snapshots, network activity, console logs, and browser state for a run, so a flaky timing failure leaves evidence behind instead of forcing you to guess at what the page was doing when the step ran.
If you want to see it on a real AJAX page, the Smart Wait sample project is on GitHub, and the Smart Wait documentation covers the configuration in full.
|
FAQs
What is Fluent Wait in Selenium?
A wait that repeatedly checks for a condition at custom polling intervals until the condition is met or a maximum timeout is reached, with control over ignored exceptions.
When should Fluent Wait be used?
For dynamic content that loads at different speeds, elements that appear unpredictably, and situations where you want to reduce flaky tests without relying on fixed waits.
How is Fluent Wait different from implicit and explicit waits?
Implicit wait applies a global fixed wait, explicit wait targets a specific element/condition, and Fluent Wait adds granular control over timeout, polling frequency, and exception handling.
What are the key components of Fluent Wait?
Timeout period, polling frequency, ignored exceptions (like NoSuchElementException and StaleElementReferenceException), and the condition to evaluate (built-in or custom).
How does Katalon simplify wait handling compared to writing Fluent Wait code?
It provides built-in wait keywords like WebUI.waitForElementVisible(), WebUI.waitForElementClickable(), and WebUI.waitForElementPresent() so you can wait without managing polling intervals and exceptions manually.