135

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?

Scott Anderson
  • 631
  • 5
  • 26
hoang nguyen
  • 2,119
  • 5
  • 21
  • 20
  • I recommend using this library, which solves the intermittent StaleElementReference exception and brings some enhancements to Selenium page Object model and Page Factory features: https://github.com/fslev/selenium-jutils#retry-on-error – Slev Florin Jul 27 '21 at 16:37
  • 1
    @SlevFlorin That library uses `PageFactory` which the Selenium creator that wrote it said not to use. – JeffC Oct 10 '21 at 20:59
  • 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 java – 3r1c Jun 01 '22 at 13:57
  • @JeffC could you please provide more details about where and why creator said not to use it ? Some doc link ? The official doc is here: https://github.com/SeleniumHQ/selenium/wiki/PageFactory and there is no note refering to what you said – Slev Florin Oct 13 '22 at 18:48
  • @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. https://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." – JeffC Oct 14 '22 at 14:07

18 Answers18

122

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;
}
HaveSpacesuit
  • 3,572
  • 6
  • 40
  • 59
jspcal
  • 50,847
  • 7
  • 72
  • 76
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.

Kenny
  • 1,292
  • 2
  • 15
  • 21
29

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

cocorossello
  • 1,289
  • 1
  • 17
  • 30
  • Disclaimer: I'm just a happy selenide user, nothing to do with its development – cocorossello Dec 17 '18 at 09:10
  • Your second solution would work because element goes to stale when you click it not when you find it. – Rajagopalan Mar 25 '19 at 09:35
  • 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 user – cocorossello Mar 25 '19 at 10:12
  • 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? – Rajagopalan Mar 25 '19 at 11:51
  • 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) – cocorossello Mar 26 '19 at 08:56
  • 2
    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. – hfontanez Apr 02 '19 at 17:50
  • 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 rehzon@yahoo.com – Jawad Nov 05 '19 at 21:06
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!

Jim Holmes
  • 1,680
  • 8
  • 10
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.

Christian.D
  • 879
  • 1
  • 9
  • 21
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

cezarypiatek
  • 1,078
  • 11
  • 21
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.

George Kargakis
  • 4,940
  • 3
  • 16
  • 12
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;
                    }
                });
vaibhavcool20
  • 861
  • 3
  • 11
  • 28
  • This doesn't answer the question. – JeffC Oct 10 '21 at 21:06
  • I though it did. – vaibhavcool20 Dec 01 '21 at 05:05
  • 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. – JeffC Dec 01 '21 at 16:31
  • How do you refetch the element in selenium? – vaibhavcool20 Dec 07 '21 at 14:20
  • 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. – JeffC Dec 07 '21 at 15:02
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))));
    }
Gryu
  • 2,102
  • 2
  • 16
  • 29
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.
    }
  }
}
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
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);
not2savvy
  • 2,902
  • 3
  • 22
  • 37
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;
undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
-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;
    }
Shivam Bharadwaj
  • 1,864
  • 21
  • 23
-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()") 
pguardiario
  • 53,827
  • 19
  • 119
  • 159
  • 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. – JeffC Oct 10 '21 at 21:06
  • 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. – pguardiario Oct 10 '21 at 23:09
  • 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. – JeffC Oct 10 '21 at 23:13
  • No, it's the other way around. The browser speaks Javascript, not Java. – pguardiario Oct 10 '21 at 23:25
  • 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. – JeffC Oct 10 '21 at 23:34
  • There are 2 different runtimes. The click happens in the browser's runtime, which I guess is not obvious. – pguardiario Oct 10 '21 at 23:44
-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
    }
}
Matt
  • 1,518
  • 4
  • 16
  • 30
CodingGirl1
  • 21
  • 2
  • 5
  • 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. – JeffC Oct 10 '21 at 21:05
-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()
hardfork
  • 2,470
  • 1
  • 23
  • 43
-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.

  • Your definition of a stale element is not correct. You should probably take a few minutes and google the definition. – JeffC Oct 10 '21 at 21:00
-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

lockedan
  • 5
  • 1
  • 4
    That solution does not prevent StaleElementReferenceException – MrSpock May 24 '16 at 14:18
  • 2
    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. – Angsuman Chakraborty Feb 08 '17 at 07:48