End-to-end Testing
How-tos & Guides
12 min read

How to deal with StaleElementReferenceException in Selenium

Learn what StaleElementReferenceException is, why and when it occurs, and how to avoid it in your Selenium tests.

Antonello Zanini
Published November 28, 2022
AI Assistant for Playwright
Code AI-powered test steps with the free ZeroStep JavaScript library
Learn more

In Selenium, a WebElement is a reference to an HTML element within the Document Object Model (DOM) of a web page. In Java, when you interact with a WebElement that is no longer associated with an HTML element within the DOM, Selenium throws a StaleElementReferenceException. This exception is pretty common in web apps that are based on client-side frameworks, such as React. This is because React uses its own “virtual DOM”, which is a lightweight JavaScript representation of the actual DOM, and continually sends batched DOM changes from the virtual DOM into the actual DOM.

In this article, you will learn everything you need to know to understand when Selenium throws a StaleElementReferenceException and how you can avoid it in your Selenium WebDriver tests.

What is a StaleElementReferenceException?

Selenium throws a StaleElementReferenceException when the reference to the element you are interacting with is now stale. This means that the element is no longer attached to the actual DOM of the web page. In other words, a StaleElementReferenceException occurs when interacting with a WebElement that is no longer associated with an HTML element in the DOM.

StaleElementReferenceException is one of the many subclasses of the more generic WebDriverException, which is a special RunTimeException and represents the most general Selenium exception.

You can simulate a StaleElementReferenceException in a Selenium test with the logic below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;

public class Main {
    public static void main(String[] args) {
        // setting the system property for the Chrome Driver
        System.setProperty("webdriver.chrome.driver", "<the_path_to_your_chrome_driver>");

        // initializing a Selenium WebDriver ChromeDriver instance
        WebDriver driver = new ChromeDriver();

        // connecting to your target web page
        driver.get("https://app.reflect.run/registration");

        // the locator strategy to retrieve the desired
        // name input field
        By nameInputBy = By.cssSelector("input.input-field");

        // retrieving an input HTML element from the page
        WebElement nameHtmlElement = driver.findElement(nameInputBy));

        // setting a name in the input element
        nameHtmlElement.sendKeys("John");

        // refreshing the page
        driver.navigate().refresh();

        // nameHtmlElement is now stale

        // trying to set a new name, but this will throw a StaleElementReferenceException
        nameHtmlElement.sendKeys("Maria");
    }
}

This simple test connects to the Reflect registration page below:

Then, nameHtmlElement stores a reference to the first input field in the form, which corresponds to the “First name” input element. The Selenium test interacts with it by filling it with the “John” string through the sendKeys() method. Then, it refreshes the page. This is when the nameHtmlElement WebElement reference becomes stale. This is because the HTML input element it originally referenced is no longer attached to the DOM.

So, when Selenium tries to interact again with nameHtmlElement, it will throw the following StaleElementReferenceException:

1
Exception in thread "main" org.openqa.selenium.StaleElementReferenceException: stale element reference: element is not attached to the page document

It does not matter that the refreshed web page still contains the name input field. What matters is that the nameHtmlElement reference points to a specific DOM element that was destroyed during the refresh. Therefore, the WebElement pointer variable refers to an HTML element that is no longer part of the actual DOM. This is why nameHtmlElement becomes stale after the refresh.

When does a StaleElementReferenceException occur in a Selenium test?

The two most common reasons for a StaleElementReferenceException are that the selected HTML element you have to interact with is no longer on the page, or it was destroyed and then recreated.

Let’s cover both scenarios below.

Reason #1: The HTML element is no longer present on the web page

This generally happens due to a JavaScript operation that dynamically updates the DOM. When interacting with some elements in a web page, you can trigger JavaScript operations that lead to permanent changes in the DOM.

For example, you select an HTML element in a list in your Selenium test. Then, you interact with the search input, the page performs an AJAX request, retrieves data, and overwrites the list with new elements. Now the original HTML element is no longer in the web page.

Similarly, after clicking on a button, a JavaScript event handler within the page end up removing some elements on the web page. This is pretty common when it comes to cancel or undo buttons. Again, HTML elements are being removed from the DOM.

Also, keep in mind that some DOM elements have a very short life cycle. For example, a banner to inform the user of an offer may remain on the page for a very limited amount of time and then disappear forever. This is also true for one-time notification elements.

In all these cases, your test may end up with several stale WebElement references. Each of these stale references will throw a StaleElementReferenceException if you interact with them.

Reason #2: The HTML element in the DOM was deleted and recreated

As demonstrated in the example above, this happens when you refresh the web page. This is because all DOM elements get destroyed and then recreated by the browser. In this scenario, the WebDriver variables you initialized in your Selenium test reference to elements that were in the previous DOM, but are not in the actual DOM.

Something similar can happen when the web page uses a client-side framework such as React or Vue. These technologies utilize a virtual DOM, which is an in-memory representation of the DOM that allows these frameworks to optimize the performance of their applications. These technologies must continuously keep the virtual DOM tree synchronized with the DOM tree, which means lots of potential stale elements.

For example, React keeps the virtual DOM synchronized with the real DOM with a process called reconciliation. When something changes in the state of the current page, React performs the reconciliation process. First, React creates a new virtual DOM tree. Then, it compares it to the previous tree using a diffing algorithm to understand what needs to be changed in the DOM. Finally, it uses ReactDOM to ensure the actual DOM gets the new to nodes and refreshes the updated elements. During the refresh of a node, the HTML element is deleted, recreated, and then inserted back into the DOM.

In other words, the technologies based on the virtual DOM swap HTML elements in and out of the DOM very frequently. When interacting with any reference to an element that has since been swapped out, Selenium will throw a StaleElementReferenceException.

Also, note that some HTML elements can be added to the DOM programmatically. For example, a modal can be created via JavaScript, injected into the DOM when needed, and deleted when it is no longer useful. In this case, the elements get deleted and then recreated each time as a result of certain actions.

How to avoid a StaleElementReferenceException

The only way to avoid a StaleElementReferenceException in a Selenium test is to refresh your stale reference. In other words, you have to re-apply the locator strategy and retrieve the HTML element again.

Here we modify our first example to re-retrieve the element when a StaleElementReferenceException occurs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;

public class Main {
    public static void main(String[] args) {
        // setting the system property for the Chrome Driver
        System.setProperty("webdriver.chrome.driver", "<the_path_to_your_chrome_driver>");

        // initializing a Selenium WebDriver ChromeDriver instance
        WebDriver driver = new ChromeDriver();

        // connecting to your target web page
        driver.get("https://app.reflect.run/registration");

        // the locator strategy to retrieve the desired
        // name input field
        By nameInputBy = By.cssSelector("input.input-field");

        // retrieving an input HTML element from the page
        WebElement nameHtmlElement = driver.findElement(nameInputBy));

        // setting a name in the input element
        nameHtmlElement.sendKeys("John");

        // refreshing the page
        driver.navigate().refresh();

        // nameHtmlElement is now stale

        try {
            // trying to set a new name...
            nameHtmlElement.sendKeys("Maria");
        } catch(StaleElementReferenceException e) {
            // retrieving the name input field again
            nameHtmlElement = driver.findElement(By.cssSelector("input.input-field"));
            // now nameHtmlElement is no longer stale
        }

        // you can now use nameHtmlElement without any problems
        nameHtmlElement.sendKeys("Maria");

        // ...
    }
}

As you can see, the try-catch block catches the StaleElementReferenceException and re-retrieve the desired element. This way, nameHtmlElement goes back to a non-stale state. So, you can then retry the sendKeys() operation, which will be executed successfully.

This solution works, but it is definitely cumbersome. You cannot expect to apply this try-catch logic every time you suspect a WebElement reference may be stale.

Similarly, you can apply the following logic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// verifying if the WebElement is stale
boolean isNameHtmlElementStale = ExpectedConditions.stalenessOf(nameHtmlElement).apply(driver);

// if the element is stale
if (isNameHtmlElementStale) {
    // re-retrieving the desired input HTML element
    nameHtmlElement = driver.findElement(nameInputBy);
}

// now nameHtmlElement is no longer stale

// performing the desired operation on nameHtmlElement
nameHtmlElement.sendKeys("Maria");

The ExpectedConditions.stalenessOf() static method allows you to check whether an element is attached to the DOM or not. Again, this allows you to avoid a StaleElementReferenceException. However, spreading these if checks all over your Selenium test may not be the best solution.

There are two more elegant solutions to avoid StaleElementReferenceExceptions. Let’s dig into both.

Solution #1: Defining a utility function based on retry logic

You can easily embed the retry logic presented above in a utility function defined as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static boolean executeOperationOnWebElement(
        WebDriver driver,
        WebElement htmlElement,
        By by,
        Consumer<WebElement> operation,
        int maxAttempts
) {
    // the flag indicating whether the operation passed
    // as a parameter was executed successfully
    boolean result = false;

    int attempts = 0;
    while (attempts < maxAttempts) {
        try {
            // trying to execute the operation on the
            // target WebElement
            operation.accept(htmlElement);

            result = true;

            break;
        } catch (StaleElementReferenceException e) {
            // overwriting the WebElement reference by
            // re-retrieving the desired HTML element
            // according to the By locator strategy
            htmlElement = driver.findElement(by);
        }

        attempts++;
    }

    return result;
}

What is important to note here is the Consumer parameter. This is used to pass the operation to perform on htmlElement to the utility function. Consumer was introduced in Java 8 and represents a functional interface that can be used as the assignment target for a lambda expression or method reference.

You can use this utility function as below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        // setting the system property for the Chrome Driver
        System.setProperty("webdriver.chrome.driver", "<the_path_to_your_chrome_driver>");

        // initializing a Selenium WebDriver ChromeDriver instance
        WebDriver driver = new ChromeDriver();

        // connecting to your target web page
        driver.get("https://app.reflect.run/registration");

        // the locator strategy to retrieve the desired
        // name input field
        By nameInputBy = By.cssSelector("input.input-field");

        // retrieving an input HTML element from the page
        WebElement nameHtmlElement = driver.findElement(nameInputBy);
        // setting a name in the input element
        nameHtmlElement.sendKeys("John");

        // refreshing the page
        driver.navigate().refresh();

        // nameHtmlElement is now stale

        boolean result = executeOperationOnWebElement(
                driver,
                nameHtmlElement,
                nameInputBy,
                (htmlElement) -> {
                    htmlElement.sendKeys("Maria");
                    // this should print "Maria"
                    System.out.println(htmlElement.getAttribute("value"));
                },
                3 // trying 3 times before failing
        );

        // ...
    }

    // the executeOperationOnWebElement function definition omitted for brevity...
}

After running this Selenium script, the following result will be printed:

1
Maria

This means that the executeOperationOnWebElement() works as expected and allows you to elegantly avoid a StaleElementReferenceException.

Solution 2: Using WebDriverWait

WebDriverWait allows you to wait for an HTML element to satisfy a specific predicate or condition. For example, you can use it to implement retry logic to deal with StaleElementReferenceExceptions as below:

1
2
3
4
5
6
new WebDriverWait(driver, Duration.ofSeconds(10))
        .ignoring(StaleElementReferenceException.class)
        .until((WebDriver d) -> {
            // your operation...
            return true;
        });

This code will continually attempt to perform the operation defined in the lambda function passed to the until() method, ignoring StaleElementReferenceExceptions, until the operation is executed successfully or the 10-second timeout is reached.

This is a Selenium-based, concise, and effective solution that does not require defining custom functions. You can use WebDriverWait to avoid StaleElementReferenceExceptions as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

public class Main {
    public static void main(String[] args) {
        // setting the system property for the Chrome Driver
        System.setProperty("webdriver.chrome.driver", "<the_path_to_your_chrome_driver>");

        // initializing a Selenium WebDriver ChromeDriver instance
        WebDriver driver = new ChromeDriver();

        // connecting to your target web page
        driver.get("https://app.reflect.run/registration");

        By nameInputBy = By.cssSelector("input.input-field");

        // retrieving an input HTML element from the page
        WebElement nameHtmlElement = driver.findElement(nameInputBy);
        // setting a name in the input element
        nameHtmlElement.sendKeys("John");

        // refreshing the page
        driver.navigate().refresh();

        // nameHtmlElement is now stale

        // ...

        new WebDriverWait(driver, Duration.ofSeconds(10))
                .ignoring(StaleElementReferenceException.class)
                .until((WebDriver d) -> {
                    WebElement htmlElement = d.findElement(nameInputBy);
                    htmlElement.sendKeys("Maria");

                    System.out.println(htmlElement.getAttribute("value"));

                    return true;
                });

        // ...
    }
}

Just like before, this Selenium test prints:

1
Maria

Conclusion

In this article, you learned what a StaleElementReferenceException is, why and when Selenium throws it, and you can avoid it. This exception occurs frequently when dealing with client-side frameworks based on the virtual DOM, such as React and Vue. Given the popularity of client-side frameworks, understanding why StaleElementReferenceException occurs and being able to debug it properly is essential to writing robust Selenium WebDriver tests.

Try Reflect: A testing platform with better defaults

Reflect is a no-code testing tool that lets you avoid the tedium associated with other testing tools. Reflect has built-in logic to detect and wait for operations to complete before proceeding to the next step. Not only does this help reduce test flakiness, but it also makes test creation a lot faster and easier.

Stop struggling with flaky tests — try it for free.

Get started with Reflect today

Create your first test in 2 minutes, no installation or setup required. Accelerate your testing efforts with fast and maintainable test suites without writing a line of code.

Copyright © Reflect Software Inc. All Rights Reserved.