在webdriver中找到两个元素中的任何一个

时间:2012-10-22 19:38:11

标签: selenium webdriver selenium-webdriver

在我的应用程序中,当我打开页面X时,我希望看到元素A或元素B.它们被放置在DOM中的不同位置,可以使用它们的ID找到,例如driver.findElement(By.id("idA"))

如何让webdriver找到A或B?

方法driver.findElements(By)会在找到至少一个元素时停止等待,但这种方法迫使我对A和B使用相同的定位器。

可靠地找到A或B的正确方法是什么,这样我就不必等待隐式超时?

7 个答案:

答案 0 :(得分:19)

ID为I1的元素或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))

不要忘记fluentWait机制:

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;
};

您可以获得有关fluentWait here

的更多信息

IMHO解决您的问题的方法如下:

fluentWait(By.xpath(//E1[@id=I1] | //E2[@id=I2]));
fluentWait(By.cssSelector(E1#I1,E2#I2))

仅供参考:here是很好的xpath,cssSelector手册

希望这会对你有所帮助。

答案 1 :(得分:2)

这是我的解决方案,使用其他人建议的流畅等待。您需要使用驱动程序对象或您自己的获取驱动程序对象的方法替换对getDriver()的任何调用或对驱动程序的引用。

/**
 * 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;
}

我的版本还检查以确保所有找到的元素都是单数,可见和启用的,但如果您只想检查是否存在,或者您不关心定位器是否找到多个匹配元素,则可以轻松删除它。通过抑制默认超时然后调用findElements()来检查是否存在元素可能看起来很笨拙,但显然这是在Selenium API中这样做的推荐方法。

答案 2 :(得分:2)

我写了一个ExpectedCondition,可以随意使用它。

public static ExpectedCondition<By> titleIs(final By[] selectors) {
    return new ExpectedCondition<By>() {
        public By apply(WebDriver driver) {
            WebElement el=null;
            for (By selector:selectors) {
                try {
                    el = driver.findElement(selector);
                } catch (NoSuchElementException ex) {
                    // ignore as we are waiting for that to stop
                }
                if (el!=null) return selector; 
            }
            return null;
        }
    };
}

答案 3 :(得分:1)

我也遇到过这个问题所以我为它制定了一个方法。 请注意,Method位于一个包含webdriver为“self._driver”的类中。代码是Python。

调用Method的一个例子是:

self.MES(3,('name','name_of_element1'),('id','id_of_element2'))

from selenium.common.exceptions import NoSuchElementException
import time

def MES(self, wait_time, element1, element2):
    '''
    A function to check a website for multiple elements at the same time
    MultiElementSearch. Returns the element if found, or False if neither
    are found.
    It will also throw a ValueError is the element locator type is not
    valid.

    MES(int, (str, str), (str, str)) -> Element or bool
    '''
    time1 = time.time()
    while time.time() < (time1 + wait_time):
        try:
            if element1[0] == 'id':
                selection1 = self._driver.find_element_by_id(element1[1])
            elif element1[0] == 'name':
                selection1 = self._driver.find_element_by_name(element1[1])
            elif element1[0] == 'xpath':
                selection1 = self._driver.find_element_by_xpath(element1[1])
            elif element1[0] == 'link_text':
                selection1 = self._driver.find_element_by_link_text(element1[1])
            elif element1[0] == 'partial_link_text':
                selection1 = self._driver.find_element_by_partial_link_text(
                    element1[1])
            elif element1[0] == 'tag_name':
                selection1 = self._driver.find_element_by_tag_name(element1[1])
            elif element1[0] == 'class_name':
                selection1 = self._driver.find_element_by_class_name(
                    element1[1])
            elif element1[0] == 'css_selector':
                selection1 = self._driver.find_element_by_css_selector(
                    element1[1])
            else:
                raise ValueError(
                    'The first element locator type is not vaild')
            return selection1

        except NoSuchElementException:
            pass

        try:
            if element2[0] == 'id':
                selection2 = self._driver.find_element_by_id(element2[1])
            elif element2[0] == 'name':
                selection2 = self._driver.find_element_by_name(element2[1])
            elif element2[0] == 'xpath':
                selection2 = self._driver.find_element_by_xpath(element2[1])
            elif element2[0] == 'link_text':
                selection2 = self._driver.find_element_by_link_text(element2[1])
            elif element2[0] == 'partial_link_text':
                selection2 = self._driver.find_element_by_partial_link_text(
                    element2[1])
            elif element2[0] == 'tag_name':
                selection2 = self._driver.find_element_by_tag_name(element2[1])
            elif element2[0] == 'class_name':
                selection2 = self._driver.find_element_by_class_name(
                    element2[1])
            elif element2[0] == 'css_selector':
                selection2 = self._driver.find_element_by_css_selector(
                    element2[1])
            else:
                raise ValueError(
                    'The second element locator type is not vaild')
            return selection2
        except NoSuchElementException:
            pass
    return False

答案 4 :(得分:0)

@pavel_kazlou,关于你在FluentWait上的问题:基本上有两种类型的等待: 明确等待

WebDriverWait.until(condition-that-finds-the-element)

隐含等待

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);

差异是

  • 明显 - 隐含的等待时间适用于您的所有元素 脚本,但仅针对特定元素明确
  • 在显式中,您可以配置频率(而不是500 毫秒)你想检查条件。
  • 在Explicit中,您还可以配置忽略除以外的其他异常 “NoSuchElement”直到超时.. 使用FluentWait,其工作方式与WebDriverWait类似(实际上扩展了FluentWait),但为您提供了更多的灵活性。

这里是WebDriverWait用法示例(使用不同的WebDriverWait构造函数来指定元素轮询间隔(以毫秒为单位)。):

new WebDriverWait(webDriver(), 10, 50).until(ExpectedConditions.elementToBeClickable(By.xpath(menuItemXpath)));

使用与WebDriverWait类似的FluentWait(实际上扩展了FluentWait),但为您提供了更多的灵活性:特别是能够选择要忽略的WebDriver异常。 用法示例:

new FluentWait(webDriver())
.withTimeout(timeout, TimeUnit.SECONDS)
.pollingEvery(50, TimeUnit.MILLISECONDS)
.ignoring(NoSuchElementException.class)
.until(ExpectedConditions.elementToBeClickable(By.xpath(menuItemXpath)));

结束我的注释:fluentWait是显式类型的等待,并且您可以明确选择要忽略的WebDriver异常类型,其中任何隐式等待包括等待任何webElement的固定时间量。从这个角度来看,恕我直言流利的方法更加强大。

答案 5 :(得分:0)

这是一个Java 8解决方案。

包装器对象:

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Wait;
import org.openqa.selenium.support.ui.WebDriverWait;

public class SelectorWebElement
{
    private WebElement webElement;
    private By by;

    private SelectorWebElement(WebElement webElement, By by)
    {
        this.webElement = webElement;
        this.by = by;
    }

    public By getBy()
    {
        return by;
    }

    public WebElement getWebElement()
    {
        return webElement;
    }

    private static ExpectedCondition<SelectorWebElement> findFirstElement(By... selectors)
    {
        return driver ->
        {
            for (By selector : selectors)
            {
                try
                {
                    assert driver != null;
                    WebElement webElement = driver.findElement(selector);
                    if (webElement.isDisplayed())
                    {
                        return new SelectorWebElement(webElement, selector);
                    }
                } catch (Exception ignored)
                {

                }
            }

            return null;
        };
    }

    public static SelectorWebElement waitForFirstElement(WebDriver driver,
                                                         long timeout,
                                                         By... selectors)
    {
        Wait wait = new WebDriverWait(driver, timeout);
        return (SelectorWebElement) wait.until(findFirstElement(selectors));
    }
}

示例代码:

By badPasswordSelector = By.cssSelector("...");
By myAccountPage = By.cssSelector("...");
SelectorWebElement selectorWebElement = SelectorWebElement.waitForFirstElement(driver, 5, badPasswordSelector, myAccountPage);

By matchedSelector = selectorWebElement.getBy();

if (matchedSelector.equals(badPasswordSelector))
{
    System.out.println("Bad password");
} else if (matchedSelector.equals(myAccountPage))
{
    System.out.println("Successfully logged in");
}

答案 6 :(得分:0)

任何人都希望在PYTHON中做到这一点

这是一个非常简单的方法,对我有用,首先我使用implicitly_wait方法,因此驱动程序会自动等待给定的时间,直到找到元素为止:

driver.implicitly_wait(30) #driver constantly tests for 30 seconds before complaining

现在,如果我想找到两个不同元素中的任何一个(或者以不同的方式使用相同的元素),我只需执行以下操作:

#We see if either is present, we only need one for the if statement to be true
if driver.find_element_by_class_name('dir') or driver.find_element_by_class_name('dirSel'):

    #Set wait time to 0 so we can try both fast (since we know one was found but not which)
    driver.implicitly_wait(0) 

    try:
        ele = driver.find_element_by_class_name('dir')
    except: 
        ele = driver.find_element_by_class_name('dirSel')

    driver.implicitly_wait(30) #reset the driver's wait time. 

这很容易变成一个函数,并且可以缩放以搜索两个以上的元素,只是想避免这种情况,因为原始帖子在Java中寻求帮助。然而,selenium命令在各种语言中非常统一,所以我帮助这可以帮助那些不在python中工作的人:)