I\'m implementing a lot of Selenium tests using Java. Sometimes, my tests fail due to a StaleElementReferenceException
. Could you suggest some approaches to mak
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()
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
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!
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
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;
}
});
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.