How to avoid "StaleElementReferenceException" in Selenium?
Asked Answered
S

18

138

I am implementing a lot of Selenium tests using Java - sometimes, my tests fail due to a StaleElementReferenceException.

Could you suggest some approaches to making the tests more stable?

Staggers answered 19/10, 2012 at 4:45 Comment(5)
I recommend using this library, which solves the intermittent StaleElementReference exception and brings some enhancements to Selenium page Object model and Page Factory features: github.com/fslev/selenium-jutils#retry-on-errorDiffract
@SlevFlorin That library uses PageFactory which the Selenium creator that wrote it said not to use.Sangraal
In c# there is a method called StalenessOf(IWebElement element) inside the ExpectedConditions class which can be used. Don't know if the class also implements the method in javaLugar
@Sangraal could you please provide more details about where and why creator said not to use it ? Some doc link ? The official doc is here: github.com/SeleniumHQ/selenium/wiki/PageFactory and there is no note refering to what you saidDiffract
@SlevFlorin This is a video of Simon Stewart, the creator of WebDriver, Selenium Project lead, and creator of PageFactory at seleniumconf a few years ago stating not to use PageFactory. youtu.be/gyfUpOysIF8?t=1518. I started the section a little early to give context but at 27:27 he specifically states, "Don't use page factory."Sangraal
S
124

This can happen if a DOM operation happening on the page is temporarily causing the element to be inaccessible. To allow for those cases, you can try to access the element several times in a loop before finally throwing an exception.

Try this excellent solution from darrelgrainger.blogspot.com:

public boolean retryingFindClick(By by) {
    boolean result = false;
    int attempts = 0;
    while(attempts < 2) {
        try {
            driver.findElement(by).click();
            result = true;
            break;
        } catch(StaleElementException e) {
        }
        attempts++;
    }
    return result;
}
Selfloading answered 19/10, 2012 at 4:54 Comment(4)
It could be fixed also by using different reference of element.Spherulite
If the above doesn't solve it, updating to the latest chromedriver is what solved it for us.Nunes
@Nunes The version of ChromeDriver has nothing to do with this issue.Sangraal
@Selfloading Should we be doing this for every element we try to click since this reloading/StaleElement Exception can potentially happen anytime?Aalst
L
80

I was having this issue intermittently. Unbeknownst to me, BackboneJS was running on the page and replacing the element I was trying to click. My code looked like this.

driver.findElement(By.id("checkoutLink")).click();

Which is of course functionally the same as this.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
checkoutLink.click();

What would occasionally happen was the javascript would replace the checkoutLink element in between finding and clicking it, ie.

WebElement checkoutLink = driver.findElement(By.id("checkoutLink"));
// javascript replaces checkoutLink
checkoutLink.click();

Which rightfully led to a StaleElementReferenceException when trying to click the link. I couldn't find any reliable way to tell WebDriver to wait until the javascript had finished running, so here's how I eventually solved it.

new WebDriverWait(driver, timeout)
    .ignoring(StaleElementReferenceException.class)
    .until(new Predicate<WebDriver>() {
        @Override
        public boolean apply(@Nullable WebDriver driver) {
            driver.findElement(By.id("checkoutLink")).click();
            return true;
        }
    });

This code will continually try to click the link, ignoring StaleElementReferenceExceptions until either the click succeeds or the timeout is reached. I like this solution because it saves you having to write any retry logic, and uses only the built-in constructs of WebDriver.

Layer answered 24/8, 2014 at 9:34 Comment(1)
this answer has been deprecated.Tabasco
M
30

Kenny's solution is good, however it can be written in a more elegant way

new WebDriverWait(driver, timeout)
        .ignoring(StaleElementReferenceException.class)
        .until((WebDriver d) -> {
            d.findElement(By.id("checkoutLink")).click();
            return true;
        });

Or also:

new WebDriverWait(driver, timeout).ignoring(StaleElementReferenceException.class).until(ExpectedConditions.elementToBeClickable(By.id("checkoutLink")));
driver.findElement(By.id("checkoutLink")).click();

But anyway, best solution is to rely on Selenide library, it handles this kind of things and more. (instead of element references it handles proxies so you never have to deal with stale elements, which can be quite difficult). Selenide

Monotint answered 6/1, 2017 at 9:20 Comment(7)
Disclaimer: I'm just a happy selenide user, nothing to do with its developmentMonotint
Your second solution would work because element goes to stale when you click it not when you find it.Gentes
Use selenide to avoid this problem, much easier. Selenium is not meant to be used alone because of this issue and the fact that is a low level API for a simple userMonotint
I am perfectly aware of that. I am using WATIR which is a wrapper around Ruby Selenium Binding, WATIR automatically takes care of all these problem(for an instance stale element). I am looking for something equivalent in Java binding, I found Selenide but I don't know how to change the implicit wait and explicit wait in selenide. Can you tell me how to do that? Or is there any material you can offer me where I can refer? and what's your opinion on FluentLenium?Gentes
In my case I'm using a custom WebDriverProvider, so I'm responsible for creating the driver. I haven't used FluentLenium but I consider Selenide much powerful, altough to use it correctly you have to rely on Selenide waits (and forget about selenium implicit waits) like $(".mydiv").shouldHave(text("hello")) which already will wait (until Selenide Configuration.timeout)Monotint
People should know that the selected answer by the OP dates back to 2012. MANY things have changed in the past 7 years. This answer is more correct for 2019.Hereinbefore
I have written a light weight wrapper library on top of selenium that not only handles staleness by auto refreshing in such a way that you will NEVER get that exception (you will either find or not find the element) but also does a lot more (like centralized frame and window handling, ability to create relative web elements, central and element level control over implicit and explicit waits, and more. contact me if anyone is interested at [email protected]Cannae
H
24

Generally this is due to the DOM being updated and you trying to access an updated/new element -- but the DOM's refreshed so it's an invalid reference you have..

Get around this by first using an explicit wait on the element to ensure the update is complete, then grab a fresh reference to the element again.

Here's some psuedo code to illustrate (Adapted from some C# code I use for EXACTLY this issue):

WebDriverWait wait = new WebDriverWait(browser, TimeSpan.FromSeconds(10));
IWebElement aRow = browser.FindElement(By.XPath(SOME XPATH HERE);
IWebElement editLink = aRow.FindElement(By.LinkText("Edit"));

//this Click causes an AJAX call
editLink.Click();

//must first wait for the call to complete
wait.Until(ExpectedConditions.ElementExists(By.XPath(SOME XPATH HERE));

//you've lost the reference to the row; you must grab it again.
aRow = browser.FindElement(By.XPath(SOME XPATH HERE);

//now proceed with asserts or other actions.

Hope this helps!

Higinbotham answered 19/10, 2012 at 20:29 Comment(0)
P
15

The reason why the StaleElementReferenceException occurs has been laid out already: updates to the DOM between finding and doing something with the element.

For the click-Problem I've recently used a solution like this:

public void clickOn(By locator, WebDriver driver, int timeout)
{
    final WebDriverWait wait = new WebDriverWait(driver, timeout);
    wait.until(ExpectedConditions.refreshed(
        ExpectedConditions.elementToBeClickable(locator)));
    driver.findElement(locator).click();
}

The crucial part is the "chaining" of Selenium's own ExpectedConditions via the ExpectedConditions.refreshed(). This actually waits and checks if the element in question has been refreshed during the specified timeout and additionally waits for the element to become clickable.

Have a look at the documentation for the refreshed method.

Poore answered 27/10, 2016 at 14:33 Comment(0)
S
5

In my project I introduced a notion of StableWebElement. It is a wrapper for WebElement which is able to detect if element is Stale and find a new reference to the original element. I have added a helper methods to locating elements which return StableWebElement instead of WebElement and the problem with StaleElementReference disappeared.

public static IStableWebElement FindStableElement(this ISearchContext context, By by)
{
    var element = context.FindElement(by);
    return new StableWebElement(context, element, by, SearchApproachType.First);
} 

The code in C# is available on my project's page but it could be easily ported to java https://github.com/cezarypiatek/Tellurium/blob/master/Src/MvcPages/SeleniumUtils/StableWebElement.cs

Sacrificial answered 1/10, 2017 at 8:47 Comment(0)
M
0

A solution in C# would be:

Helper class:

internal class DriverHelper
{

    private IWebDriver Driver { get; set; }
    private WebDriverWait Wait { get; set; }

    public DriverHelper(string driverUrl, int timeoutInSeconds)
    {
        Driver = new ChromeDriver();
        Driver.Url = driverUrl;
        Wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeoutInSeconds));
    }

    internal bool ClickElement(string cssSelector)
    {
        //Find the element
        IWebElement element = Wait.Until(d=>ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
        return Wait.Until(c => ClickElement(element, cssSelector));
    }

    private bool ClickElement(IWebElement element, string cssSelector)
    {
        try
        {
            //Check if element is still included in the dom
            //If the element has changed a the OpenQA.Selenium.StaleElementReferenceException is thrown.
            bool isDisplayed = element.Displayed;

            element.Click();
            return true;
        }
        catch (StaleElementReferenceException)
        {
            //wait until the element is visible again
            element = Wait.Until(d => ExpectedConditions.ElementIsVisible(By.CssSelector(cssSelector)))(Driver);
            return ClickElement(element, cssSelector);
        }
        catch (Exception)
        {
            return false;
        }
    }
}

Invocation:

        DriverHelper driverHelper = new DriverHelper("http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp", 10);
        driverHelper.ClickElement("input[value='csharp']:first-child");

Similarly can be used for Java.

Marks answered 16/2, 2016 at 10:48 Comment(0)
T
0

Kenny's solution is deprecated use this, i'm using actions class to double click but you can do anything.

new FluentWait<>(driver).withTimeout(30, TimeUnit.SECONDS).pollingEvery(5, TimeUnit.SECONDS)
                    .ignoring(StaleElementReferenceException.class)
                    .until(new Function() {

                    @Override
                    public Object apply(Object arg0) {
                        WebElement e = driver.findelement(By.xpath(locatorKey));
                        Actions action = new Actions(driver);
                        action.moveToElement(e).doubleClick().perform();
                        return true;
                    }
                });
Tabasco answered 15/12, 2018 at 16:11 Comment(5)
This doesn't answer the question.Sangraal
I though it did.Tabasco
Ignoring the StaleElementReferenceException doesn't make it go away. This is just going to time out instead because once you get that exception, it doesn't just go away without some new action like refetching the element, etc.Sangraal
How do you refetch the element in selenium?Tabasco
By refetch I mean driver.findElement() again, store that element, and then do what needs to be done again. You might want to spend some time reading some articles on why StaleElementReferenceException is thrown, what a stale element is, and how to avoid it.Sangraal
S
0

I've found solution here. In my case element becomes inaccessible in case of leaving current window, tab or page and coming back again.

.ignoring(StaleElement...), .refreshed(...) and elementToBeClicable(...) did not help and I was getting exception on act.doubleClick(element).build().perform(); string.

Using function in my main test class:

openForm(someXpath);

My BaseTest function:

int defaultTime = 15;

boolean openForm(String myXpath) throws Exception {
    int count = 0;
    boolean clicked = false;
    while (count < 4 || !clicked) {
        try {
            WebElement element = getWebElClickable(myXpath,defaultTime);
            act.doubleClick(element).build().perform();
            clicked = true;
            print("Element have been clicked!");
            break;
        } catch (StaleElementReferenceException sere) {
            sere.toString();
            print("Trying to recover from: "+sere.getMessage());
            count=count+1;
        }
    }

My BaseClass function:

protected WebElement getWebElClickable(String xpath, int waitSeconds) {
        wait = new WebDriverWait(driver, waitSeconds);
        return wait.ignoring(StaleElementReferenceException.class).until(
                ExpectedConditions.refreshed(ExpectedConditions.elementToBeClickable(By.xpath(xpath))));
    }
Sweetie answered 22/8, 2019 at 13:1 Comment(0)
M
0

Clean findByAndroidId method that gracefully handles StaleElementReference.

This is heavily based off of jspcal's answer but I had to modify that answer to get it working cleanly with our setup and so I wanted to add it here in case it's helpful to others.

// This loops gracefully handles StateElementReference errors and retries up to 10 times. These can occur when an element, like a modal or notification, is no longer available.
export async function findByAndroidId( id, { assert = wd.asserters.isDisplayed, timeout = 10000, interval = 100 } = {} ) {
  MAX_ATTEMPTS = 10;
  let attempt = 0;

  while( attempt < MAX_ATTEMPTS ) {
    try {
      return await this.waitForElementById( `android:id/${ id }`, assert, timeout, interval );
    }
    catch ( error ) {
      if ( error.message.includes( "StaleElementReference" ) )
        attempt++;
      else
        throw error; // Re-throws the error so the test fails as normal if the assertion fails.
    }
  }
}
Maurili answered 12/12, 2019 at 22:7 Comment(0)
G
0

Create a wrapper function (Java)

As an alternative to the accepted answer, my approach is similar in that it catches the exception and makes a few attempts, but it's more generic, so you can throw any kinds of actions at it as long as they are wrapped in a void function.

Please feel free to copy and use this code:

public void doPreventingStaleElement(Runnable function)
{
    int maxRetries = 3; // maximum number of retries 
    int retries = 0;
    boolean stale;
    
    // try/catch block attempts to fix a stale element
    do  {
        try {
            function.run();
            stale = false;
        }
        catch (StaleElementReferenceException eStale) {
            stale = true;
            // Work-around for stale element reference when getting the first page
            if (retries < maxRetries) {
                retries++;
                System.out.println(function.getClass().getSimpleName() + " failed due to stale element reference, retry=" + retries);
                 try {
                     // Exponential increase of wait time - 1, 4, 9, 16, 25 seconds
                     Thread.sleep(1000 * (int) Math.pow(retries,2));
                 } catch (final InterruptedException e) {
                     Thread.currentThread().interrupt();
                 }
            }
            else {
                System.out.println(function.getClass().getSimpleName() + " failed due to stale element reference, too many retries");
                eStale.printStackTrace();
                throw(eStale);
            }
        }
         
    } while (stale && retries < maxRetries);
    
    return;
}

Note that it will still throw a StaleElementReferenceException after maxRetries attempts.

Example of usage

As an example I want to do this:

final List<WebElement> buttons = getDriver().findElements(By.xpath("//button[@testid='dismiss-me']"));
for (final WebElement closeButton : buttons) {
    closeButton.click();
}

or this:

driver.findElement(By.id("login-form-username")).sendKeys(getUser());
driver.findElement(By.id("login-form-password")).sendKeys(getPassword());
driver.findElement(By.id("login-form-submit")).click();

Then I wrap them in void functions

private void clickButtons() {

    final List<WebElement> buttons = getDriver().findElements(By.xpath("//button[@testid='dismiss-me']"));
    for (final WebElement closeButton : buttons) {
        closeButton.click();
    }
}

private void performLogin() {
    driver.findElement(By.id("login-form-username")).sendKeys(getUser());
    driver.findElement(By.id("login-form-password")).sendKeys(getPassword());
    driver.findElement(By.id("login-form-submit")).click();
}

and so I can just

doPreventingStaleElement(whateverObject::clickButtons);
doPreventingStaleElement(whateverObject::performLogin);
Groce answered 2/2, 2023 at 13:21 Comment(0)
G
0

StaleElementReferenceException

StaleElementReferenceException indicates that the reference to an element is now stale i.e. the element no longer appears within the HTML DOM of the page.


Details

Every DOM Tree element is identified by the WebDriver by a unique identifying reference, known as a WebElement. The web element reference is a UUID used to execute commands targeting specific elements, such as getting an element's tag name or retrieving a property off an element.

When an element is no longer attached to the DOM, i.e. it has been removed from the document or the document has changed, it is said to be got stale. Staleness occurs for example when you have a web element reference and the document it was retrieved from navigates and due to navigation, all web element references to the previous document will be discarded along with the document. This will cause any subsequent interaction with the web element to fail with the stale element reference error.


Solution

The best approach to avoid StaleElementReferenceException is to induce WebDriverWait for the elementToBeClickable() before invoking click as follows:

new WebDriverWait(driver, Duration.ofSeconds(10)).until(ExpectedConditions.elementToBeClickable(By.cssSelector("elementCssSelector"))).click();

Note: You have to import the following:

import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.By;
Gavial answered 3/2, 2023 at 14:11 Comment(0)
C
-1

This works for me using C#

public Boolean RetryingFindClick(IWebElement webElement)
    {
        Boolean result = false;
        int attempts = 0;
        while (attempts < 2)
        {
            try
            {
                webElement.Click();
                result = true;
                break;
            }
            catch (StaleElementReferenceException e)
            {
                Logging.Text(e.Message);
            }
            attempts++;
        }
        return result;
    }
Colon answered 19/2, 2018 at 10:48 Comment(2)
how does it differ from the above solution ? What is 100% in computer science ?Rouse
@Rouse I guess this is the same implementation but in C# rather than Java - although almost identical?Unanimous
S
-1

The problem is by the time you pass the element from Javascript to Java back to Javascript it can have left the DOM.
Try doing the whole thing in Javascript:

driver.executeScript("document.querySelector('#my_id')?.click()") 
Swanskin answered 3/1, 2019 at 1:27 Comment(6)
This isn't going to do really anything different than using the Java method except you don't get any Intellisense with the JS code, you bring in a whole new category of problems by using JS inside Java, etc.Sangraal
It's actually completely different. It avoids stale element issues by getting that dom element immediately. If you miss intellisense you should switch to Javascript which makes more sense anyway.Swanskin
No, it actually doesn't get it any faster. If nothing else, it's probably slightly slower because the JS has to be interpreted instead of just using driver.findElement(By.id()).click();. The end result is exactly the same.Sangraal
No, it's the other way around. The browser speaks Javascript, not Java.Swanskin
This question is about Java. You are suggesting that loading Java libraries, calling a Java method that in turn interprets JS code, evaluates it, and then comes all the way back out is faster than just calling the Java method? I don't think so.Sangraal
There are 2 different runtimes. The click happens in the browser's runtime, which I guess is not obvious.Swanskin
S
-1

Try this

while (true) { // loops forever until break
    try { // checks code for exceptions
        WebElement ele=
        (WebElement)wait.until(ExpectedConditions.elementToBeClickable((By.xpath(Xpath))));  
        break; // if no exceptions breaks out of loop
    } 
    catch (org.openqa.selenium.StaleElementReferenceException e1) { 
        Thread.sleep(3000); // you can set your value here maybe 2 secs
        continue; // continues to loop if exception is found
    }
}
Suffrage answered 14/2, 2019 at 13:45 Comment(1)
This code can very easily get into an infinite loop. You should just implement a counter or timeout to avoid this. Also, you don't need to hardcode a 3s sleep... that's wasted since the page isn't reloading so you can just try again immediately.Sangraal
H
-1

There could be a potential problem that leads to the StaleElementReferenceException that no one mentioned so far (in regard to actions).

I explain it in Javascript, but it's the same in Java.

This won't work:

let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform() // this leads to a DOM change, #b will be removed and added again to the DOM.
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()

But instantiating the actions again will solve it:

let actions = driver.actions({ bridge: true })
let a = await driver.findElement(By.css('#a'))
await actions.click(a).perform()  // this leads to a DOM change, #b will be removed and added again to the DOM.
actions = driver.actions({ bridge: true }) // new
let b = await driver.findElement(By.css('#b'))
await actions.click(b).perform()
Halette answered 7/10, 2019 at 10:18 Comment(1)
If it's the same in Java, then write it in Java. That's what the OP is asking for.Sangraal
O
-1

Usually StaleElementReferenceException when element we try to access has appeared but other elements may affect the position of element we are intrested in hence when we try to click or getText or try to do some action on WebElement we get exception which usually says element not attached with DOM.

Solution I tried is as follows:

 protected void clickOnElement(By by) {
        try {
            waitForElementToBeClickableBy(by).click();
        } catch (StaleElementReferenceException e) {
            for (int attempts = 1; attempts < 100; attempts++) {
                try {
                    waitFor(500);
                    logger.info("Stale element found retrying:" + attempts);
                    waitForElementToBeClickableBy(by).click();
                    break;
                } catch (StaleElementReferenceException e1) {
                    logger.info("Stale element found retrying:" + attempts);
                }
            }
        }

protected WebElement waitForElementToBeClickableBy(By by) {
        WebDriverWait wait = new WebDriverWait(getDriver(), 10);
        return wait.until(ExpectedConditions.elementToBeClickable(by));
    }

In above code I first try to wait and then click on element if exception occurs then I catch it and try to loop it as there is a possibility that still all elements may not be loaded and again exception can occur.

Overact answered 29/7, 2020 at 14:10 Comment(1)
Your definition of a stale element is not correct. You should probably take a few minutes and google the definition.Sangraal
K
-4

Maybe it was added more recently, but other answers fail to mention Selenium's implicit wait feature, which does all the above for you, and is built into Selenium.

driver.manage().timeouts().implicitlyWait(10,TimeUnit.SECONDS);

This will retry findElement() calls until the element has been found, or for 10 seconds.

Source - http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp

Kurgan answered 14/10, 2015 at 13:37 Comment(2)
That solution does not prevent StaleElementReferenceExceptionHomeroom
Just to eliminate any confusion on versions, even in the latest version of Selenium implicitlyWait() does NOT prevent StaleElementReferenceException. I use a method which calls in a loop with sleep, till success or fixed count.Ruelle

© 2022 - 2024 — McMap. All rights reserved.