skip to content

Stale Reference Errors

A stale reference means the handle you hold points at a node no longer attached to the document, because a re-render replaced it or a navigation swapped the whole page underneath you.

on this pageshow

explore

questions

5

In Selenium, why does a page-object field holding a WebElement found in the constructor go stale?

level: middleimportance: must knowfreq 60%

answer

  1. How long the field lives
  2. How long the node lives
  3. Constructor runs once, page changes often
  4. Hold the recipe, not the result
  5. A By field survives a re-render

basics

~20 s

The field lives as long as the page object while the node lives only until the next re-render. The constructor mints one reference, so the first redraw kills that field for the rest of the object's life.

solid answer

~40 s

A field typed `WebElement` freezes one reference at construction time. The page object then survives many interactions — filtering a cellar grid, editing a quantity, sorting — and each of those can detach the node the field still names. Every later method call on that field fails, and constructing a second page object only resets the clock until the next re-render. Store the `By` locator in the field instead and call `findElement` inside each method, so the reference is minted immediately before it is used. The class keeps the same shape and the same readable call sites; what changes is that the field holds the recipe for finding the element rather than a result that expires.

code

java · 16 lines
java
class CellarGridPage {
  private final WebDriver driver;
  private final By burgundyRow = By.cssSelector("#cellar-grid tr[data-region='burgundy']");

  CellarGridPage(WebDriver driver) {
    this.driver = driver;
  }

  String burgundyQuantity() {
    return driver.findElement(burgundyRow).findElement(By.cssSelector(".qty")).getText();
  }

  void decrementBurgundy() {
    driver.findElement(burgundyRow).findElement(By.cssSelector(".decrement")).click();
  }
}

go deeper

for a junior

Know that a field holding an element keeps one reference forever, and that storing the locator and looking it up inside each method is the ordinary way to write it.

for a middle

An interviewer expects the reasoning: the field outlives the node, the constructor lookup runs once and far from the use, and swapping the field's type to a locator moves the find to the point of use.

for a senior

Demonstrate the limits too — that a locator field does not help a handle carried across an action inside one method, and does not make the object usable after the test has left the page.

for a principal

Own it as a convention: decide that page objects may hold the driver, locators and data but never element handles, and make that something reviewers can check without argument.

## Field lifetime versus node lifetime A page object exists to give a test readable names for the things on a screen. The trap is in how those names are stored. A field declared as ```java private final WebElement burgundyRow; ``` and assigned once in the constructor holds **a reference to one specific DOM node, captured at one specific instant**. The field's lifetime is the page object's lifetime — typically the whole test method. The node's lifetime is however long the application leaves it attached, which in a wine-cellar inventory grid is often measured in single interactions. Those two lifetimes are unrelated, and the field silently assumes they are the same. The first time the grid re-renders — a region filter, a sort by vintage, an inline quantity edit — the node is detached and the field becomes permanently unusable, along with every method that reads it. ## Why the constructor is the wrong place to look things up Construction happens once, at a moment chosen for code-organisation reasons rather than for page-state reasons. That creates three separate problems: - **The lookup runs too early.** The element may not be rendered yet when the page object is built, so the constructor fails for a reason that has nothing to do with what the test was trying to do. - **The lookup runs too rarely.** One find serves many uses spread across the test, so the gap between find and use grows without limit. - **The failure appears far from the cause.** The test fails inside a method that looks innocent, and the line that actually created the stale reference is back in the constructor. Re-creating the page object after a failure does not solve anything either. It mints a fresh reference, which then goes stale on the next re-render exactly as before. The bug is the field's type, not the field's age. ## Store the locator, not the element The fix is a one-word change in the field's type and a one-line change in each accessor: keep the `By`, and find inside the method. | | Field holds a `WebElement` | Field holds a `By` | |---|---|---| | Reference minted | once, in the constructor | on every call, at the point of use | | Survives a re-render | no, permanently dead afterwards | yes, the next call finds the new node | | Failure when the element is missing | at construction, far from the test's intent | at the call, naming the method that needed it | | Cost | one lookup per page object | one lookup per use | The call sites in the test do not change at all. `cellarPage.burgundyQuantity()` reads the same either way; only the class's internals differ. That is what makes this fix cheap enough that there is no excuse for the other version. ## Lists of rows are the same bug, multiplied A field typed `List<WebElement>` and populated in the constructor is the same mistake with a wider blast radius. One re-render detaches every row it names, so the whole field dies at once rather than one entry at a time. Store the `By` that describes the rows and run the lookup when the caller asks for them; the list you hand back is then a snapshot the caller uses immediately, rather than state the object carries across interactions. ## What this does and does not fix What it fixes: 1. Staleness caused by the page object outliving the nodes it named — which is most staleness in a suite that uses page objects at all. 2. Constructor failures on elements that render late, since nothing is looked up until it is needed. 3. Failure messages that name the method the test was actually in. What it does not fix: - A handle the **method itself** holds across an action. If a method finds the row, clicks something that re-renders, and then reads the row again from the same local variable, it is stale for the ordinary reason and the field's type is irrelevant. - A navigation. If the test has left the page the object describes, finding at the point of use will fail as not-found rather than as stale — clearer, but still a failure, and the right response is to stop using that page object. - Anything about *when* the element becomes ready. Finding at the point of use narrows the window to nearly nothing; it does not wait, and readiness is a separate mechanism. ## The rule underneath A page object should hold **the durable description of where something is**, never a perishable result of having looked. Locators, strings and the driver are durable; element handles are valid for one command against one document. Any field that stores a handle is a bet that the page will not change between construction and last use — and in a grid built to re-render, that bet loses on the first filter click.

  • Does constructing a new page object after the failure fix a stale field?
    Only until the next re-render. A new object runs the same constructor lookup and freezes a new reference, which dies the same way on the following filter or sort. The problem is that the field stores a result rather than a locator, so re-running the constructor just resets a clock that will expire again.
  • Finding on every call costs an extra command per use. When does that matter?
    On a remote browser, where each lookup is a round trip, and in methods called many times in a loop. Even then the reference must be minted close to its use, so the tuning lever is calling the method less often rather than caching the handle back inside the field.
  • Can storing the locator instead of the element still leave a method throwing a stale reference?
    Yes. If a method finds an element, performs an action that re-renders the page, then reuses the same local variable, that handle is stale for the ordinary reason. Moving the lookup out of the constructor fixes the field, not a handle carried across an action inside a single method.

saying these in an interview costs you the question

  • Assigns WebElement fields in the constructor and reuses them
  • Believes re-creating the page object fixes a stale field
  • Thinks a field is safe because the URL never changed
  • Stores a list of row elements as page object state
  • Blames the application for re-rendering its own grid
open as a page

In Selenium, how does a stale element caused by a grid re-render differ from one caused by leaving the page?

level: middleimportance: must knowfreq 72%

basics

~20 s

A re-render detaches one node while the document stays live, so re-finding on the same page works. A navigation replaces the whole document, so every earlier reference dies at once and only the new page can supply one.

open as a page

In Selenium, why can catching StaleElementReferenceException and retrying the same WebElement never succeed?

level: seniorimportance: must knowfreq 56%

basics

~20 s

The instance is bound to one reference the browser has already declared dead, and Selenium documents that all future calls on it fail. Recovery needs a new lookup, and a repeated write needs proof the first one did not land.

open as a page

In Selenium, what does StaleElementReferenceException tell you about a WebElement your test found earlier?

level: juniorimportance: should knowfreq 68%

basics

~20 s

It means the node that handle points at is no longer part of the page the browser is showing. Selenium re-checks the reference before every element command, so that handle is dead and the element has to be found again.

open as a page

In Selenium, why does a loop over a cached List of WebElement rows start failing part-way through?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The list is a snapshot of references captured at one instant, not a live view. If the loop body changes the page, the rows it replaces are detached and every remaining handle in the list is dead.

open as a page