Q14 of 37 · Selenium
How do you handle a StaleElementReferenceException?
Short answer
Short answer: It means the WebElement you held a handle to was removed or replaced in the DOM. Don't catch and retry blindly — re-locate the element after the action that re-rendered it. Fluent waits with `ignoring(StaleElementReferenceException.class)` are useful for transient cases.
Detail
StaleElementReferenceException happens when you've stored a WebElement and the underlying DOM node has been removed, replaced, or detached. The reference still points to the old node, which no longer exists in the live tree, so any operation throws.
Common triggers:
- Modern frameworks (React, Vue) re-render lists and tables on state changes — every row gets new DOM nodes.
- AJAX updates that replace a section of the page (
innerHTMLrewrite, framework re-mount). - Navigating to a new page and trying to use a reference from the previous one.
The wrong fix: a generic catch-and-retry that swallows real bugs.
The right fixes, in order of preference:
1. Re-locate just before use. If the element changes between actions, look it up again rather than holding the reference.
2. Use fluent waits to ignore transient stales with ignoring(StaleElementReferenceException.class).
3. PageFactory's @CacheLookup caches references and is the most common cause of stale exceptions on dynamic pages. Drop @CacheLookup for any element on a page that re-renders.
The senior insight: stale-element bugs are usually a sign your test is reading state across a re-render. The cleanest fix is to refactor the test so the reference doesn't need to survive across one — not to wallpaper with retries.
// EXAMPLE
// ❌ Stored reference; list re-renders; reference is stale
WebElement row = driver.findElement(By.cssSelector(".row[data-id='42']"));
row.findElement(By.cssSelector(".edit")).click();
row.findElement(By.cssSelector(".save")).click(); // throws
// ✅ Re-locate after the action that re-renders
driver.findElement(By.cssSelector(".row[data-id='42'] .edit")).click();
driver.findElement(By.cssSelector(".row[data-id='42'] .save")).click();
// ✅ Or wrap in FluentWait that ignores stale-element transients
WebElement fresh = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(5))
.pollingEvery(Duration.ofMillis(200))
.ignoring(StaleElementReferenceException.class)
.until(d -> d.findElement(By.cssSelector(".row[data-id='42']")));