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
Advertisement
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).