skip to content

A Selenium FluentWait set to ignoring(WebDriverException.class) stopped failing on a flaky page. What did that hide?

level: seniorimportance: should knowfreq 37%

answer

  1. The base class of almost everything
  2. A blanket retry, not a targeted one
  3. Real bugs now arrive as timeouts
  4. Only the last throwable survives as the cause
  5. The list grows and never shrinks

basics

~20 s

Almost every Selenium error subclasses WebDriverException, so stale elements, intercepted clicks, bad selectors, script errors and even a dead session are now retried in silence. The only failure left is a TimeoutException that names nothing.

solid answer

~40 s

`WebDriverException` is the root of nearly every exception Selenium throws, so ignoring it turns the wait into a blanket retry. `StaleElementReferenceException`, `ElementClickInterceptedException`, `InvalidSelectorException`, `JavascriptException` and `NoSuchSessionException` all get swallowed, and only the first is a genuine settling condition — the rest are defects now reported as a `TimeoutException` after the full timeout. Diagnosis gets worse rather than better: `FluentWait` attaches only the **most recent** ignored throwable as the timeout's cause, and clears it whenever an attempt returns null or false, so one quiet attempt after a real error leaves a timeout with no cause at all. The list is also additive with no way to un-ignore, and `WebDriverWait` already has `NotFoundException` on it from its constructor. Name the narrow types you expect instead, and always add `withMessage`.

code

java · 29 lines
java
import java.time.Duration;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.ElementClickInterceptedException;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;

public final class QuoteRecalculation {

  public static void submitWhenSettled(WebDriver driver) {
    Wait<WebDriver> wait =
        new FluentWait<>(driver)
            .withTimeout(Duration.ofSeconds(15))
            .pollingEvery(Duration.ofMillis(500))
            .ignoreAll(
                List.of(
                    StaleElementReferenceException.class,
                    ElementClickInterceptedException.class))
            .withMessage("quote recalculation never finished, submit stayed blocked");

    wait.until(
        d -> {
          d.findElement(By.id("submit-quote")).click();
          return true;
        });
  }
}

go deeper

for a junior

Remember that ignoring lists exception types to retry on, and that widening it to a base class silently covers many unrelated failures. Keep the list to the specific types you expect.

for a middle

Explain the exception hierarchy underneath WebDriverException and why retrying an InvalidSelectorException costs a full timeout. Be able to say that the ignore list is additive and that WebDriverWait already carries NotFoundException.

for a senior

Show the diagnostic reasoning: a suite that only stabilised after a wide ignore is hiding a defect, and you can explain exactly why the resulting TimeoutException often carries no cause and no useful message.

for a principal

Own the policy question of where ignore lists are defined at all. A shared helper that adds a broad ignore removes every downstream team's ability to fail fast, and no local change can undo it.

## What ignoring() promises, and what it costs On Selenium's `FluentWait`, `ignoring(Class)` and `ignoreAll(Collection)` add throwable types to a list. When the polled function throws something on that list, the wait **records it and retries** instead of failing. Anything not on the list ends the wait immediately. The list is therefore a statement of intent: *these failures are expected while the page settles, everything else is a bug*. Widening it to `WebDriverException` throws that statement away, because `WebDriverException` is the **root of nearly every exception Selenium raises**. ## What a WebDriverException ignore actually swallows | Exception | What it really means | After the wide ignore | |---|---|---| | `StaleElementReferenceException` | the element was re-rendered under you | retried, often correctly | | `ElementClickInterceptedException` | an overlay is covering the target | retried until the timeout | | `InvalidSelectorException` | the locator string is malformed | retried until the timeout | | `JavascriptException` | the page's own script threw | retried until the timeout | | `NoSuchSessionException` | the browser session is gone | retried against a dead session | Only the first row is a genuine settling condition. The rest are defects, and after the wide ignore each of them produces the same outcome: the wait burns its whole timeout and reports a `TimeoutException` saying the condition never became true. On a rooftop-solar quote builder that means a typo in the locator for the annual-yield field and a genuinely broken yield calculation look identical in CI, and both take the full timeout to report. ## The list only ever grows Three properties of the ignore list surprise people: 1. **It is additive.** `ignoring` delegates to `ignoreAll`, which calls `addAll`. Calling it twice registers both sets. 2. **There is no un-ignore.** `FluentWait` exposes no method to remove a type or clear the list, so a wide entry added once in a base class or a helper cannot be narrowed downstream — the only fix is to stop adding it. 3. **`WebDriverWait` starts with an entry already there.** Its constructor calls `ignoring(NotFoundException.class)`, so anything you add stacks on top of that. Since `NoSuchElementException` extends `NotFoundException`, a not-found element is already retried before you configure anything. It also accepts more than exceptions. The parameter type is `Class<? extends Throwable>`, and the ignore check runs **before** `FluentWait` decides whether to rethrow — so an `Error`, an `AssertionError` included, can be put on the list and will be retried like anything else. That is occasionally useful and very easy to abuse. ## Why the TimeoutException is a poor consolation prize `FluentWait` keeps the last ignored throwable in a local variable and attaches it as the **cause** of the `TimeoutException`. That sounds like it preserves the evidence, but it is narrower than it looks: - the variable holds only the **most recent** throwable, not a history; - it is **cleared** every time the function returns `null` or `false`, on the reasoning that a falsy result, not the earlier exception, is what caused the timeout; - so an intermittent `ElementClickInterceptedException` followed by one quiet falsy attempt leaves you a `TimeoutException` with **no cause at all**. Meanwhile the message itself is only `Expected condition failed: waiting for <function>` plus `(tried for ... with ... milliseconds interval)` unless somebody called `withMessage`, and a lambda's `toString()` is a synthetic class name. A wide ignore plus no message is a failure report that names neither what broke nor what was being waited for. ## What to do instead - **Name the narrow types you actually expect.** `ignoring(StaleElementReferenceException.class)` says the quote panel re-renders while it recalculates. `ignoreAll(List.of(StaleElementReferenceException.class, ElementClickInterceptedException.class))` says the recalculation also throws a spinner over the button. - **Let the rest propagate.** A locator error or a script error should fail the test in milliseconds with its own type and stack trace, not after a full timeout as a `TimeoutException`. - **Always call `withMessage`.** Text such as `withMessage("annual-yield figure never refreshed after roof area changed")` survives into the failure and tells the next reader which step of the quote flow stalled. - **Re-read a wide ignore as a bug report.** If a suite only became stable once `WebDriverException` was ignored, the wait is not the fix; something in the application or the locator is failing and being retried into silence. ## One more trap: checked exceptions If the polled function throws a **checked** exception that is not on the ignore list, `FluentWait` cannot rethrow it through `until()`'s signature, so it wraps it in a plain `java.lang.RuntimeException`. The original is the cause, but the type you catch is not the type that was thrown — worth knowing before you write a `catch` clause around a wait.

  • Why does a TimeoutException sometimes arrive with no cause even though attempts were throwing?
    `FluentWait` keeps only the last ignored throwable, and it clears that variable whenever an attempt returns null or false. The reasoning is that a falsy result, not an older exception, is what caused the timeout. So an intermittent exception followed by one quiet attempt leaves the timeout with a null cause.
  • Can you remove a type from a FluentWait's ignore list once it has been added?
    No. `ignoring` delegates to `ignoreAll`, which calls `addAll`, and there is no remove or clear method on the class. A wide ignore added in a shared base class or helper cannot be narrowed downstream; the only fix is to stop adding it and rebuild the wait.
  • What happens if the polled function throws a checked exception that is not ignored?
    `FluentWait` cannot rethrow it through `until()`'s signature, so it wraps it in a plain `java.lang.RuntimeException` with the original as the cause. Errors and unchecked exceptions propagate unchanged. The practical consequence is that the type you catch around a wait may not be the type that was thrown.

saying these in an interview costs you the question

  • Treats a wider ignore list as a way to reduce failures
  • Thinks an ignored exception means the condition passed
  • Believes ignoring replaces a previously registered type
  • Assumes every ignored throwable is kept for the report
  • Expects a malformed locator to fail fast regardless of ignores