Platform

Solutions

Resources

Platform

Solutions

Resources

Platform

Solutions

Resources

Blog

StaleElementReferenceException in Selenium: Causes, Fixes and Code Examples

Adwitiya Pandey

Published on

How to Fix Stale Element Reference Exception in Selenium
eBook CTA Background
RECOMMENDED RESOURCES

The Ultimate Enterprise Guide To Agentic Testing

AI agents can write, fix and triage your tests at scale. See how to use them in QA while keeping people in control of everything that ships.

Get the eBook →

Enterprise guide to agentic testing
eBook CTA Background
RECOMMENDED RESOURCES

The Ultimate Enterprise Guide To Agentic Testing

AI agents can write, fix and triage your tests at scale. See how to use them in QA while keeping people in control of everything that ships.

Get the eBook →

Enterprise guide to agentic testing
eBook CTA Background
RECOMMENDED RESOURCES

The Ultimate Enterprise Guide To Agentic Testing

AI agents can write, fix and triage your tests at scale. See how to use them in QA while keeping people in control of everything that ships.

Get the eBook →

Enterprise guide to agentic testing
eBook CTA Background
RECOMMENDED RESOURCES
Enterprise guide to agentic testing

The Ultimate Enterprise Guide To Agentic Testing

AI agents can write, fix and triage your tests at scale. See how to use them in QA while keeping people in control of everything that ships.

Get the eBook →

Table of contents

Lorem ipsum

Lorem ipsum

Lorem ipsum

Lorem ipsum

Lorem ipsum

StaleElementReferenceException is one of the most common errors Selenium users run into, and one of the most frustrating. Your test finds an element, the page changes slightly, and the next click fails, even though the element still looks exactly the same on screen.

The usual fixes, such as re-locating elements, explicit waits, retries and page objects, all help. But they treat the symptom, not the cause. This guide explains why stale elements happen, shows each fix with working Java code, and explains how AI test platforms avoid the problem by identifying elements differently.



What is StaleElementReferenceException?

StaleElementReferenceException is thrown when Selenium tries to use a web element that’s no longer attached to the page’s DOM.

When you call findElement(), Selenium returns a reference to one specific node in the DOM. If that node is removed or replaced, for example because the page re-rendered, the reference points to something that no longer exists. Any action on it, such as clicking, typing or reading text, throws the exception.

WebElement saveButton = driver.findElement(By.id("save"));

// The app re-renders the form, so the old button node is replaced with a new one
driver.findElement(By.id("refresh-data")).click();

saveButton.click(); // throws StaleElementReferenceException
WebElement saveButton = driver.findElement(By.id("save"));

// The app re-renders the form, so the old button node is replaced with a new one
driver.findElement(By.id("refresh-data")).click();

saveButton.click(); // throws StaleElementReferenceException
WebElement saveButton = driver.findElement(By.id("save"));

// The app re-renders the form, so the old button node is replaced with a new one
driver.findElement(By.id("refresh-data")).click();

saveButton.click(); // throws StaleElementReferenceException
WebElement saveButton = driver.findElement(By.id("save"));

// The app re-renders the form, so the old button node is replaced with a new one
driver.findElement(By.id("refresh-data")).click();

saveButton.click(); // throws StaleElementReferenceException

The error message looks like this:





StaleElementReferenceException is a subclass of WebDriverException. It doesn’t mean your locator is wrong. It means the reference you’re holding is out of date, and you need to find the element again.



When Does a Selenium Stale Element Reference Exception Occurs?

Cause

What Happens

Common Example

Page refresh or navigation

Every element reference from the old page is discarded

Clicking a link, then using an element found before the click

JavaScript re-rendering

Frameworks destroy and recreate elements, even when nothing visible changes

React, Vue or Angular updating a component after state changes

Element removed and recreated

A new element with identical attributes replaces the old one

A table refreshing its rows after sorting or filtering

Switching frames or windows

The element belongs to a different browsing context

Using an element found inside an iframe after switching back to the main page

AJAX updates

Part of the page is replaced without the URL changing

Search results, prices or live data updating in place

Stale elements are especially common in single-page applications, ecommerce sites where baskets and prices update constantly, financial dashboards with live data, and healthcare systems with forms that change as you fill them in.



How to Fix StaleElementReferenceException in Selenium

Method 1: Re-Locate the Element Before Each Interaction

The simplest fix is to stop holding on to element references. Find the element again each time you use it.

// Risky: the reference can go stale between steps
WebElement quantity = driver.findElement(By.id("quantity"));
quantity.clear();
driver.findElement(By.id("update-basket")).click(); // the basket re-renders
quantity.sendKeys("3"); // stale

// Safer: find the element at the moment you use it
driver.findElement(By.id("quantity")).clear();
driver.findElement(By.id("update-basket")).click();
driver.findElement(By.id("quantity")).sendKeys("3");
// Risky: the reference can go stale between steps
WebElement quantity = driver.findElement(By.id("quantity"));
quantity.clear();
driver.findElement(By.id("update-basket")).click(); // the basket re-renders
quantity.sendKeys("3"); // stale

// Safer: find the element at the moment you use it
driver.findElement(By.id("quantity")).clear();
driver.findElement(By.id("update-basket")).click();
driver.findElement(By.id("quantity")).sendKeys("3");
// Risky: the reference can go stale between steps
WebElement quantity = driver.findElement(By.id("quantity"));
quantity.clear();
driver.findElement(By.id("update-basket")).click(); // the basket re-renders
quantity.sendKeys("3"); // stale

// Safer: find the element at the moment you use it
driver.findElement(By.id("quantity")).clear();
driver.findElement(By.id("update-basket")).click();
driver.findElement(By.id("quantity")).sendKeys("3");
// Risky: the reference can go stale between steps
WebElement quantity = driver.findElement(By.id("quantity"));
quantity.clear();
driver.findElement(By.id("update-basket")).click(); // the basket re-renders
quantity.sendKeys("3"); // stale

// Safer: find the element at the moment you use it
driver.findElement(By.id("quantity")).clear();
driver.findElement(By.id("update-basket")).click();
driver.findElement(By.id("quantity")).sendKeys("3");
  • Pros: simple, and removes stored references that can go stale

  • Cons: repetitive, and it doesn’t help if the DOM changes between finding and acting


Method 2: Use Explicit Waits

Wait for the element to be in the state you need before acting on it.

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

wait.until(ExpectedConditions.elementToBeClickable(By.id("save"))).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

wait.until(ExpectedConditions.elementToBeClickable(By.id("save"))).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

wait.until(ExpectedConditions.elementToBeClickable(By.id("save"))).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

wait.until(ExpectedConditions.elementToBeClickable(By.id("save"))).click();

When you know the DOM is about to change, combine stalenessOf() and refreshed() to wait for the old element to disappear and the new one to be ready:

WebElement table = driver.findElement(By.id("orders-table"));
driver.findElement(By.id("sort-by-date")).click();

// Wait for the old table to be removed from the DOM
wait.until(ExpectedConditions.stalenessOf(table));

// Then wait for the new table, re-finding it if it goes stale again during the wait
WebElement refreshedTable = wait.until(ExpectedConditions.refreshed(
    ExpectedConditions.visibilityOfElementLocated(By.id("orders-table"))));
WebElement table = driver.findElement(By.id("orders-table"));
driver.findElement(By.id("sort-by-date")).click();

// Wait for the old table to be removed from the DOM
wait.until(ExpectedConditions.stalenessOf(table));

// Then wait for the new table, re-finding it if it goes stale again during the wait
WebElement refreshedTable = wait.until(ExpectedConditions.refreshed(
    ExpectedConditions.visibilityOfElementLocated(By.id("orders-table"))));
WebElement table = driver.findElement(By.id("orders-table"));
driver.findElement(By.id("sort-by-date")).click();

// Wait for the old table to be removed from the DOM
wait.until(ExpectedConditions.stalenessOf(table));

// Then wait for the new table, re-finding it if it goes stale again during the wait
WebElement refreshedTable = wait.until(ExpectedConditions.refreshed(
    ExpectedConditions.visibilityOfElementLocated(By.id("orders-table"))));
WebElement table = driver.findElement(By.id("orders-table"));
driver.findElement(By.id("sort-by-date")).click();

// Wait for the old table to be removed from the DOM
wait.until(ExpectedConditions.stalenessOf(table));

// Then wait for the new table, re-finding it if it goes stale again during the wait
WebElement refreshedTable = wait.until(ExpectedConditions.refreshed(
    ExpectedConditions.visibilityOfElementLocated(By.id("orders-table"))));
  • Pros: handles timing well, and is more reliable than implicit waits

  • Cons: needs waits throughout your suite, and the element can still go stale after the wait ends


Method 3: Retry the Action

Catch the exception and try again with a freshly located element.

public void clickWithRetry(By locator, int maxAttempts) {
    for (int attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            driver.findElement(locator).click();
            return;
        } catch (StaleElementReferenceException e) {
            if (attempt == maxAttempts) {
                throw e;
            }
            // Loop again and re-find the element
        }
    }
}
public void clickWithRetry(By locator, int maxAttempts) {
    for (int attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            driver.findElement(locator).click();
            return;
        } catch (StaleElementReferenceException e) {
            if (attempt == maxAttempts) {
                throw e;
            }
            // Loop again and re-find the element
        }
    }
}
public void clickWithRetry(By locator, int maxAttempts) {
    for (int attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            driver.findElement(locator).click();
            return;
        } catch (StaleElementReferenceException e) {
            if (attempt == maxAttempts) {
                throw e;
            }
            // Loop again and re-find the element
        }
    }
}
public void clickWithRetry(By locator, int maxAttempts) {
    for (int attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
            driver.findElement(locator).click();
            return;
        } catch (StaleElementReferenceException e) {
            if (attempt == maxAttempts) {
                throw e;
            }
            // Loop again and re-find the element
        }
    }
}
  • Pros: handles brief re-renders, and is easy to reuse

  • Cons: adds time, and can hide real problems if retries become the default answer


Method 4: Use Page Objects with Lazy Loading

With Selenium’s PageFactory, fields annotated with @FindBy are proxies. Selenium finds the element each time you use the field, rather than once when the page object is created.

public class BasketPage {

    @FindBy(id = "quantity")
    private WebElement quantity;

    @FindBy(id = "update-basket")
    private WebElement updateButton;

    public BasketPage(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public void updateQuantity(String value) {
        quantity.clear();       // located now
        quantity.sendKeys(value);
        updateButton.click();   // located now
    }
}
public class BasketPage {

    @FindBy(id = "quantity")
    private WebElement quantity;

    @FindBy(id = "update-basket")
    private WebElement updateButton;

    public BasketPage(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public void updateQuantity(String value) {
        quantity.clear();       // located now
        quantity.sendKeys(value);
        updateButton.click();   // located now
    }
}
public class BasketPage {

    @FindBy(id = "quantity")
    private WebElement quantity;

    @FindBy(id = "update-basket")
    private WebElement updateButton;

    public BasketPage(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public void updateQuantity(String value) {
        quantity.clear();       // located now
        quantity.sendKeys(value);
        updateButton.click();   // located now
    }
}
public class BasketPage {

    @FindBy(id = "quantity")
    private WebElement quantity;

    @FindBy(id = "update-basket")
    private WebElement updateButton;

    public BasketPage(WebDriver driver) {
        PageFactory.initElements(driver, this);
    }

    public void updateQuantity(String value) {
        quantity.clear();       // located now
        quantity.sendKeys(value);
        updateButton.click();   // located now
    }
}

Avoid adding @CacheLookup to elements that re-render, because it stores the first reference and brings the stale element problem back.

  • Pros: tidier code, and elements are found when they’re used

  • Cons: an element can still go stale between being found and being used, and PageFactory takes some set-up


Method 5: Tell WebDriverWait to Ignore Stale Elements

You can tell a wait to keep trying when it hits a stale element.

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.ignoring(StaleElementReferenceException.class);

String status = wait.until(d -> d.findElement(By.id("order-status")).getText());
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.ignoring(StaleElementReferenceException.class);

String status = wait.until(d -> d.findElement(By.id("order-status")).getText());
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.ignoring(StaleElementReferenceException.class);

String status = wait.until(d -> d.findElement(By.id("order-status")).getText());
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.ignoring(StaleElementReferenceException.class);

String status = wait.until(d -> d.findElement(By.id("order-status")).getText());
  • Pros: combines waiting and stale element handling in one place

  • Cons: only protects the code inside the wait, and can hide genuine instability


Which Fix Should You Use?

Situation

Best Fix

You store an element, then do something that changes the page

Re-locate the element before each use

You know an action will refresh part of the page

Wait for staleness, then wait for the new element

A component re-renders unpredictably for a moment

A short retry, or a wait that ignores stale elements

You want cleaner code across a large suite

Page objects without @CacheLookup


Why These Fixes Only Go So Far

All of these methods work around the same limitation. Selenium identifies an element with one locator, holds a reference to one DOM node, and has no idea that a newly rendered button is the same “Save” button a user sees.

  • Explicit waits deal with timing, not with elements being replaced.

  • Retries hide the problem rather than preventing it.

  • Page objects organise code, but don’t make locators any less fragile.

  • Re-locating elements adds code without making identification smarter.

On a small, stable application, that’s manageable. On a fast-changing single-page app with hundreds of tests, handling stale elements becomes part of nearly every test you write, and part of most failures you investigate.



How Virtuoso QA Avoids Stale Elements

Virtuoso QA doesn’t hold on to element references between steps. Each step identifies its element at the moment it runs, using many signals together, such as visible text, label, role, position and surrounding context.

If the application re-renders a button, the next step simply finds the new one.

Plain English Instead of Stored References

Instead of finding, storing and re-finding elements in code, you describe the action:

Write "3" in field "Quantity"
Click "Update basket"
Look for "3 items in your basket"
Write "3" in field "Quantity"
Click "Update basket"
Look for "3 items in your basket"
Write "3" in field "Quantity"
Click "Update basket"
Look for "3 items in your basket"
Write "3" in field "Quantity"
Click "Update basket"
Look for "3 items in your basket"

There’s no element reference to go stale, and no wait or retry code to write.

To learn how plain English steps let more of your team build and maintain tests, read our guide to natural language programming in testing.

Identification from Many Signals

Because each element is identified from several attributes at once, a change to one of them, such as a regenerated ID or a new class name, doesn’t stop the step finding it.

Repairs Your Team Reviews

When a release changes the page more significantly, Virtuoso QA proposes a repair for your team to accept or reject. Tests keep running through UI changes, and every change leaves a record.

Also read: our guide to self-healing in continuous testing explains how reviewable repairs keep suites stable through frequent releases.

Live Authoring and Clear Failures

Each step runs against the live application as you write it, so you see immediately whether it works. When a run fails, you see the failed step beside the last passing run, with screenshots, the DOM snapshot, network data and a likely cause.

Bringing Selenium Suites Across

GENerator converts existing Selenium tests into plain English journeys, so you keep the coverage you’ve built and leave stale element handling behind. Explore GENerator to see how the conversion works.



Related Reads

StaleElementReferenceException FAQs

What causes StaleElementReferenceException in Selenium?‍

How do you handle stale element reference in Selenium WebDriver?‍

What is the difference between NoSuchElementException and StaleElementReferenceException?‍

Can I completely avoid StaleElementReferenceException in Selenium?‍

How does AI native testing prevent stale element exceptions?‍

How long does it take to migrate Selenium tests to AI native platforms?‍

See what your next release looks like with Virtuoso

Book a walkthrough on your applications and your workflows. Bring a requirement, a user journey, or a brittle Selenium script, and watch the loop run on something you recognise.

See what your next release looks like with Virtuoso

Book a walkthrough on your applications and your workflows. Bring a requirement, a user journey, or a brittle Selenium script, and watch the loop run on something you recognise.

See what your next release looks like with Virtuoso

Book a walkthrough on your applications and your workflows. Bring a requirement, a user journey, or a brittle Selenium script, and watch the loop run on something you recognise.

Virtuoso QA is establishing the standard of proof for software releases. Its governed QA loop turns business requirements into tests for any browser-based application: AI proposes, a deterministic engine executes, a person approves what matters, and every decision leaves evidence.

Trust Center

AICPA

SOC

WAVE STRONG PERFORMER

@ Copyright 2026 SpotQA, Creators of Virtuoso QA

Virtuoso QA is establishing the standard of proof for software releases. Its governed QA loop turns business requirements into tests for any browser-based application: AI proposes, a deterministic engine executes, a person approves what matters, and every decision leaves evidence.

Trust Center

AICPA

SOC

WAVE STRONG PERFORMER

@ Copyright 2026 SpotQA, Creators of Virtuoso QA

Virtuoso QA is establishing the standard of proof for software releases. Its governed QA loop turns business requirements into tests for any browser-based application: AI proposes, a deterministic engine executes, a person approves what matters, and every decision leaves evidence.

Trust Center

AICPA

SOC

WAVE STRONG PERFORMER

@ Copyright 2026 SpotQA, Creators of Virtuoso QA