In my application when I open page X I expect to see either element A or element B. They are placed in different locations in DOM and can be found using their ids, for example driver.findElement(By.id("idA"))
How can I ask webdriver to find either A or B?
There is method driver.findElements(By)
which will stop waiting when at least one element is found, but this method forces me to use the same locator for A and B.
What is the proper way to reliably find either A or B, so that I don't have to wait implicit timeout?
Element with id I1 or element with id I2
xpath: //E1[@id=I1] | //E2[@id=I2]
css: css=E1#I1,E2#I2
driver.findElement(By.xpath(//E1[@id=I1] | //E2[@id=I2]))
driver.findElement(By.cssSelector(E1#I1,E2#I2))
don't forget about fluentWait mechanism:
public WebElement fluentWait(final By locator){
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(30, TimeUnit.SECONDS)
.pollingEvery(5, TimeUnit.SECONDS)
.ignoring(org.openqa.selenium.NoSuchElementException.class);
WebElement foo = wait.until(
new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(locator);
}
}
);
return foo;
};
you can get more info about fluentWait here
IMHO solution to your issue be like:
fluentWait(By.xpath(//E1[@id=I1] | //E2[@id=I2]));
fluentWait(By.cssSelector(E1#I1,E2#I2))
FYI: here is nice xpath,cssSelector manual
hope this helps you.
Here is my solution, which uses a fluent wait as others have suggested. You would need to replace any calls to getDriver() or references to a driver with a driver object or your own method that fetches it.
/**
* Waits for any one of a given set of WebElements to become displayed and
* enabled.
*
* @param locators
* An array of locators to be sought.
* @param timeout
* Timeout in seconds.
*/
protected void waitForOneOfManyToBePresent(By[] locators, int timeout) {
try {
(new WebDriverWait(getDriver(), timeout))
.until(somethingIsPresent(locators));
} catch (TimeoutException timeoutEx) {
// Do what you wish here to handle the TimeoutException, or remove
// the try/catch and let the TimeoutException fly. I prefer to
// rethrow a more descriptive Exception
}
}
/**
* Condition for presence of at least one of many elements.
*
* @param locators
* An array of By locators to be sought.
* @return Boolean T if at least one element is present, F otherwise.
*/
protected ExpectedCondition<Boolean> somethingIsPresent(By[] locators) {
final By[] finalLocators = locators;
return new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver driver) {
boolean found = false;
for (By locator : finalLocators) {
if (isElementPresent(locator)) {
found = true;
break;
}
}
return new Boolean(found);
}
};
}
/**
* Similar to does element exist, but also verifies that only one such
* element exists and that it is displayed and enabled.
*
* @param by
* By statement locating the element.
* @return T if one and only one element matching the locator is found, and
* if it is displayed and enabled, F otherwise.
*/
protected boolean isElementPresent(By by) {
// Temporarily set the implicit timeout to zero
driver.manage().timeouts().implicitlyWait(0, TimeUnit.MILLISECONDS);
// Check to see if there are any elements in the found list
List<WebElement> elements = driver.findElements(by);
boolean isPresent = (elements.size() == 1)
&& elements.get(0).isDisplayed() && elements.get(0).isEnabled();
// Return to the original implicit timeout value
driver.manage().timeouts()
.implicitlyWait(Properties.TIMEOUT_TEST, TimeUnit.SECONDS);
// Properties.TIMEOUT_TEST is from other personal code, replace with your
// own default timeout setting.
return isPresent;
}
My version also checks to make sure that any found element is singular, visible and enabled, but you can remove that easily if you only want to check for existence or if you don't care if your locators are finding multiple matching elements. Checking for the presence of an element by suppressing the default timeout and then calling findElements() may seem clunky, but it's apparently the recommended way to do so in the Selenium API.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With