Найти любой из двух элементов в webdriver - программирование
Подтвердить что ты не робот

Найти любой из двух элементов в webdriver

В моем приложении, когда я открываю страницу X, я ожидаю увидеть либо элемент A, либо элемент B. Они размещаются в разных местах DOM и могут быть найдены с использованием их идентификаторов, например driver.findElement(By.id("idA"))

Как я могу попросить webdriver найти A или B?

Существует метод driver.findElements(By), который перестанет ждать, когда будет найден хотя бы один элемент, но этот метод заставляет меня использовать тот же локатор для A и B.

Каков правильный способ надежного поиска A или B, так что мне не нужно ждать неявного тайм-аута?

4b9b3361

Ответ 1

Элемент с идентификатором I1 или элементом с идентификатором 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 здесь

Решение IMHO для вашей проблемы будет выглядеть следующим образом:

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

FYI: здесь является хорошим xpath, cssSelector manual

надеюсь, что это поможет вам.

Ответ 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() может показаться неуклюжим, но, по-видимому, это рекомендуется в API Selenium.

Ответ 3

Я написал 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;
        }
    };
}

Ответ 4

Я столкнулся с этой проблемой, поэтому я сделал для нее метод. Обратите внимание, что метод находится внутри класса, который содержит webdriver как "self._driver". Код находится в Python.

Примером вызова метода будет:

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

Ответ 5

@pavel_kazlou, хорошо о вашем вопросе о FluentWait: в принципе есть два типа ожидания: Явное ожидание

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

Неявное ожидание

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

Разница

  • Очевидное. Неявное время ожидания применяется ко всем элементам вашего script, но Явный только для определенного элемента
  • В Explicit вы можете настроить, как часто (вместо 500 миллисекунда), вы хотите проверить состояние.
  • В Explicit вы также можете настроить игнорировать другие исключения, кроме "NoSuchElement" до тайм-аута.. Используйте FluentWait, который работает аналогично WebDriverWait (который фактически расширяет FluentWait), но дает вам немного большую гибкость.

Вот пример использования WebDriverWait (используйте другой конструктор WebDriverWait, чтобы указать интервал опроса элемента (измеренный в миллисекундах).):

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

Используя FluentWait, который работает аналогично WebDriverWait (который фактически расширяет FluentWait), но дает вам немного больше гибкости: в частности, возможность выбора исключения WebDriver для игнорирования. пример использования:

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

В завершение моей заметки: fluentWait - это явный тип ожидания и предоставляет вам возможность явно указать, какой тип исключения WebDriver игнорировать, где, поскольку любое неявное ожидание включает фиксированное количество времени для ожидания любого веб-элемента. Подход IMHO fluentWait является более надежным с этой точки зрения.

Ответ 6

Вот решение 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");
}