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:
|
|
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
:
|
|
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:
|
|
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:
|
|
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 StaleElementReferenceException
s. 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:
|
|
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:
|
|
After running this Selenium script, the following result will be printed:
|
|
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 StaleElementReferenceException
s as below:
|
|
This code will continually attempt to perform the operation defined in the lambda function passed to the
until()
method, ignoring StaleElementReferenceException
s, 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 StaleElementReferenceException
s as follows:
|
|
Just like before, this Selenium test prints:
|
|
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.