37
votes

I have the following code in a Selenium 2 Web Driver test which works when I am debugging but most of the time fails when I run it in the build. I know it must be something to do with the way the page is not being refreshed but do not know how to resolve it so any pointers as to what I have done wrong are appreciated. I am using JSF primefaces as my web application framework. When I click on the add new link a popup dialog box appears with a input box that I can enter a date into then click save. It is on getting the input element to enter text into that I get a stale element ref exception.

Thanks in advance

import static org.junit.Assert.assertEquals;

 import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.WebDriverWait;


public class EnterActiveSubmissionIntegrationTest {
Map<String, Map<String, String>> tableData = new HashMap<String, Map<String, String>>();

@Test
public void testEnterActiveSubmission() throws Exception {
    // Create a new instance of the Firefox driver
    // Notice that the remainder of the code relies on the interface, 
    // not the implementation.
    System.setProperty("webdriver.chrome.driver", "C:/apps/chromedriver.exe");
    WebDriver driver = new ChromeDriver();

    // And now use this to visit Google
    driver.get("http://localhost:8080/strfingerprinting");
    // Alternatively the same thing can be done like this
    // driver.navigate().to("http://www.google.com");

    // Find the text input element by its name
    WebElement element = driver.findElement(By.linkText("Manage Submissions"));
    element.click();
    parseTableData(driver, "form:submissionDataTable_data", 1);
    assertEquals(tableData.get("form:submissionDataTable_data").get("12"), "Archived");

    WebElement newElement = driver.findElement(By.linkText("Add new"));
    newElement.click();

    WebDriverWait wait = new WebDriverWait(driver,10);
    wait.until(new ExpectedCondition<Boolean>() {
        public Boolean apply(WebDriver driver) {
            WebElement button = driver.findElement(By
                    .name("createForm:dateInput_input"));

            if (button.isDisplayed())
                return true;
            else
                return false;

        }
    });

    WebElement textElement = driver.findElement(By.name("createForm:dateInput_input"));
    textElement.sendKeys("24/04/2013");
    WebElement saveElement = driver.findElement(By.name("createForm:saveButton"));
    saveElement.click();

    driver.navigate().refresh();

    parseTableData(driver, "form:submissionDataTable_data", 2);

    //Close the browser
    driver.quit();
}



private void parseTableData(WebDriver driver, String id, int expectedRows) {
    // Check the title of the page or expected element on page
    WebElement subTableElement = driver.findElement(By.id(id));
    List<WebElement> tr_collection=subTableElement.findElements(By.xpath("id('"+ id + "')/tr"));

    assertEquals("incorrect number of rows returned", expectedRows, tr_collection.size());
    int row_num,col_num;
    row_num=1;

    if(tableData.get(id) == null) {
        tableData.put(id, new HashMap<String, String>());
    }
    Map<String, String> subTable = tableData.get(id);
    for(WebElement trElement : tr_collection)
    {
        List<WebElement> td_collection=trElement.findElements(By.xpath("td"));
        col_num=1;
        for(WebElement tdElement : td_collection)
        {
            subTable.put(row_num + "" + col_num, tdElement.getText());
            col_num++;
        }
        row_num++;
    }
}
}

When I run this I get the following exception but it can occur on

WebElement textElement = driver.findElement(By.name("createForm:dateInput_input")); 

or

if (button.isDisplayed())

exception trace

org.openqa.selenium.StaleElementReferenceException: stale element reference: element is not attached to the page document
(Session info: chrome=26.0.1410.64)
  (Driver info: chromedriver=0.8,platform=Windows NT 6.0 SP2 x86) (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 56 milliseconds
For documentation on this error, please visit:        http://seleniumhq.org/exceptions/stale_element_reference.html
Build info: version: '2.32.0', revision: '6c40c187d01409a5dc3b7f8251859150c8af0bcb', time: '2013-04-09 10:39:28'
System info: os.name: 'Windows Vista', os.arch: 'x86', os.version: '6.0', java.version: '1.6.0_10'
Session ID: 784c53b99ad83c44d089fd04e9a42904
Driver info: org.openqa.selenium.chrome.ChromeDriver
Capabilities [{platform=XP, acceptSslCerts=true, javascriptEnabled=true,   browserName=chrome, rotatable=false, driverVersion=0.8, locationContextEnabled=true,  version=26.0.1410.64, cssSelectorsEnabled=true, databaseEnabled=true, handlesAlerts=true,  browserConnectionEnabled=false, nativeEvents=true, webStorageEnabled=true,   applicationCacheEnabled=false, takesScreenshot=true}]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at  sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at  sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:187)
at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:145)
at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:554)
at org.openqa.selenium.remote.RemoteWebElement.execute(RemoteWebElement.java:268)
at org.openqa.selenium.remote.RemoteWebElement.isDisplayed(RemoteWebElement.java:320)
at com.integration.web.EnterActiveSubmissionIntegrationTest$1.apply(EnterActiveSubmissionIntegrationTest.java:58)
at com.integration.web.EnterActiveSubmissionIntegrationTest$1.apply(EnterActiveSubmissionIntegrationTest.java:1)
at org.openqa.selenium.support.ui.FluentWait.until(FluentWait.java:208)
at com.integration.web.EnterActiveSubmissionIntegrationTest.testEnterActiveSubmission(EnterActiveSubmissionIntegrationTest.java:53)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
22
Can anyone advise on this?user1107753

22 Answers

63
votes

First of all lets be clear about what a WebElement is.

A WebElement is a reference to an element in the DOM.

A StaleElementException is thrown when the element you were interacting is destroyed and then recreated. Most complex web pages these days will move things about on the fly as the user interacts with it and this requires elements in the DOM to be destroyed and recreated.

When this happens the reference to the element in the DOM that you previously had becomes stale and you are no longer able to use this reference to interact with the element in the DOM. When this happens you will need to refresh your reference, or in real world terms find the element again.

23
votes

This is not a problem. If you wrap your .findElement call in a try-catch block and catch the StaleElementReferenceException , then you can loop and retry as many times as you need until it succeeds.

Here are some examples I wrote.

Another example from Selenide project:

public static final Condition hidden = new Condition("hidden", true) {
    @Override
    public boolean apply(WebElement element) {
      try {
        return !element.isDisplayed();
      } catch (StaleElementReferenceException elementHasDisappeared) {
        return true;
      }
    }
  };
20
votes

What was happening to me was that webdriver would find a reference to a DOM element and then at some point after that reference was obtained, javascript would remove that element and re-add it (because the page was doing a redraw, basically).

Try this. Figure out the action that causes the dom element to be removed from the DOM. In my case, it was an async ajax call, and the element was being removed from the DOM when the ajax call was complete. Right after that action, wait for the element to be stale:

... do a thing, possibly async, that should remove the element from the DOM ...
wait.until(ExpectedConditions.stalenessOf(theElement));

At this point you are sure that the element is now stale. So, the next time you reference the element, wait again, this time waiting for it to be re-added to the DOM:

wait.until(ExpectedConditions.presenceOfElementLocated(By.id("whatever")))
12
votes

Two reasons for Stale element

  1. An element that is found on a web page referenced as a WebElement in WebDriver then the DOM changes (probably due to JavaScript functions) that WebElement goes stale.

  2. The element has been deleted entirely.

When you try to interact with the staled WebElement[any above case], the StaleElementException is thrown.

How to avoid/resolve Stale Exception?

  1. Storing locators to your elements instead of references
driver = webdriver.Firefox();
driver.get("http://www.github.com");
search_input = lambda: driver.find_element_by_name('q');
search_input().send_keys('hello world\n'); 
time.sleep(5);


search_input().send_keys('hello frank\n') // no stale element exception
  1. Leverage hooks in the JS libraries used
   # Using Jquery queue to get animation queue length.
    animationQueueIs = """
    return $.queue( $("#%s")[0], "fx").length;
    """ % element_id
    wait_until(lambda: self.driver.execute_script(animationQueueIs)==0)
  1. Moving your actions into JavaScript injection
 self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
  1. Proactively wait for the element to go stale
  # Wait till the element goes stale, this means the list has updated
  wait_until(lambda: is_element_stale(old_link_reference))

This solution, which worked for me, I have mentioned here if you have any additional scenario, which worked for you then comment below

9
votes

Try waiting for an element like this:

// Waiting 30 seconds for an element to be present on the page, checking
// for its presence once every 5 seconds.
Wait<WebDriver> stubbornWait = new FluentWait<WebDriver>(driver)
    .withTimeout(30, SECONDS)
    .pollingEvery(5, SECONDS)
    .ignoring(NoSuchElementException.class)
    .ignoring(StaleElementReferenceException.class);

WebElement foo = stubbornWait.until(new Function<WebDriver, WebElement>() {
    public WebElement apply(WebDriver driver) {
        return driver.findElement(By.id("foo"));
    }
});
4
votes

StaleElementReferenceException is due to unavailability of an element being accessed by findelement method.

You need make sure before performing any operations on an element(If you have a doubt on availability of that element)

Waiting for an element's visibility

(new WebDriverWait(driver, 10)).until(new ExpectedCondition()
    {
           public Boolean apply(WebDriver d) {
              return d.findElement(By.name("createForm:dateInput_input")).isDisplayed();
     }});

Or else Use this logic to verify whether the element is present or not.

2
votes

Use the Expected Conditions provided by Selenium to wait for the WebElement.

While you debug, the client is not as fast as if you just run a unit test or a maven build. This means in debug mode the client has more time to prepare the element, but if the build is running the same code he is much faster and the WebElement your looking for is might not visible in the DOM of the Page.

Trust me with this, I had the same problem.

for example:

inClient.waitUntil(ExpectedConditions.visibilityOf(YourElement,2000))

This easy method calls wait after his call for 2 seconds on the visibility of your WebElement on DOM.

1
votes

I have solved this problem with the following code.

public WebElement waitForElement(final By findBy, final int waitTime) {
    Wait<AppiumDriver> wait = new FluentWait<>((AppiumDriver) driver)
            .withTimeout(waitTime, TimeUnit.SECONDS)
            .pollingEvery(POLL_TIME, TimeUnit.SECONDS)
            .ignoring(NoSuchElementException.class,StaleElementReferenceException.class);

    WebElement webElement = wait.until(new Function<AppiumDriver, WebElement>() {
        @Override
        public WebElement apply(AppiumDriver driver) {
            System.out.println("Trying to find element " + findBy.toString());                
            WebElement element = driver.findElement(findBy);
            return element;
        }
    });
    return webElement;
}
1
votes

I would suggest not to use @CachelookUp for Selenium WebDriver for StaleElementReferenceException.

If you are using @FindBy annotation and have @CacheLookUp, just comment it out and check.

1
votes

When a Stale Element Exception occurs!!

Stale element exception can happen when the libraries supporting those textboxes/ buttons/ links has changed which means the elements are same but the reference has now changed in the website without affecting the locators. Thus the reference which we stored in our cache including the library reference has now become old or stale because the page has been refreshed with updated libraries.

for(int j=0; j<5;j++)
try {
    WebElement elementName=driver.findElement(By.name(“createForm:dateInput_input”));
    break;
} catch(StaleElementReferenceException e){
e.toString();
System.out.println(“Stale element error, trying ::  ” + e.getMessage());
}
elementName.sendKeys(“20/06/2018”);
0
votes

This worked for me (source here):

 /**
     * Attempts to click on an element multiple times (to avoid stale element
     * exceptions caused by rapid DOM refreshes)
     *
     * @param d
     *            The WebDriver
     * @param by
     *            By element locator
     */
    public static void dependableClick(WebDriver d, By by)
    {
        final int MAXIMUM_WAIT_TIME = 10;
        final int MAX_STALE_ELEMENT_RETRIES = 5;

        WebDriverWait wait = new WebDriverWait(d, MAXIMUM_WAIT_TIME);
        int retries = 0;
        while (true)
        {
            try
            {
                wait.until(ExpectedConditions.elementToBeClickable(by)).click();

                return;
            }
            catch (StaleElementReferenceException e)
            {
                if (retries < MAX_STALE_ELEMENT_RETRIES)
                {
                    retries++;
                    continue;
                }
                else
                {
                    throw e;
                }
            }
        }
    }
0
votes

The WebDriver have to wait until the Element is located and a timeout is after 10 seconds.

WebElement myDynamicElement1 = new WebDriverWait(driver, 10).until(
    ExpectedConditions.presenceOfElementLocated(
        By.name("createForm:dateInput_input")
    )
);
0
votes

Use webdriverwait with ExpectedCondition in try catch block with for loop EX: for python

for i in range(4):
    try:
        element = WebDriverWait(driver, 120).until( \
                EC.presence_of_element_located((By.XPATH, 'xpath')))
        element.click()    
        break
    except StaleElementReferenceException:
        print "exception "
0
votes

This solution worked fine for me:

Adding error handling function and try again

var pollLoop = function () {
      element(by.id('spinnerElem')).getAttribute('class').then(function (cls) {
                if (cls.indexOf('spinner-active') > -1) {
                    // wait for the spinner
                } else {
                    //do your logic
                    promise.defer().fulfill();
                }
            }, function () {
                // This err handling function is to handle the {StaleElementReferenceError} and makes sure we find the element always.
                pollLoop();
            });
        };
0
votes

After deep investigation of the problem I found that error occurs selecting DIV elements that were added for Bootstrap only. Chrome browser removes such DIVS and the error occurs. It is enough to step down and select real element for fixing an error. For example, my modal dialog has structure:

<div class="modal-content" uib-modal-transclude="">
    <div class="modal-header">
        ...
    </div>
    <div class="modal-body">
        <form class="form-horizontal ...">
            ...
        </form>
    <div>
<div>

Selecting div class="modal-body" generates an error, selecting form ... works as it would.

0
votes

In my case, this error was caused by the fact that I was defining the ActionChains element outside of the

def parse(self, response):

method when using a combination of Selenium and Scrapy, e.g.:

Doesn't work:

class MySpider(scrapy.Spider):
     action_chains = ActionChains(self.driver)

Moving action_chains = ActionChains(self.driver) inside the def parse(self, response): solved the problem, e.g.:

Works:

def parse(self, response):
     self.driver.get(response.url)
     action_chains = ActionChains(self.driver)
0
votes

The best way I have found to avoid stale element references is to not use the PageFactory, but instead store the locators (i.e. By elements).

public class WebDriverFactory {

    // if you want to multithread tests, use a ThreadLocal<WebDriver> 
    // instead.
    // This also makes it so you don't have to pass around WebDriver objects
    // when instantiating new Page classes
    private static WebDriver driver = null;

    public static WebDriver getDriver() {
       return driver;
    }
    public static void setDriver(WebDriver browser)  {
       driver = browser;
    }       
}

// class to let me avoid typing out the lengthy driver.findElement(s) so 
// much
public Abstract class PageBase {
    private WebDriver driver = WebDriverFactory.getDriver();

    // using var args to let you easily chain locators
    protected By getBy(By... locator) {
      return new ByChained(locator);
    }

    protected WebElement find(By... locators) {
      return driver.findElement(getBy(locators));
    }

    protected List<WebElement> findEm(By... locators) {
      return driver.findElements(getBy(locators));
    }

    protected Select select(By... locators) {
      return new Select(getBy(locators));
    }
}

public class somePage extends PageBase {
  private static WebDriver driver = WebDriverFactory.getDriver();
  private static final By buttonBy = By.cssSelector(".btn-primary");

  public void clickButton() {
     WebDriverWait wait = new WebDriverWait(driver, 10);
     wait.until(ExpectedConditions.elementToBeClickable(buttonBy));
     find(buttonBy).click();
  }

}

I have a class full of static WebDriverWait methods that I use. And I don't remember if the above use of WebDriver wait will handle the StaleElement exception or not. If not, you could use a fluent wait instead like in DjangoFan's answer. But the principle I'm displaying will work (even if that specific line with the WebDriverWait blows up.

So the tldr;

  1. Use locators, and a combination of WebDriverWait/Fluent wait/ relocating the element yourself, so if your element goes stale, you can relocate it without having to duplicate the locator in an @FindBy (for a pagefactory initialized element), since there's no WebElement.relocate() method.
  2. To simplify life, have an Abstract BasePage class with convenience methods for locating an element/list of elements.
0
votes

I tried many of the above suggestions, but simplest one worked. In my case it was the use of @CachelookUp for the web element caused the stale element exception. I guess after refreshing the page, the element reference not reloaded and failed to find the element. Disabling @CachelookUp line for the element worked.

    //Search button
    @FindBy(how=How.XPATH, using =".//input[@value='Search']")  
    //@CachelookUp
    WebElement BtnSearch;
0
votes

In Selenium 3, you can use ExpectedConditions.refreshed(your condition).

It will handle the StaleElementReferenceException and retry the condition.

-1
votes

Please donot confuse others amongs ourselves, if we are not sure on the answers. It is quite frustrating for end user. The simple and the short answer is use the @CacheLookup annotation in webdriver. Please refer below link for it. How does @CacheLookup work in WebDriver?

-1
votes

With reference to the answer given by @djangofan, it looks like the viable solution is to keep your code inside try catch block where a possible Staleness occurs. When I use this below code I didn't get the problem any time.

public void inputName(String name)
{
    try {
        waitForVisibilityElement(name);//My own visibility function
        findElement(By.name("customerName")).sendKeys(name);
    }
    catch (StaleElementReferenceException e)
    {
        e.getMessage();
    }
}

I have tried using the ExpectedConditions.presenceOfElementLocated(By) but the staleness exceptions still throws intermittently.

Hope this solution helps.

-2
votes

Just download new chrome extension and use selenium server 3 it will work fine.