skip to content

In Selenium, how does ExpectedConditions.stalenessOf decide that the element it was given is gone?

level: middleimportance: must knowfreq 53%

answer

  1. It watches for a call to fail
  2. Any method call forces the check
  3. The reference must predate the change
  4. Two exceptions both mean gone
  5. Returns a boolean, never an element

basics

~20 s

By calling a method on the reference each poll and watching for it to fail. Selenium 4 calls isEnabled and discards the result; when that raises a stale element reference or no such element error, the condition returns true.

solid answer

~40 s

Each poll `stalenessOf` calls `element.isEnabled()` on the reference you passed in, purely to force the remote end to resolve it. If the call succeeds the element is still attached, so the condition returns `false` and the wait polls again; if it raises `StaleElementReferenceException` or `NoSuchElementException`, the condition returns `true` and `until` ends. Two things follow. You must capture the reference **before** the action that replaces the page, because the condition can only observe a handle you already hold. And `until` returns `true`, not an element — anything you touch afterwards must be located again. On a cinema showtime picker, grab the Friday grid, click the Saturday tab, wait for the old grid to go stale, then find the new showtimes.

code

java · 21 lines
java
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public class ShowtimeDateSwitch {

  public static WebElement switchToSaturday(WebDriver driver) {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

    WebElement fridayGrid = driver.findElement(By.cssSelector("#showtimes"));
    driver.findElement(By.cssSelector("[data-date='saturday']")).click();

    wait.until(ExpectedConditions.stalenessOf(fridayGrid));

    return wait.until(ExpectedConditions.elementToBeClickable(
        By.cssSelector(".showtime")));
  }
}

go deeper

for a junior

Remember the order: capture the element, do the thing that replaces it, then wait for staleness. Getting the reference after the click is the mistake this condition punishes first.

for a middle

Explain how it works, not just when to use it: a method call on the reference each poll, with the resulting exception treated as the success signal. Note that it yields a boolean, so the new element must be located again.

for a senior

Demonstrate judgment about signals. Show why waiting for the old document to disappear is unambiguous where waiting for new content is not, and name the case it cannot detect, an in-place mutation that leaves the node attached.

for a principal

Own the principle that a synchronisation point should be impossible to satisfy by the state you are leaving. Teams that only ever wait forwards build suites that pass against the previous screen, and that failure is invisible in review.

## What the predicate does on every poll `ExpectedConditions.stalenessOf(WebElement)` is the inverse of almost every other condition: it waits for an element to **stop existing** rather than to start. In Selenium 4's Java support library its body is three lines, and they are worth knowing literally: 1. Call `element.isEnabled()` on the reference you handed in. The return value is discarded — the call is made purely to force the remote end to resolve the element reference. 2. If that call **succeeds**, the element is still attached, so the condition returns `false` and `WebDriverWait` sleeps and polls again. 3. If that call **throws** `StaleElementReferenceException` or `NoSuchElementException`, the reference no longer resolves, so the condition returns `true` and `until` ends. Two consequences fall straight out of that shape. First, the condition's type is `ExpectedCondition<Boolean>`, so `until` gives you `true` — **not** an element. Anything you want to touch afterwards must be located again. Second, it can only work on a reference you already hold, which means **you must capture the element before the action that destroys it**. A `stalenessOf` call written after the navigation has nothing meaningful to observe. ## Why "the page is gone" is a better signal than "the new page is here" | Signal | What it becomes true on | Failure mode | |---|---|---| | `stalenessOf(oldElement)` | the old document being torn down | needs a reference captured beforehand | | waiting for a new element | the replacement being attached | passes instantly if the old page already contains a match for the locator | That second row is the trap `stalenessOf` exists to avoid. On a page whose old and new states share markup, a wait for the new content is satisfied by the old content, and the test races ahead into a document that is about to be replaced. Waiting for the **old** element to go stale is unambiguous, because the old reference is bound to the old document and can never be satisfied by it once it is gone. ## The cinema showtime picker sequence Switching from Friday to Saturday on a showtimes page re-renders the whole grid of screenings. The safe order is: ```java WebElement grid = driver.findElement(By.cssSelector("#showtimes")); driver.findElement(By.cssSelector("[data-date='saturday']")).click(); wait.until(ExpectedConditions.stalenessOf(grid)); WebElement saturday = wait.until( ExpectedConditions.elementToBeClickable(By.cssSelector(".showtime"))); ``` - The reference to the Friday grid is captured **first**, while it is still valid. - The date tab is clicked, which begins the re-render. - `stalenessOf` polls the old reference until it stops resolving. That moment is the proof that the Friday grid is gone, not merely that it has been repainted. - Only then is the Saturday content located, from scratch, by a fresh `findElement` inside the next condition. Skip the third line and the last line can match a Friday showtime that is still on screen for another 40 milliseconds. ## What it does and does not promise - It promises the **specific reference you passed** no longer resolves. It says nothing about what has replaced it, or whether the replacement has finished rendering. - It is therefore a *half* of a synchronisation, not a whole one. The idiomatic pairing is `stalenessOf` on the old thing, followed by a separate wait on the new thing. - It returns `true` on `NoSuchElementException` as well as `StaleElementReferenceException`, so a reference whose element is removed outright is handled the same way as one detached by a re-render. - It will hang for the full timeout on a page that **re-renders without detaching** — an app that mutates the existing node's text in place leaves the reference perfectly valid, and the condition never becomes true. - A cached `WebElement` held across a re-render is a liability everywhere else in a test suite; `stalenessOf` is the one place where that liability is deliberately turned into a signal. ## Common ways it is written wrong 1. Locating the element after the click that triggers the navigation. The freshly found element belongs to the new document already, so the condition either waits out its timeout or reports staleness for the wrong reason. 2. Expecting `until` to return the new element. It returns `true`; assigning it to a `WebElement` will not compile, and reusing the old variable afterwards will raise `StaleElementReferenceException` on first use. 3. Using it as a general-purpose "wait for the page to settle". It is a single-element observation, and a page can replace a great deal of itself without touching the one node you happened to reference.

  • Why wait for the old element to go stale instead of waiting for the new content to appear?
    Because on a page whose two states share markup, a wait for the new content is satisfied by the old content and returns before anything has changed. The old reference is bound to the old document, so it can never be satisfied by the page you are trying to leave. It is an unambiguous signal where the other is not.
  • What happens if the application re-renders the element's text in place rather than replacing the node?
    The condition never becomes true. The reference still resolves, so `isEnabled()` keeps succeeding and the wait spins out to `TimeoutException` on a page that behaved correctly. In-place mutation needs a different signal, typically one about the content itself rather than about the node's existence.
  • Is stalenessOf on its own enough to synchronise a date change?
    No, it is half of one. It proves the old grid is gone and says nothing about whether a replacement exists or has finished rendering. The idiomatic shape is a staleness wait on the old reference followed by a separate wait on the new element, located from scratch.

saying these in an interview costs you the question

  • Locates the element after the action that replaces it
  • Expects until to return the replacement element
  • Reuses the captured reference once the wait ends
  • Treats it as a general wait for the page to settle
  • Assumes it also confirms the new content has rendered