web-dev-qa-db-ja.com

webdriverで2つの要素のいずれかを見つける

私のアプリケーションでは、ページXを開くと、要素Aまたは要素Bのいずれかが表示されるはずです。これらはDOMのさまざまな場所に配置され、driver.findElement(By.id("idA"))などのIDを使用して見つけることができます。

WebドライバーにAまたはBのいずれかを見つけるように依頼するにはどうすればよいですか?

少なくとも1つの要素が見つかると待機を停止するメソッドdriver.findElements(By)がありますが、このメソッドでは、AとBに同じロケーターを使用する必要があります。

暗黙のタイムアウトを待つ必要がないように、AまたはBのいずれかを確実に見つける適切な方法は何ですか?

16
pavel_kazlou

IDI1の要素またはIDI2の要素

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に関する詳細情報を入手できます ここ

あなたの問題に対する私見の解決策は次のようになります:

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

参考: ここ はNice xpath、cssSelectorのマニュアルです

これがお役に立てば幸いです。

20

これが私の解決策であり、他の人が示唆しているように流暢な待機を使用します。 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()を呼び出すことで要素の存在を確認するのは不格好に思えるかもしれませんが、SeleniumAPIでこれを行うための推奨される方法のようです。

2
James Martineau

このために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;
        }
    };
}
2
user1518458

私もこの問題に出くわしたので、その方法を作りました。メソッドは、「self._driver」としてWebドライバーを含むクラス内にあることに注意してください。コードは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
1
Brady

PYTHONでこれを実行しようとしている人のために

これが私にとって有効な非常に単純なメソッドです。最初に_implicitly_wait_メソッドを使用して、ドライバーが要素を見つけるまで、指定された時間だけ自動的に待機します。

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

ここで、2つの異なる要素のいずれか(または同じ要素を異なる方法で)を見つけたい場合は、次のようにします。

_#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. 
_

これは簡単に関数に変換し、3つ以上の要素を検索するようにスケーリングできますが、元の投稿ではJavaでのヘルプが求められていたため、これは避けたかっただけです。それにもかかわらず、Seleniumコマンドは言語間でかなり統一されているので、python)で作業していない人にも役立つように支援します:)

0
seeiespi

@ pavel_kazlou、FluentWaitに関する質問については、基本的に2種類の待機があります:明示的な待機

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

暗黙の待機

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

違いは

  • 明白-暗黙の待機時間はスクリプト内のすべての要素に適用されますが、明示的なのは特定の要素にのみ適用されます
  • Explicitでは、状態をチェックする頻度(500ミリ秒ではなく)を構成できます。
  • Explicitでは、タイムアウトまで「NoSuchElement」以外の例外を無視するように構成することもできます。WebDriverWait(実際にはFluentWaitを拡張します)と同様に機能する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を待機する一定の時間が含まれます。 IMHO fluentWaitアプローチは、この観点からより堅牢です。

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");
}
0
BullyWiiPlaza