Selenium:尽管存在不可见元素,但让 findElements 等待可见元素

Selenium: Let findElements wait for visible element although invisible element exists

我们想将一些键发送到由名称标识的元素。在应用程序中可能有多个具有相同名称的元素,但在这种情况下只有一个元素是可见的。为此,我们有一个这样的代码片段(简单代码,没有生产代码):

List<WebElement> list = driver.findElements(By.xpath("//[@name='title']"));
for (WebElement elem : list) {
    try {
         elem.sendKeys(value);
         break;
    } catch (Exception e) {
         // ignore
    }
}

如果标题元素还不存在,我们使用隐式等待等待它出现。所以通常这会很好用。无论如何,有时我们会遇到这样的情况,即已经有具有该名称的元素(但被隐藏),而正确的元素将由异步代码创建。但在这种情况下,代码将不起作用。由于 findElements() 将 return 立即(没有隐式等待)只是 returning 不可见元素。在这种情况下,sendKeys() 将等待元素变得可见,但这永远不会发生(忽略在 findElements 之后创建的新元素),因此在隐式等待超时后它会失败。

基本上我们需要能够告诉 findElements() 我们只想拥有可见元素。如果没有可见元素,Selenium 应该等待隐式等待时间。这可能吗?

作为您的用例涉及:

  • 可能有多个同名元素,但在这种情况下只有一个可见
  • 将一些键发送到由名称标识的元素
  • 等待出现
  • 使用隐式等待

满足上述所有条件的多用途解决方案是使用 WebDriverWait in conjunction with ExpectedConditions 设置为 elementToBeClickable()

  • elementToBeClickable(): 期望检查元素可见并启用,以便您可以单击它。

  • 代码示例:

    try {
        new WebDriverWait(driver, 20).until(ExpectedConditions.elementToBeClickable(By.xpath("//button[@class='nsg-button']"))).sendKeys(value);
    }
    catch (TimeoutException e) {
        System.out.println("Desired element was not present");
    }
    

此外,您必须删除

的所有实例

Note: Do not mix implicit and explicit waits. Doing so can cause unpredictable wait times. For example setting an implicit wait of 10 seconds and an explicit wait of 15 seconds, could cause a timeout to occur after 20 seconds.

您可以在 Replace implicit wait with explicit wait (selenium webdriver & java)

中找到相关讨论

根据 DebanjanB 和 JeffC 的回答,我能够创建自己的等待实现,它等待第一个可见元素,但也考虑了在等待期间创建的元素:

new WebDriverWait(driver, 5).until(drv -> {
    List<WebElement> elements = drv.findElements(By.xpath("//[@name='title']"));
    for (WebElement element : elements) {
        if (element.isDisplayed()) {
            return element;
        }
    }
    return null;
});

或使用流 ;-)

new WebDriverWait(driver, 5).until(drv -> {
    List<WebElement> elements = drv.findElements(By.xpath("//[@name='title']"));
    return elements.stream()
        .filter(WebElement::isDisplayed)
        .findFirst()
        .orElse(null);
});