As Java is not my "best language" I would like to ask for support. I'm trying to validate if element from the list contains a specific value.
I'm having element which locates at least 5+ elements
I'm trying to get that element:
eg. List<WebElement> elementsList = getWebElementList(target, false);
I'm trying to validate if specific text is a part of the taken list. By doing something like:
elementsList.contains(valueToCheck);
it does not work...
also I found a way of using stream() method which looks more/less like this but I'm having troubles with continuation:
elementsList.stream().filter(WebElement::getText.....
Can you please explain me how lists are handled in Java in modern/ nowadays way? In C# I was mostly using LINQ but don't know if Java has similar functionality.
Once you get the element list.You can try something like that. this will return true or false.
elementsList.stream().anyMatch(e -> e.getText().trim().contains("specficelementtext")
You can not apply contains() method directly on a list of WebElement objects.
This method can be applied on String object only.
You also can not extract text directly from list of WebElement objects.
What you can do is:
Get the list of WebElements matching the given locator, iterate over the WebElements in the list extracting their text contents and checking if that text content contains the desired text, as following:
public static boolean waitForTextInElementList(WebDriver driver, By locator, String expectedText, int timeout) {
try {
List<WebElement> list;
for (int i = 0; i < timeout; i++) {
list = driver.findElements(locator);
for (WebElement element : list) {
if (element.getText().contains(expectedText)) {
return true;
}
}
i++;
}
} catch (Exception e) {
ConsoleLogger.error("Could not find the expected text " + expectedText + " on specified element list" + e.getMessage());
}
return false;
}
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
I'm trying to use Selenium PageObjects to model a page that lacks a lot of convenient id or class attributes on the tags, so I'm finding that I need to develop more creative ways to identify elements on a page. Among them is a pattern like the following:
<div id="menuButtons">
<a><img src="logo.png" alt="New"></a>
<a><img src="logo2.png" alt="Upload"></a>
</div>
It would be convenient to be able to create a custom findBy search to be able to identify a link by the alt text of its contained image tag, so I could do something like the following:
#FindByCustom(alt = "New")
public WebElement newButton;
The exact format of the above isn't important, but what's important is that it continue to work with PageFactory.initElements.
The author of this article extended the 'FindBy` annotation to support his needs. You can use this to override the 'FindBy' and make your on implementation.
Edited code sample:
private static class CustomFindByAnnotations extends Annotations {
protected By buildByFromLongFindBy(FindBy findBy) {
How how = findBy.how();
String using = findBy.using();
switch (how) {
case CLASS_NAME:
return By.className(using);
case ID:
return By.id(using);
case ID_OR_NAME:
return new ByIdOrName(using);
case LINK_TEXT:
return By.linkText(using);
case NAME:
return By.name(using);
case PARTIAL_LINK_TEXT:
return By.partialLinkText(using);
case TAG_NAME:
return By.tagName(using);
case XPATH:
return By.xpath(using);
case ALT:
return By.cssSelector("[alt='" + using " + ']");
default:
throw new IllegalArgumentException("Cannot determine how to locate element " + field);
}
}
}
Please note I didn't try it myself. Hope it helps.
If you simply want the <a> tag you can use xpath to find the element and go one level up using /..
driver.findElement(By.xpath(".//img[alt='New']/.."));
Or you can put the buttons in list and access them by index
List<WebElement> buttons = driver.findElements(By.id("menuButtons")); //note the spelling of findElements
// butttons.get(0) is the first <a> tag
I am porting over an existing project and I have a super class called DetailPage that is the only thing I can change/edit. It's a selenium testing project and in previous versions I had hundreds of individual pages that extended DetailPage and had the following lines:
#FindBy(id="someID")
private WebElement someIDBox;
to search a page for a given id and assign that element to a variable. I now have a similar website that is nonW3C so instead of finding by id I now need to find by name ie:
#FindBy(name="someID")
private WebElement someIDBox;
is what I now want.
So my question is how can I (if possible) make a super class function in DetailPage that would notice I say id and override with name?
I DO NOT want a function that just replaces the text id with name in the individual pages as I need to preserve those.
The author of this article extended the 'FindBy` annotation to support his needs. You can use this to override the 'FindBy' and make your on implementation.
Edited code sample:
private static class CustomFindByAnnotations extends Annotations {
protected By buildByFromLongFindBy(FindBy findBy) {
How how = findBy.how();
String using = findBy.using();
switch (how) {
case CLASS_NAME:
return By.className(using);
case ID:
return By.id(using); // By.name(using); in your case
case ID_OR_NAME:
return new ByIdOrName(using);
case LINK_TEXT:
return By.linkText(using);
case NAME:
return By.name(using);
case PARTIAL_LINK_TEXT:
return By.partialLinkText(using);
case TAG_NAME:
return By.tagName(using);
case XPATH:
return By.xpath(using);
default:
throw new IllegalArgumentException("Cannot determine how to locate element " + field);
}
}
}
Please note I didn't try it myself. Hope it helps.
This does not meet the criteria that I asked but thanks to #guy I was introduced to ByIdOrName which helps with my problem. So I made a little script that went through all my files in the workspace and replaced
#FindBy(id="someID")
private WebElement someIDBox;
with
#FindBy(how = How.ID_OR_NAME, using="someID")
private WebElement someIDBox;
while this made it so I had to alter all test pages(which is what I wanted to avoid) it does not alter how other tests for other websites work which is key!
How can I tell Selenium webdriver to wait on a specific element to have a specific attribute set in css?
I want to wait for:
element.getCssValue("display").equalsIgnoreCase("block")
Usually one waits for elements to be present like this:
webdriver.wait().until(ExpectedConditions.presenceOfElementLocated(By.id("some_input")));
How can I wait for the specific css value of display attribute?
I think this would work for you.
webdriver.wait().until(ExpectedConditions.presenceOfElementLocated(By.xpath("//*[#id='some_input'][contains(#style, 'display: block')]")));
Small modification of the above answer helpped for me. I had to use two (s before I start By.XPATH, not 1:
WebDriverWait(browser,1000).until(EC.presence_of_element_located((By.XPATH,xpathstring)))
In C# with extension method:
public static WebDriverWait wait = new WebDriverWait(SeleniumInfo.Driver, TimeSpan.FromSeconds(20));
public static void WaitUntilAttributeValueEquals(this IWebElement webElement, String attributeName, String attributeValue)
{
wait.Until<IWebElement>((d) =>
{
if (webElement.GetAttribute(attributeName) == attributeValue)
{
return webElement;
}
return null;
});
}
Usage:
IWebElement x = SeleniumInfo.Driver.FindElement(By.Xpath("//..."));
x.WaitUntilAttributeValueEquals("disabled", null); //Verifies that the attribute "disabled" does not exist
x.WaitUntilAttributeValueEquals("style", "display: none;");