skip to content

In Selenium's ExpectedConditions, how do or(), and(), not() and refreshed() compose other conditions?

level: seniorimportance: must knowfreq 48%

answer

  1. Conditions that take conditions
  2. Any branch versus every branch
  3. One of them swallows a throwing branch
  4. Combining collapses the result to Boolean
  5. The stale guard keeps the inner type

basics

~20 s

or() passes when any branch is satisfied, and() when every branch is, and not() when the wrapped one is not. All three yield a Boolean, losing any element. refreshed() wraps a single condition, keeps its return type, and absorbs staleness so the poll retries.

solid answer

~40 s

`ExpectedConditions.or(...)` sweeps its branches each poll and succeeds on the first one that returns `TRUE` or a non-null non-Boolean value; it catches a `RuntimeException` from a branch, stores it for the failure message, and moves on. `and(...)` needs every branch and stops at the first that yields `null` or `FALSE`, recording that index — but it does not catch, so a throwing branch propagates into the wait. `not(...)` inverts one condition, and it does not catch either, which is why an ignored exception inside it silently defeats the inversion. All three declare `Boolean`, so composing element-returning conditions loses the element and you must locate it again. `refreshed(...)` is different: it wraps one condition, preserves its type, and converts staleness or not-found into another poll.

code

java · 9 lines
java
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));

wait.until(ExpectedConditions.or(
    ExpectedConditions.visibilityOfElementLocated(By.id("placement-result")),
    ExpectedConditions.visibilityOfElementLocated(By.cssSelector(".grading-error"))));

// or(...) returned Boolean.TRUE, so the node has to be located again
WebElement outcome =
    driver.findElement(By.cssSelector("#placement-result, .grading-error"));

go deeper

for a junior

Know that these four exist and that or, and and not take other conditions and give back a true-or-false condition. Recognising them in a colleague's wait is enough at this stage.

for a middle

Explain the evaluation order: or stops at the first satisfied branch, and stops at the first unsatisfied one and remembers its index, and both report a Boolean rather than an element.

for a senior

Demonstrate the operational difference. Say which combinator absorbs a throwing branch and which lets it escape into the wait, and show that you re-locate the node after a composed wait.

for a principal

Own the guidance for a suite: when a composed wait genuinely models an either-or product outcome, and when it is hiding the fact that nobody decided what 'ready' means for that screen.

## Why the shipped set needs combinators Selenium 4's `ExpectedConditions` class carries dozens of single-purpose predicates, but a real page rarely settles into one state. On a language-school placement quiz, submitting the last answer ends in **either** a scored result panel **or** a grading-failed banner, and the wait has to end on whichever arrives. Four factories exist for exactly this shaping problem: `or(...)`, `and(...)`, `not(...)` and `refreshed(...)`. All four take conditions and return conditions, so they nest. ## or(...) — first branch to succeed wins `ExpectedConditions.or(ExpectedCondition<?>...)` walks its branches in order on every poll. A branch counts as satisfied when it returns `Boolean.TRUE`, or when it returns any non-`null` value that is not a `Boolean` — which is how an element-returning branch such as `visibilityOfElementLocated` participates. The first satisfied branch ends the sweep and `or` returns `true`. Two details matter in production: - **`or` swallows a `RuntimeException` from a branch.** It catches the throwable, stores it in place of that branch's result so it can be printed in the failure message, and carries on to the next branch. A branch that throws therefore does not abort the wait. - **`or` returns a `Boolean`, not the element.** Whichever branch matched, the value that reaches you is `true`. You have to locate the node yourself afterwards, which is the single most common surprise with this combinator. Three habits keep `or` honest in a suite: - Give every branch a locator that is genuinely reachable, since a branch that can never match simply never contributes. - Order the branches so the expected outcome is first, which keeps the sweep cheap on the happy path. - Treat the failure outcome as a real branch rather than something you discover from a timeout. ## and(...) — every branch, every poll `ExpectedConditions.and(ExpectedCondition<?>...)` evaluates its branches in order and returns `false` as soon as one yields `null` or `Boolean.FALSE`, remembering which index failed so the timeout message can name it. Only when every branch passes does it return `true`. Its exception behaviour is the mirror image of `or`: **`and` does not catch anything**. A `RuntimeException` from a branch propagates straight out of the condition into `FluentWait.until`, where it is either matched against the wait's ignore list and retried, or rethrown immediately. Like `or`, it collapses the result to a `Boolean`. ## not(...) — inverting a single condition `ExpectedConditions.not(ExpectedCondition<?>)` applies the wrapped condition once and returns `true` when the result is `null` or `Boolean.FALSE` — that is, when the inner condition was *not* satisfied. It is how you express "the level selector is no longer selected" without a dedicated helper. The caveat is written into its own documentation: `not` does not catch exceptions either. If the inner condition throws something the wait is configured to ignore, the wait swallows it and simply polls again, so the inversion never happens and the wait can time out on a condition you believe is already true. ## refreshed(...) — a guard, not a boolean operator `ExpectedConditions.refreshed(ExpectedCondition<T>)` is the odd one out. It wraps **one** condition and returns whatever that condition returns — so it preserves the element type instead of collapsing to `Boolean`. Its job is to catch `StaleElementReferenceException` and `NoSuchElementException` from the inner condition and return `null`, which the polling loop reads as "not yet" and retries. That matters because `WebDriverWait` ignores `NotFoundException` by default, and `StaleElementReferenceException` does **not** extend `NotFoundException` — it extends `WebDriverException` directly. So a condition that finds an element and then reads something off it can escape the wait with a staleness error rather than a `TimeoutException`. `refreshed` turns that escape into another poll. | Factory | Arity | Catches exceptions from a branch? | Result type | |---|---|---|---| | `or(...)` | many | yes, any `RuntimeException`, then continues | `Boolean` | | `and(...)` | many | no, it propagates | `Boolean` | | `not(...)` | one | no, it propagates | `Boolean` | | `refreshed(...)` | one | staleness and not-found only | the inner condition's type | ## Using them without being surprised 1. Compose with `or` when two outcomes are both acceptable endings, then re-locate the element you actually want. 2. Compose with `and` when a screen is only ready once several independent things are true, and accept that one throwing branch ends the poll. 3. Wrap with `refreshed` when the inner condition does a find *and then* a read on what it found. 4. Reach for `not` only for a genuine inversion, and check what your wait ignores before you rely on it. A naming note for readers who switch bindings: the Java factories are `or`, `and` and `not`, while Python's `selenium.webdriver.support.expected_conditions` module spells the same three ideas `any_of`, `all_of` and `none_of`.

  • Why does or() succeed on a branch that returns a WebElement rather than a Boolean?
    Its per-branch test accepts `Boolean.TRUE`, or any non-null result whose type is not `Boolean`. That second clause is what lets element-returning and list-returning conditions take part, since their waiting value is `null` and their success value is an object.
  • You wrap a condition in not() and the wait still times out even though the state clearly flipped. What happened?
    `not` applies the inner condition and inverts only its returned value. If the inner condition throws something the wait is configured to ignore, the exception never reaches `not`, the poll simply repeats, and no inversion is ever computed. Check the wait's ignore list before relying on `not`.
  • Why does refreshed() exist when WebDriverWait already ignores exceptions by default?
    `WebDriverWait` ignores `NotFoundException` by default, and `StaleElementReferenceException` extends `WebDriverException` directly rather than `NotFoundException`, so it is not covered. `refreshed` catches staleness inside the condition and returns `null`, turning the escape into one more poll.

saying these in an interview costs you the question

  • Expects or() to return the element from whichever branch matched
  • Believes and() catches a thrown exception and keeps checking branches
  • Thinks not() converts a thrown exception into a passing result
  • Assumes refreshed() combines several conditions like and() does
  • Says staleness is already in WebDriverWait's default ignore list