Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

find any of two elements in webdriver

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?

like image 744
pavel_kazlou Avatar asked Oct 22 '12 19:10

pavel_kazlou


2 Answers

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.

like image 174
eugene.polschikov Avatar answered Oct 30 '22 09:10

eugene.polschikov


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.

like image 36
James Martineau Avatar answered Oct 30 '22 11:10

James Martineau