In Selenium's ExpectedConditions, which helpers hand back a usable WebElement and which only hand back a Boolean?
answer
- The wait returns what the predicate returned
- Asks for a node versus asserts a fact
- Some give a list, one gives a dialog
- Negative checks have nothing to hand back
- Assign it instead of finding twice
basics
~20 sHelpers that ask for a node return it: presenceOfElementLocated, visibilityOfElementLocated, visibilityOf and elementToBeClickable give a WebElement, the all-elements helpers give a list, alertIsPresent gives an Alert. Helpers that assert a page fact, such as invisibilityOfElementLocated, stalenessOf and titleIs, give only a Boolean.
solid answer
~30 s`until` returns whatever the condition returned, unchanged, so the condition's type is what you get. The node-shaped helpers hand the element back — `presenceOfElementLocated`, `visibilityOfElementLocated`, `visibilityOf` and both `elementToBeClickable` overloads all produce a `WebElement`; `presenceOfAllElementsLocatedBy`, `visibilityOfAllElementsLocatedBy` and the `numberOfElementsToBe` family produce a `List<WebElement>`; `alertIsPresent` produces an `Alert`. The statement-shaped helpers produce only `Boolean`: `invisibilityOfElementLocated`, `stalenessOf`, `titleIs`, `textToBePresentInElementLocated`, `elementToBeSelected`, `numberOfWindowsToBe` and the `or`/`and`/`not` combinators. The practical rule is to assign the element-returning ones instead of finding the node a second time, which removes a re-render race between the wait and the action.
code
java · 11 linesWebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement startButton = wait.until(
ExpectedConditions.elementToBeClickable(By.id("start-placement-test")));
startButton.click();
List<WebElement> cards = wait.until(
ExpectedConditions.presenceOfAllElementsLocatedBy(By.cssSelector(".quiz-question")));
boolean spinnerGone = wait.until(
ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".grading-spinner")));go deeper
Remember that a wait on visibility or clickability gives you the element, so you can chain the click straight onto the wait call rather than searching for it again.
Explain the split by shape: node-shaped helpers return an element or a list, statement-shaped ones return a Boolean, and until passes the value through untouched.
Show why the split matters in a real suite: the second lookup after a wait is a re-render race, and page-object signatures follow directly from which family a helper belongs to.
Own the API convention your framework exposes, so that helper methods return what callers actually need and nobody re-finds a node the wait already located.
## `until` gives back exactly what the condition gave it `FluentWait.until(Function)` — the method `WebDriverWait` inherits — keeps calling the supplied function until it returns something that is neither `null` nor `Boolean.FALSE`, and then **returns that value to you**. Nothing in the wait converts or unwraps it. So the useful question about any member of Selenium 4's `ExpectedConditions` class is not only "what does it check" but "what type does it produce", because that type is what lands in your variable. Concretely, on a language-school placement quiz, `elementToBeClickable(By.id("start-placement-test"))` returns the button itself, so the wait and the click are one statement. `invisibilityOfElementLocated(By.cssSelector(".grading-spinner"))` returns `Boolean.TRUE`, so there is nothing to click and nothing to keep. ## The element-returning family These hand back a live reference you should keep: - `presenceOfElementLocated(By)` — the `WebElement` as soon as `findElement` succeeds. - `visibilityOfElementLocated(By)` — the `WebElement` once it is also displayed. - `visibilityOf(WebElement)` — the same element you passed in, once displayed. - `elementToBeClickable(By)` and `elementToBeClickable(WebElement)` — the element once it is displayed and enabled. - `presenceOfNestedElementLocatedBy(...)` — the nested `WebElement`. A second group returns a collection rather than a single node: `presenceOfAllElementsLocatedBy(By)`, `visibilityOfAllElementsLocatedBy(By)`, `visibilityOfNestedElementsLocatedBy(...)`, `numberOfElementsToBe(By, int)`, `numberOfElementsToBeMoreThan(...)` and `numberOfElementsToBeLessThan(...)` all produce a `List<WebElement>`. That is genuinely useful on a placement quiz: waiting for the twenty question cards and getting the list in the same call. One member stands alone: `alertIsPresent()` produces an `Alert` handle rather than a `WebElement` or a `Boolean`, returning `null` while no dialog is up. ## The Boolean family Everything phrased as a *statement about the page* rather than *a request for a node* returns `Boolean`: - `invisibilityOfElementLocated(By)` and `invisibilityOf(WebElement)` - `stalenessOf(WebElement)` - `titleIs(String)`, `titleContains(String)`, `urlToBe(String)`, `urlContains(String)` - `textToBePresentInElementLocated(By, String)` and `textToBe(By, String)` - `elementToBeSelected(...)` and `elementSelectionStateToBe(...)` - `numberOfWindowsToBe(int)` - `attributeToBe(...)`, `attributeContains(...)`, `domAttributeToBe(...)`, `domPropertyToBe(...)` - the combinators `or(...)`, `and(...)` and `not(...)` For all of these the only success value is `true`, because `FluentWait` treats `Boolean.FALSE` as "keep polling". Assigning the result is legal but tells you nothing you did not already know from the fact that the call returned rather than threw. | Shape | Example factories | Type from `until` | What you do next | |---|---|---|---| | Single node | `visibilityOfElementLocated`, `elementToBeClickable` | `WebElement` | act on the returned element | | Many nodes | `presenceOfAllElementsLocatedBy`, `numberOfElementsToBe` | `List<WebElement>` | iterate the returned list | | Dialog | `alertIsPresent` | `Alert` | keep the returned handle | | Page statement | `invisibilityOfElementLocated`, `stalenessOf`, `titleIs` | `Boolean` | carry on; nothing is handed back | ## Why the distinction is worth money 1. **It removes a race.** If you wait on `visibilityOfElementLocated` and then call `driver.findElement` with the same locator, you have done two finds, and the page can re-render between them. Assigning the returned element does one find. 2. **It shapes your page objects.** A method that wraps an element-returning condition can have a `WebElement` return type; one that wraps a Boolean condition cannot, and pretending otherwise forces a redundant lookup inside it. 3. **It explains the combinators.** `or(...)` and `and(...)` are declared to produce `Boolean`, so composing two element-returning conditions loses both elements. `refreshed(ExpectedCondition<T>)` is the exception — it is generic in `T` and preserves whatever the wrapped condition produced. ## A worked case from the placement quiz The submit step on a placement quiz needs all three shapes in a row, and each one is used differently: 1. `elementToBeClickable(By.id("submit-placement"))` returns the button, so the wait and the `click()` are one statement and no second lookup can race the re-render that the click triggers. 2. `invisibilityOfElementLocated(By.cssSelector(".grading-spinner"))` returns `Boolean.TRUE` and nothing else, so the next line has to locate the results panel on its own. 3. `presenceOfAllElementsLocatedBy(By.cssSelector(".score-row"))` returns the `List<WebElement>` of score rows, which you iterate directly instead of running `findElements` again. The pattern is the same each time: if the condition's name asks for a node, keep what comes back; if it asserts a fact about the page, the call's only product is the fact that it returned at all. ## Practical notes - The element you get back is an ordinary `WebElement`, with all the usual consequences: it can go stale later, and holding it across a navigation is not safe. - A Boolean-returning condition that never becomes true ends in `TimeoutException`, and the message includes the condition's own `toString()` — which is why the shipped conditions all override `toString()` with a readable phrase such as `element found by ... to become invisible`. - Selenium 4 spells the timeout as a `java.time.Duration`, so these are written `new WebDriverWait(driver, Duration.ofSeconds(10)).until(...)`; the older seconds-as-`long` constructor is gone.
- Why is assigning the element returned by a wait better than finding it again on the next line?Finding it again is a second lookup, and the page can re-render between the two. The condition already did a find that succeeded, so keeping its result removes that window entirely and saves a round trip to the driver.
- What happens to the element type when you wrap two element conditions in and()?It is lost. `and(...)` is declared as an `ExpectedCondition<Boolean>`, so it reports only that every branch passed. `refreshed(ExpectedCondition<T>)` is the one wrapper that stays generic and gives back whatever the condition it wraps produced.
- Why can a Boolean-returning condition never end the wait by returning false?The polling loop treats `null` and `Boolean.FALSE` identically as 'not yet' and keeps going until the timeout. So the only value such a condition can end a wait with is `true`; a persistent false state ends in `TimeoutException` instead.
saying these in an interview costs you the question
- Thinks every ExpectedConditions helper returns a WebElement
- Calls findElement again right after a visibility wait succeeded
- Expects invisibilityOfElementLocated to return the hidden element
- Believes until converts the condition's result into a Boolean
- Assumes a Boolean condition can end a wait by returning false