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?
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;
}
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.
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
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!
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.
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
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.
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'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))));
}
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. If this answer helped you, please go upvote jspcal's answer.
// 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.
}
}
}
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;
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);
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
}
}
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()
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.
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;
}
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()")
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
Related
I am getting element not found exception while trying to locate the element in a try loop. Below is my code:
private boolean isPresent(WebDriver driver,String findElement)
{
driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);
try {
driver.findElement(By.xpath(findElement));
return true;
}
catch (NoSuchElementException e) {
return false;
}
finally{
driver.manage().timeouts().implicitlyWait(40,TimeUnit.SECONDS);
}
}
Instead of using find element and timeouts use some waits or until for the element to be present and then do the operation.
eg. This will wait until the element is located, then do what you want to do with your myDynamicELement
WebElement myDynamicElement = (new WebDriverWait(driver, 10))
.until(ExpectedConditions.presenceOfElementLocated(By.id("myElement")));
It looks like you are trying to validate whether your element is present or not. For that use a logic something like this.
A) Inside try
1) Wait for the element to be present
2) Then Use if then else to check if element present and return true or false
B) Inside Catch handle the error.
The better way to do this is to avoid throwing exceptions in the first place.
private boolean isPresent(WebDriver driver, By locator)
{
return !driver.findElements(locator).isEmpty();
}
Instead of passing the locator as string and requiring XPath, use a By locator. Now you can pass the method any locator type... Id, CSS selector, etc.
I have a responsive design, so many items I need to interact with have 2 WebElements. One for Desktop and one for Mobile, and I am trying to use PageFactory. Here's what I have now to identify and interact with the element.
//this returns 2 webelements, one for desktop and one for mobile
#FindBy(xpath = "//selector-dropdown/p")
private WebElement dropdown;
public void ClickDropdown() throws InterruptedException {
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(ExpectedConditions.visibilityOf(dropdown)).click();
}
I was under the impression that ExpectedConditions.visibilityOf(WebElement) would find the first element visible. Right now when I open the app on Desktop, it finds the element (Desktop is first in the DOM). But on Mobile, it times out waiting for visibility, because it is stuck waiting for the first one to become visible.
My alternative is using #FindBy to declare every element twice, and making an if statement to decide which path to take. Is this extra work the only way to make it work?
Your assumption "... ExpectedConditions.visibilityOf(WebElement) would find the first element visible." is wrong! You will need to declare your WebElement as a List, find all of them, and pick the one that is visible. Here is a sample that I used successfully in the past:
#FindBy(xpath = "//input[#ng-model='loginData.username']")
private List<WebElement> txtUsername;
public String getUsername() {
driverWait.until(CustomExpectedConditions.presenceOfAllElements(txtUsername));
for (WebElement oneUsername : txtUsername) {
if (oneUsername.isDisplayed())
return oneUsername.getAttribute("value");
}
return null;
}
public void setUsername(String username) {
driverWait.until(CustomExpectedConditions.presenceOfAllElements(txtUsername));
for (WebElement oneUsername : txtUsername) {
if (oneUsername.isDisplayed()) {
oneUsername.clear();
oneUsername.sendKeys(username);
break;
}
}
}
Based on discussion elsewhere, here is the CustomExpectedConditions:
public class CustomExpectedConditions {
/**
* Based on {#link ExpectedConditions#presenceOfAllElementsLocatedBy(org.openqa.selenium.By)}.
*
* #param elements
* #return
*/
public static ExpectedCondition<List<WebElement>> presenceOfAllElements(
final List<WebElement> elements) {
return new ExpectedCondition<List<WebElement>>() {
#Override
public List<WebElement> apply(WebDriver driver) {
// List<WebElement> elements = findElements(locator, driver);
return elements.size() > 0 ? elements : null;
}
};
}
}
Combining #Andersons and #SiKing answers into a solution that can be used everywhere, you could make a method available to all of your PageObjects in a base class, which you might already have:
protected WebElement getVisibleElement(List<WebElement> elements)
{
//Need a guard clause here to ensure there are exactly two elements,
//Or make the wait check for all elements more safely. Either way,
//consider changing the method name to be clear about expectations
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(
ExpectedConditions.or(
//This should be done more safely, unless already guarded to expect 2 elements
ExpectedConditions.visibilityOf(elements.get(0))),
ExpectedConditions.visibilityOf(elements.get(1)))
)
);
for (WebElement element : elements) {
if (element.isDisplayed())
{
return element;
}
}
//Throw element not visible exception or something
}
Then in your PageObject:
#FindBy(xpath = "//selector-dropdown/p")
private List<WebElement> dropdown;
public void ClickDropdown() throws InterruptedException {
getVisibleElement(dropdown)).click();
}
As I know in Java you can combine multiple conditions with ExpectedConditions.and() as below:
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(
ExpectedConditions.and(
ExpectedConditions.visibilityOf(driver.findElement(By.xpath("(//selector-dropdown/p)[1]"))),
ExpectedConditions.visibilityOf(driver.findElement(By.xpath("(//selector-dropdown/p)[2]" )))
)
);
or just try
ExpectedConditions.visibilityOfAllElementsLocatedBy(By.xpath("//selector-dropdown/p"));
to wait until all elements matched by specified selector became visible
Also try to look for atribute in DOM, which makes the element visible. That's the fastes and should be prefered aproach.
For example your element in DOM might be or sit under element with 'display: none;' or 'position: fixed'
Then your x-path might for example look like:
//div[not(contains(#style,'display: none;')) ...
or
//div[contains(#style,'position: fixed') ...
Or there might be something else.
DOM by it self always tells you which element is visible and which is not.
This is the method I came up with, maybe someone can use the full solution. This will A. wait for the first element in a list to be visible, then B. assign the visible one as a single element. The wait is short because it shouldn't take more than a second to figure out which element is visible.
protected WebElement getVisibleElement(List<WebElement> elements) {
WebDriverWait wait = new WebDriverWait(driver, 1);
WebElement rE = null;
int elementsSize = elements.size();
for (int i = 0; i < elementsSize; i++) {
System.out.println("test" + i);
try {
wait.until(ExpectedConditions.or(ExpectedConditions.visibilityOf(elements.get(i))));
break;
} catch (Exception e) {
//handle exception however you like
}
}
for (WebElement element : elements) {
if (element.isDisplayed()) {
System.out.println("found and assigning to rE");
rE = element;
break;
}
}
return rE;
}
I have this idea for failing over to a JavascriptExecutor if Selenium2 fails to retrieve a WebElement object after polling for a limited time. As you can see, the method has the limitation of needing the "failover" Javascript snippet to be pre-defined when calling getElementByLocator. I could not think of any way to dynamically do this. If anyone can help me improve on this, I will award the answer to the best suggestion, however small it is.
// failover example1: "document.getElementById('gbqfb')"
// failover example2: "document.querySelector("div#gbqfb")"
public static WebElement getElementByLocator(final By locator, String failover) {
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(30, TimeUnit.SECONDS)
.pollingEvery(5, TimeUnit.SECONDS);
.ignoring(NoSuchElementException.class,StaleElementReferenceException.class);
WebElement we = wait.until( ExpectedConditions
.presenceOfElementLocated( locator ) );
if ( we.isNull() ) {
JavascriptExecutor js = (JavascriptExecutor) driver;
if ( !failover.isEmpty() ) {
we = (WebElement)js.executeScript( failover );
if ( we.isNull() ) LOG.info("Still couldn't get element.");
} else {
LOG.info("No failover String available. Cannot try with " +
"a JavascriptExecutor.");
}
}
return we;
}
Actually answered a similar question here
I wouldn't recommend delegating anything to javascript like that.. Use what Selenium gives you.. it's very sufficient.
Something that I put in every single framework i've ever built, which is VERY efficient.. Here is an exerpt from the framework found here.
I implement sort of a pseudo-wait type method before i ever perform any actions on objects. Try it yourself. It's very efficient.
These are methods from the AutomationTest class
/**
* Checks if the element is present or not.<br>
* #param by
* #return <i>this method is not meant to be used fluently.</i><br><br.
* Returns <code>true</code> if the element is present. and <code>false</code> if it's not.
*/
public boolean isPresent(By by) {
if (driver.findElements(by).size() > 0) return true;
return false;
}
/**
* Private method that acts as an arbiter of implicit timeouts of sorts.. sort of like a Wait For Ajax method.
*/
private WebElement waitForElement(By by) {
int attempts = 0;
int size = driver.findElements(by).size();
while (size == 0) {
size = driver.findElements(by).size();
if (attempts == MAX_ATTEMPTS) fail(String.format("Could not find %s after %d seconds",
by.toString(),
MAX_ATTEMPTS));
attempts++;
try {
Thread.sleep(1000); // sleep for 1 second.
} catch (Exception x) {
fail("Failed due to an exception during Thread.sleep!");
x.printStackTrace();
}
}
if (size > 0) System.err.println("WARN: There are more than 1 " + by.toString() + " 's!");
return driver.findElement(by);
}
What I do, is anytime i perform something, like
getText(By.cssSelector("input#someId"))
if it doesn't find it the first time, it'll wait 1 second. if it finds it then, continues. does that subsequently 5 times, so a total of 5 seconds wait.. which is perfectly ok because if you DONT find the element you need, then your test effectively should fail at that point.
Also, from experience, i can tell you that using driver.findElements() is more effective than those WebDriverWait's.
That doesn't mean i don't use them.. just not for that. Unfortunately, I had not added this functionality to the getting started with selenium framework, so i'll just tell you when i used Webdriverwait's.
So my test would look like -
#Config(url="http://systemunder.test", browser=CHROME)
public class MyClass extends AutomationTest {
#Test
public void testSomething() {
setText(By.id("blah")) // if <* id="blah" /> doesn't exist, waits 1+ seconds for it to appear before interacting.
.click(By.id("Blah2")) // ^ same thing here.
.waitForPresent(By.cssSelector("ajaxy")); // this method right here would circumvent the hard waits, with webdriverwait's.
}
}
i don't remember exactly why it didn't work for me before, but using webdriverwaits in something like that, is flawless.
I have a question regarding "Element is no longer attached to the DOM".
I tried different solutions but they are working intermittent. Please suggest a solution that could be permanent.
WebElement getStaleElemById(String id, WebDriver driver) {
try {
return driver.findElement(By.id(id));
} catch (StaleElementReferenceException e) {
System.out.println("Attempting to recover from StaleElementReferenceException ...");
return getStaleElemById(id, driver);
}
}
WebElement getStaleElemByCss(String css, WebDriver driver) {
try {
return driver.findElement(By.cssSelector(css));
} catch (StaleElementReferenceException e) {
System.out.println("Attempting to recover from StaleElementReferenceException ...");
return getStaleElemByCss(css, driver);
} catch (NoSuchElementException ele) {
System.out.println("Attempting to recover from NoSuchElementException ...");
return getStaleElemByCss(css, driver);
}
}
Thanks,
Anu
The problem
The problem you are probably facing is that the method returns the right (and valid!) element, but when you're trying to access it a second later, it is stale and throws.
This usually arises when:
You click something that loads a new page asynchronously or at least changes it.
You immediatelly (before the page load could finish) search for an element ... and you find it!
The page finally unloads and the new one loads up.
You try to access your previously found element, but now it's stale, even though the new page contains it, too.
The solutions
There are four ways to solve it I know about:
Use proper waits
Use proper waits after every anticipated page-load when facing asynchronous pages. Insert an explicit wait after the initial click and wait for the new page / new content to load. Only after that you can try to search for the element you want. This should be the first thing you'll do. It will increase the robustness of your tests greatly.
The way you did it
I have been using a variant of your method for two years now (together with the technique above in solution 1) and it absolutely works most of the time and fails only on strange WebDriver bugs. Try to access the found element right after it is found (before returning from the method) via a .isDisplayed() method or something. If it throws, you already know how to search again. If it passes, you have one more (false) assurance.
Use a WebElement that re-finds itself when stale
Write a WebElement decorator that remembers how it was found and re-find it when it's accessed and throws. This obviously forces you to use custom findElement() methods that would return instances of your decorator (or, better yet, a decorated WebDriver that would return your instances from usual findElement() and findElemens() methods). Do it like this:
public class NeverStaleWebElement implements WebElement {
private WebElement element;
private final WebDriver driver;
private final By foundBy;
public NeverStaleWebElement(WebElement element, WebDriver driver, By foundBy) {
this.element = element;
this.driver = driver;
this.foundBy = foundBy;
}
#Override
public void click() {
try {
element.click();
} catch (StaleElementReferenceException e) {
// log exception
// assumes implicit wait, use custom findElement() methods for custom behaviour
element = driver.findElement(foundBy);
// recursion, consider a conditioned loop instead
click();
}
}
// ... similar for other methods, too
}
Note that while I think that the foundBy info should be accessible from the generic WebElements to make this easier, Selenium developers consider it a mistake to try something like this and have chosen not to make this information public. It's arguably a bad practice to re-find on stale elements, because you're re-finding elements implicitly without any mechanism for checking whether it's justified. The re-finding mechanism could potentially find a completely different element and not the same one again. Also, it fails horribly with findElements() when there are many found elements (you either need to disallow re-finding on elements found by findElements(), or remember the how-manyeth your element was from the returned List).
I think it would be useful sometimes, but it's true that nobody would ever use options 1 and 2 which are obviously much better solutions for the robustness of your tests. Use them and only after you're sure you need this, go for it.
Use a task queue (that can rerun past tasks)
Implement your whole workflow in a new way!
Make a central queue of jobs to run. Make this queue remember past jobs.
Implement every needed task ("find an element and click it", "find an element and send keys to it" etc.) via the Command pattern way. When called, add the task to the central queue which will then (either synchronously or asynchronously, doesn't matter) run it.
Annotate every task with #LoadsNewPage, #Reversible etc. as needed.
Most of your tasks will handle their exceptions by themselves, they should be stand-alone.
When the queue would encounter a stale element exception, it would take the last task from the task history and re-run it to try again.
This would obviously take a lot of effort and if not thought through very well, could backfire soon. I used a (lot more complex and powerful) variant of this for resuming failed tests after I manually fixed the page they were on. Under some conditions (for example, on a StaleElementException), a fail would not end the test right away, but would wait (before finally time-outing after 15 seconds), popping up an informative window and giving the user an option to manually refresh the page / click the right button / fix the form / whatever. It would then re-run the failed task or even give a possibility to go some steps back in history (e.g. to the last #LoadsNewPage job).
Final nitpicks
All that said, your original solution could use some polishing. You could combine the two methods into one, more general (or at least make them delegate to this one to reduce code repetition):
WebElement getStaleElem(By by, WebDriver driver) {
try {
return driver.findElement(by);
} catch (StaleElementReferenceException e) {
System.out.println("Attempting to recover from StaleElementReferenceException ...");
return getStaleElem(by, driver);
} catch (NoSuchElementException ele) {
System.out.println("Attempting to recover from NoSuchElementException ...");
return getStaleElem(by, driver);
}
}
With Java 7, even a single multicatch block would be sufficient:
WebElement getStaleElem(By by, WebDriver driver) {
try {
return driver.findElement(by);
} catch (StaleElementReferenceException | NoSuchElementException e) {
System.out.println("Attempting to recover from " + e.getClass().getSimpleName() + "...");
return getStaleElem(by, driver);
}
}
This way, you can greatly reduce the amount of code you need to maintain.
I solve this by 1. keeping the stale element and poll it until it throws an exception, and then 2. wait until the element is visible again.
boolean isStillOnOldPage = true;
while (isStillOnOldPage) {
try {
theElement.getAttribute("whatever");
} catch (StaleElementReferenceException e) {
isStillOnOldPage = false;
}
}
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("theElementId")));
If you are trying to Click on link, that taking you to new page. After that navigating back and clicking on other links. They below code may help you.
public int getNumberOfElementsFound(By by) {
return driver.findElements(by).size();
}
public WebElement getElementWithIndex(By by, int pos) {
return driver.findElements(by).get(pos);
}
/**click on each link */
public void getLinks()throws Exception{
try {
List<WebElement> componentList = driver.findElements(By.tagName("a"));
System.out.println(componentList.size());
for (WebElement component : componentList)
{
//click1();
System.out.println(component.getAttribute("href"));
}
int numberOfElementsFound = getNumberOfElementsFound(By.tagName("a"));
for (int pos = 0; pos < numberOfElementsFound; pos++) {
if (getElementWithIndex(By.tagName("a"), pos).isDisplayed()){
getElementWithIndex(By.tagName("a"), pos).click();
Thread.sleep(200);
driver.navigate().back();
Thread.sleep(200);
}
}
}catch (Exception e){
System.out.println("error in getLinks "+e);
}
}
Solutions to resolve them:
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
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)
Moving your actions into JavaScript injection
self.driver.execute_script("$(\"li:contains('Narendra')\").click()");
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
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.xpath(“somexpath”));
break;
} catch(StaleElementReferenceException e){
e.toString();
System.out.println(“Stale element error, trying :: ” + e.getMessage());
}
elementName.sendKeys(“xyz”);
For Fitnesse you can use:
|start |Smart Web Driver| selenium.properties|
#Fixture(name = "Smart Web Driver")
public class SmartWebDriver extends SlimWebDriver {
private final static Logger LOG = LoggerFactory.getLogger(SmartWebDriver.class);
/**
* Constructs a new SmartWebDriver.
*/
#Start(name = "Start Smart Web Driver", arguments = {"configuration"}, example = "|start |Smart Web Driver| selenium.properties|")
public SmartWebDriver(String configuration) {
super(configuration);
}
/**
* Waits for an element to become invisible (meaning visible and width and height != 0).
*
* #param locator the locator to use to find the element.
*/
#Command(name = "smartWaitForNotVisible", arguments = {"locator"}, example = "|smartWaitForNotVisible; |//path/to/input (of css=, id=, name=, classname=, link=, partiallink=)|")
public boolean smartWaitForNotVisible(String locator) {
try {
waitForNotVisible(locator);
} catch (StaleElementReferenceException sere) {
LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a StaleElementReferenceException occurred, trying to continue...", locator);
} catch (NoSuchElementException ele) {
LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a NoSuchElementException occurred, trying to continue...", locator);
} catch (AssertionError ae) {
if (ae.getMessage().contains("No element found")) {
LOG.info("Element with locator '%s' did not become invisible (visible but also width and height != 0), a AssertionError occurred, trying to continue...", locator);
} else {
throw ae;
}
}
return true;
}
}
https://www.swtestacademy.com/selenium-wait-javascript-angular-ajax/ here is a good article about dynamic waiter strategies.
Your problem is not waiting properly all the ajax, jquery or angular calls.
Then you end up with StaleElementException.
If your approach is to use Try-Catch mechanism, I guess it has a flaw. You shouldn't rely on that structure as you'll never know it's gonna work in the catch clause.
Selenium gives you the opportunity to make javascript calls.
You can execute
"return jQuery.active==0"
return
angular.element(document).injector().get('$http').pendingRequests.length
=== 0"
"return document.readyState"
"return angular.element(document).injector() === undefined"
commands just to check the existence and states of those calls.
You can do that before any findBy operation so you always work with the latest page
I am new to Selenium webdriver, maybe this question is obvious. I am after situation like this:
If the element exists, click it and go back to index page:
driver.findElement(By.id("...."])).click();
if doesn't exit, skip it and go back to index page. The test still goes on without any exception thrown.
I know one solution to this:
driver.findElements( By.id("...") ).size() != 0
so i tried:
if(driver.findElements(By.id("....")).size() > 0)
{
driver.findElement(By.id("....")).click();
driver.findElement(By.cssSelector("...")).click();
}
else
{
driver.findElement(By.cssSelector("....")).click();
}
This turned out really ugly though because if I have 10 elements to verify, this IF condition needs to be written 10 times.
Any workaround to make it neat?
There are ways to find elements without throwing exceptions by using try-catch conditions inside of loops. For example, this method I wrote (which can be simplified depending on what you use if for) will return a WebElement and it makes sure that it's clickable before returning it to you:
public static WebElement getElementByLocator( By locator ) {
driver.manage().timeouts().implicitlyWait( 5, TimeUnit.SECONDS );
WebElement we = null;
boolean unfound = true;
int tries = 0;
while ( unfound && tries < 10 ) {
tries += 1;
try {
we = driver.findElement( locator );
unfound = false; // FOUND IT
} catch ( StaleElementReferenceException ser ) {
unfound = true;
} catch ( NoSuchElementException nse ) {
unfound = true;
} catch ( Exception e ) {
staticlogger.info( e.getMessage() );
}
}
driver.manage().timeouts().implicitlyWait( DEFAULT_IMPLICIT_WAIT,
TimeUnit.SECONDS );
return we;
}
Solution could be many but that may hinder your architecture.
So easiest solution could be as follows:
Just create a method like optionalClick() in some utility class or somewhere with the arguments as:
locator_keyword: {values : id or cssSelector or xpath etc}
locator : {values : "q" }
Steps in method:
Get element based on the locator_keyword and locator
Check if element is there and click it
Otherwise don't do anything
This method can be used as a generic kind of thing for any type of objects.
Thanks for the pointer on using findelements. I think you'll also need wait logic though if you dont want it to return too early.
here is my solution in c#.
It basically reimplements the wait logic in the selenium library - unfortunately the method that does the waiting isnt exposed (annoyingly it also rethrows exceptions the wrong way and loses the stack traces!).
You'd probably have to modify it a little for java - not sure what the selenium API is there when you cant just pass around functions.
Wait reimplementation:
private IWebElement WaitAndSeeIf(Func<IWebElement> canGet)
{
var end = DateTime.Now.AddSeconds(1);
IWebElement element;
while (true)
{
element = canGet();
if (element != null)
break;
var time = DateTime.Now;
if (time > end)
break;
Thread.Sleep(200);
}
return element;
}
calling code:
var dashboardButton = WaitAndSeeIf(() =>
{
var elements = Driver.FindElements(By.XPath("//button[contains(.//*,'Dashboard')]"));
return elements.Any() ? elements.Single() : null;
});
So far I've found this useful in a couple of places (checking for the presence of dialogs in extjs etc) but have yet had the need to muck around and make it configurable. I supppose the nicest thing to do would be to have it take the implicit wait time as mentioned in the other answer.