Skip to main content
SeleniumDecoded

Retry on StaleElementReferenceException

Re-find an element and retry the action when the DOM re-renders between locating and using it.

Stable Verified against Selenium 4.48.0

Problem

You locate an element, the framework re-renders the component, and your reference now points at a detached node. click() or getText() throws StaleElementReferenceException. Waiting longer does not help; the element must be found again.

Solution

Wrap the locate-and-act pair in a small retry that re-finds by locator. Never cache the WebElement across the operation that can re-render it.

Locate and act, retrying on stale
Selenium 3 & 4 Stable
public static <T> T withRetry(WebDriver driver, By by, Function<WebElement, T> action) {
StaleElementReferenceException last = null;
for (int attempt = 0; attempt < 3; attempt++) {
try {
return action.apply(driver.findElement(by));
} catch (StaleElementReferenceException e) {
last = e;
try { Thread.sleep(200); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
}
}
throw last;
}
// Usage
withRetry(driver, By.id("add-to-cart"), el -> { el.click(); return null; });
String price = withRetry(driver, By.cssSelector(".price"), WebElement::getText);
// Or let the wait handle it: ExpectedConditions.refreshed re-finds on stale
WebElement btn = wait.until(ExpectedConditions.refreshed(
ExpectedConditions.elementToBeClickable(By.id("add-to-cart"))));
btn.click();
import time
from selenium.common.exceptions import StaleElementReferenceException
def with_retry(driver, locator, action, attempts=3):
last = None
for _ in range(attempts):
try:
return action(driver.find_element(*locator))
except StaleElementReferenceException as e:
last = e
time.sleep(0.2)
raise last
# Usage
with_retry(driver, (By.ID, "add-to-cart"), lambda el: el.click())
price = with_retry(driver, (By.CSS_SELECTOR, ".price"), lambda el: el.text)
# Or make the wait ignore stale references while polling
wait = WebDriverWait(driver, 10, ignored_exceptions=(StaleElementReferenceException,))
wait.until(lambda d: d.find_element(By.ID, "add-to-cart").is_enabled())
async function withRetry(driver, locator, action, attempts = 3) {
let last;
for (let i = 0; i < attempts; i++) {
try {
return await action(await driver.findElement(locator));
} catch (e) {
if (e.name !== 'StaleElementReferenceError') throw e;
last = e;
await driver.sleep(200);
}
}
throw last;
}
await withRetry(driver, By.id('add-to-cart'), (el) => el.click());
const price = await withRetry(driver, By.css('.price'), (el) => el.getText());
public static T WithRetry<T>(IWebDriver driver, By by, Func<IWebElement, T> action, int attempts = 3)
{
StaleElementReferenceException? last = null;
for (int i = 0; i < attempts; i++)
{
try { return action(driver.FindElement(by)); }
catch (StaleElementReferenceException e) { last = e; Thread.Sleep(200); }
}
throw last!;
}
WithRetry(driver, By.Id("add-to-cart"), el => { el.Click(); return 0; });
var price = WithRetry(driver, By.CssSelector(".price"), el => el.Text);
// Or configure the wait to ignore stale references
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
wait.IgnoreExceptionTypes(typeof(StaleElementReferenceException));

Why It Works

The retry re-executes findElement, which returns a reference to the node currently in the DOM. Three attempts with a short pause covers the re-render window of every mainstream framework. Cap the attempts; an element that is stale forever indicates a locator that matches a node the app keeps replacing, which needs a better locator, not more retries.

Gotchas

  • Do not retry actions with side effects (submitting a form) unless the first attempt provably did not happen; the stale exception is thrown before the action executes, so it is safe here.
  • If you see staleness on every test, the page is rendering after your wait resolves. Wait for a later signal (network idle, a data attribute) instead of adding retries everywhere.

Learn the theory