Retry on StaleElementReferenceException
Re-find an element and retry the action when the DOM re-renders between locating and using it.
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.
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;}
// UsagewithRetry(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 staleWebElement btn = wait.until(ExpectedConditions.refreshed( ExpectedConditions.elementToBeClickable(By.id("add-to-cart"))));btn.click();import timefrom 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
# Usagewith_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 pollingwait = 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 referencesvar 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.