In Selenium, why can a 15-second WebDriverWait fail only after about 20 seconds when a 10-second implicit wait is set?
answer
- Two timers, not one
- Who actually retries the find?
- Deadline checked after each poll, not during
- One poll that itself takes ten seconds
- Overshoot is implicit wait plus interval
basics
~20 sEach poll of the explicit wait runs a find that the remote end retries for the full implicit wait, and Selenium checks the deadline only after a poll returns, so the last poll overshoots it.
solid answer
~40 sIn Selenium 4, `WebDriverWait` is a `FluentWait`, and its `until` loop evaluates the condition, *then* checks the deadline, then sleeps 500 ms; the deadline check is deliberately last so a zero-timeout condition can still succeed. With `implicitlyWait(Duration.ofSeconds(10))` on the session, every `findElement` inside the condition blocks on the remote end for a full 10 seconds before it reports `no such element`, because the W3C find algorithm loops until its implicit-wait timer fires. So poll one runs from 0 s to 10 s, the 15-second deadline has not passed, the wait sleeps and polls again at 10.5 s, and that poll returns at 20.5 s — only then does the loop see the deadline and throw `TimeoutException`. The message still reads `tried for 15 seconds`, because it prints the configured timeout rather than elapsed time.
code
java · 28 linesimport java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public class RinkSlotWaitOvershoot {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
try {
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.get("https://rink.example/sessions");
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
long start = System.nanoTime();
try {
wait.until(ExpectedConditions.presenceOfElementLocated(By.id("book-friday-1830")));
} catch (TimeoutException e) {
long seconds = (System.nanoTime() - start) / 1_000_000_000L;
System.out.println("elapsed " + seconds + "s");
System.out.println(e.getMessage());
}
} finally {
driver.quit();
}
}
}go deeper
Recall that the two waits do not cooperate and that Selenium warns against running both. Being able to say a timeout can land later than the number you configured is enough at this stage.
Be ready to walk the timeline: where the implicit wait is spent, that FluentWait checks its deadline only after a poll returns, and that the overshoot is one implicit wait plus one polling interval.
An interviewer expects you to connect this to a run report: a step that logs a 15-second timeout but consumed 20 seconds of wall clock, and how you would confirm the session's implicit wait rather than trust the setup code.
Own the position that timeout numbers must be trustworthy across a suite. Argue why one retrying mechanism per step is a standard worth enforcing in the driver factory, not a per-test preference.
## Two retry loops, one inside the other A Selenium 4 test of an **ice-rink session booker** has two independent things that can retry an element lookup, and mixing them is what produces the strange arithmetic. - The **implicit wait** is a *session* setting. `driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10))` sends a W3C **Set Timeouts** command, and the remote end stores `10000` under the `implicit` member of the session's timeouts configuration. Every later element lookup in that session obeys it. - The **explicit wait** is a *client-side* loop. `new WebDriverWait(driver, Duration.ofSeconds(15))` is a `FluentWait<WebDriver>` living in your test process; it calls your condition, sleeps, and calls it again. Neither knows the other exists. The condition you hand the explicit wait calls `findElement`, and that one call is where the implicit wait is spent, so a single iteration of the outer loop contains a whole run of the inner one. ## What the remote end does with a find The W3C WebDriver **find** algorithm is itself a loop, not a single query: 1. Read the session's implicit wait timeout and start a timer with it. 2. Run the location strategy — for `By.cssSelector`, a `querySelectorAll` against the start node. 3. While the returned list is empty and the timer has not fired, go back to step 2. 4. Return the result; for **Find Element**, an empty result becomes a `no such element` error. So on a rink page where the slot `#book-friday-1830` never appears, a single `driver.findElement(By.id("book-friday-1830"))` does not fail fast. It holds the command for the full 10 seconds and only then answers. ## Why the client-side loop cannot stop on time `FluentWait.until` runs in this order, and the order is the whole story: 1. Evaluate the condition. 2. If it returned a non-null, non-`false` value, return that value. 3. **Then** compare the deadline against the clock and throw `TimeoutException` if it has passed. 4. Otherwise sleep for the polling interval — 500 ms by default, the `DEFAULT_SLEEP_TIMEOUT` constant `WebDriverWait` hands to `pollingEvery`. The deadline is checked *after* the evaluation deliberately, so that a wait with a zero timeout can still succeed when the condition is already true. The side effect is that the wait can never interrupt an evaluation already in flight. If one evaluation blocks for 10 seconds, the wait blocks for 10 seconds, whatever its own timeout says. ## The arithmetic on the rink page With a 10-second implicit wait and a 15-second `WebDriverWait`, on a slot that is not there: | Elapsed | What happens | |---|---| | 0.0 s | poll 1 calls `findElement`; the remote end starts its 10 s timer | | 10.0 s | poll 1 receives `no such element`; the wait ignores it, because `WebDriverWait` is constructed with `ignoring(NotFoundException.class)` | | 10.0 s | deadline check: 10 s is under 15 s, so the loop continues | | 10.5 s | poll 2 starts, after the 500 ms interval | | 20.5 s | poll 2 receives `no such element`; deadline check: 20.5 s is past 15 s | | 20.5 s | `TimeoutException` is thrown | That is exactly the case Selenium's own documentation warns about — an implicit wait of 10 and an explicit wait of 15 producing a timeout after 20. The general rule is that the overshoot is about **one implicit wait plus one polling interval**, not the sum of the two timeouts. Widening the explicit timeout to 30 seconds does not fix it; it only buys another 10.5-second step before the same overshoot happens. ## What the failure message says, and what it hides `FluentWait` builds its message from the *configured* fields, never from measurement: - it prints `tried for 15 seconds with 500 milliseconds interval`; - it never prints elapsed time; - the suppressed `lastException` it attaches is the ignored `no such element` — the thing that actually consumed the run. A suite that mixes waits therefore reports a 15-second timeout on a step that took 20.5 seconds, and that number is the one number you cannot trust when you are accounting for a run's wall-clock time. ## Removing the compounding There is no ratio of the two timeouts that composes cleanly, so tuning them against each other is wasted effort. Set the implicit wait to `Duration.ZERO` — which is also the W3C default — so that a find returns as soon as the location strategy yields an empty list, and let the explicit wait own all of the retrying. With a zero implicit wait the same failure lands just past 15 seconds, each poll costs one round trip, and the reported timeout matches the observed one.
- Does making the implicit wait shorter than the explicit timeout remove the overshoot?No. The overshoot is about one implicit wait plus one polling interval regardless of the ratio, because the client-side loop cannot interrupt a find already in flight. A 2-second implicit wait under a 15-second explicit wait still times out near 17 seconds. Only `Duration.ZERO` removes it.
- Where does the 500 ms in that timeline come from, and would shrinking it help?`FluentWait.DEFAULT_SLEEP_TIMEOUT` is 500 ms, and `WebDriverWait` passes it to `pollingEvery` as the interval. Shrinking it barely helps here: the cost is the blocking find inside each poll, not the sleep between polls. It would trim the overshoot by fractions of a second.
- Why does the TimeoutException message disagree with the stopwatch?`FluentWait` formats its message from the configured `timeout` and `interval` fields, not from measurement, so it always reports `tried for 15 seconds with 500 milliseconds interval`. The suppressed cause attached to it is the ignored `no such element` error that actually consumed the time.
saying these in an interview costs you the question
- Says the two timeouts simply add to 25 seconds every time
- Thinks the explicit wait overrides or cancels the implicit wait
- Believes the implicit wait is a client-side sleep in the bindings
- Assumes a wait can abort a find that is already in flight
- Reads elapsed run time straight off the TimeoutException message