Skip to content

Java Selenium stale element reference, using AjaxLocatorFactory

I have a super-class Component, inside this class I store common ways to access elements attached to the component.

In the Component constructor, I initElements with the pagefactory

PageFactory.initElements(new AjaxElementLocatorFactory(root,10), this);

As I understand it, this will automatically keep element reference fresh, as long as the elements are annotated by the @FindBy annotation.

I have a specialized Component called SearchResultRow which extends Component.

In this component, I have all the WebElements related to a row in the search result.

What I want to do is to get the first search result and click on it. This is my code for that.

public void clickOnFirstResult() {
    WebElement firstUser = wait.until(ExpectedConditions.elementToBeClickable(searchResultRoot.findElement(By.cssSelector("tbody > tr:nth-child(1)"))));
    new SearchResultRow(firstUser).clickOn(SearchModel.NAME);
}

Here I wait for the element to become clickable, because it’s a dynamic element that is not covered by the @FindBy annotation.

SearchModel.NAME refers to a WebElement annotated by @FindBy in the SearchResultRow component. Yet this method sometimes 10-15 % gives the error

stale element reference: element is not attached to the page document

Answer

Root cause

Stale element reference refers to web elements that were obtained before a page refresh but attempted to be used (i.e. calling click() method after said refresh.)

Solution

Wait…. Which are the best ways to wait for page reloads? One way is to use Implicit Waits. One very common way to implicitly wait some amount of time:

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

This is how it is done in Selenium Java, but there should be an equivalent method for whichever version of Selenium you are using. The question now is, what does this do? This is the way to tell the web driver to wait the given amount of time BEFORE attempting to obtain a web element. When the implicit wait time is set, it will remain set for the duration of your test session. Therefore, this is typically done at the very beginning before any tests begin to execute. More specifically, you should do it immediately after getting the web driver instance.

That said, this should not be enough. For example, what if the component become stale after initial load of a page? For that, you should use ExpectedConditions class and set an explicit wait for some condition to occur:

WebDriverWait wait = new WebDriverWait(driver, 10); // timeout of 10 seconds and polling the default value of 500 ms, or...
WebDriverWait wait = new WebDriverWait(driver, 10, 100); // timeout of 10 seconds and polling every 100 ms

Then use wait object to set your expected condition and obtain the required web element

WebElement element = wait.until(...);

You will need to pass the appropriate ExpectedConditions to the above method. Common ones to be used to avoid staleness:

WebElement element = wait.until(ExpectedConditions.elementSelectionStateToBe(...));
WebElement element = wait.until(ExpectedConditions.refreshed(...));

Lastly, you will check to see what the state of the element is before interacting with it. For example:

WebElement newElement = wait.until(ExpectedConditions.elementToBeClickable(element));
newElement.click();

In this case, element and newElement are the same object so using just wait.until(ExpectedConditions.elementToBeClickable(element)); should be enough before calling element.click(). By this point, you have established that the element is not stale and it is visible and enabled; and therefore, safe to be clicked (for instance).