hoang nguyen
hoang nguyen

Reputation: 2189

How to avoid "StaleElementReferenceException" in Selenium?

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?

Upvotes: 141

Views: 361017

Answers (19)

Sanket Bhosale
Sanket Bhosale

Reputation: 48

One of the best ways to handle it is by using a try-catch block. Just perform the same action after catching the StaleElementReferenceException.

try {
    WebElement myElement = driver.findElement(By.id("example"));
    myElement.click();
} catch (StaleElementReferenceException e) {
    WebElement myElement = driver.findElement(By.id("example"));
    myElement.click(); // Try to locate the element again
}

Upvotes: 0

Joshua Pinter
Joshua Pinter

Reputation: 47471

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.
    }
  }
}

Upvotes: 0

not2savvy
not2savvy

Reputation: 4243

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);

Upvotes: 0

undetected Selenium
undetected Selenium

Reputation: 193058

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;

Upvotes: 0

pguardiario
pguardiario

Reputation: 54984

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()") 

Upvotes: -1

Shivam Bharadwaj
Shivam Bharadwaj

Reputation: 2286

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;
    }

Upvotes: -1

Rakesh Singh Chouhan
Rakesh Singh Chouhan

Reputation: 233

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.

Upvotes: -1

hardfork
hardfork

Reputation: 3251

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()

Upvotes: -1

Gryu
Gryu

Reputation: 2189

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))));
    }

Upvotes: 0

CodingGirl1
CodingGirl1

Reputation: 21

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
    }
}

Upvotes: -1

vaibhavcool20
vaibhavcool20

Reputation: 871

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;
                    }
                });

Upvotes: 0

cocorossello
cocorossello

Reputation: 1357

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

Upvotes: 32

jspcal
jspcal

Reputation: 51894

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;
}

Upvotes: 125

cezarypiatek
cezarypiatek

Reputation: 1124

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

Upvotes: 5

Christian.D
Christian.D

Reputation: 949

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.

Upvotes: 17

George Kargakis
George Kargakis

Reputation: 5556

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.

Upvotes: 0

lockedan
lockedan

Reputation: 5

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

Upvotes: -4

Kenny
Kenny

Reputation: 1302

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.

Upvotes: 81

Jim Holmes
Jim Holmes

Reputation: 1680

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!

Upvotes: 24

Related Questions