如何在Java中要求Selenium-WebDriver等待几秒钟?

118

我正在开发一个基于Java的Selenium-WebDriver。 我添加了

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

并且

WebElement textbox = driver.findElement(By.id("textbox"));

因为我的应用程序需要几秒钟才能加载用户界面, 所以我设置了2秒的隐式等待. 但是我遇到了无法定位元素文本框的问题。

然后我添加了Thread.sleep(2000);

现在它正常工作了。哪种方法更好?


3
可能是selenium webdriver - 显式等待与隐式等待的区别的重复问题。 - Lesmana
14个回答

135

好的,等待有两种类型:显式等待和隐式等待。 显式等待的思想是

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

隐式等待的概念是

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
你可以在这里获取详细的差异:链接。 在这种情况下,我更喜欢使用显式等待(特别是fluentWait):
public WebElement fluentWait(final By locator) {
    Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
            .withTimeout(30, TimeUnit.SECONDS)
            .pollingEvery(5, TimeUnit.SECONDS)
            .ignoring(NoSuchElementException.class);

    WebElement foo = wait.until(new Function<WebDriver, WebElement>() {
        public WebElement apply(WebDriver driver) {
            return driver.findElement(locator);
        }
    });

    return  foo;
};
fluentWait函数返回你找到的网页元素。 根据fluentWait文档: Wait接口的实现,可以在运行时配置其超时和轮询间隔。 每个FluentWait实例定义了等待条件的最长时间以及检查条件的频率。此外,用户可以配置等待忽略在等待期间出现的特定类型异常,例如在页面上搜索元素时出现的NoSuchElementExceptions异常。 更多详细信息请参见此处 在您的情况下,使用fluentWait的用法如下:
WebElement textbox = fluentWait(By.id("textbox"));

在我看来,这种方法更好,因为您不知道需要等待多长时间,在轮询间隔中,您可以设置任意时间值,而该元素的出现将通过验证。


3
这是“import com.google.common.base.Function;”,而不是“import java.util.function.Function;”。 - haventchecked
还没有检查过,实际上我正在使用Intelij IDEA进行开发,它可以帮助自动导入(在基于Maven的项目中工作时)。 - eugene.polschikov
1
我只是为了其他可能想知道使用了哪个“Function”的人而提出这个问题。一开始对我来说并不明显。感谢您的回答。 - haventchecked
1
或者你可以使用lambda表达式:driver -> driver.findElement(locator)。使用这种方式,你根本不需要导入任何语句。 - Thibstars
2
现在是: .withTimeout(Duration.ofSeconds(30)).pollingEvery(Duration.ofSeconds(5)) 而不是: .withTimeout(30, TimeUnit.SECONDS).pollingEvery(5, TimeUnit.SECONDS) - SuperRetro
显示剩余2条评论

18

如果使用webdriverJs(node.js),

driver.findElement(webdriver.By.name('btnCalculate')).click().then(function() {
    driver.sleep(5000);
});

点击按钮后,上述代码会让浏览器等待5秒钟。


20
为什么在问题明确是关于Java而不是node/JavaScript时,你要发布这个内容?这和回答如何使用ruby做某事一样离题。请避免无关的回答。 - Vala
1
推荐使用sleep绝对不是一个好的解决方案。 - timbre timbre
3
@Thor84no 主要是因为我们中的一些人在寻找网络解决方案时发现了这个答案。 - Kitanga Nday

18

这个帖子有点旧,但我想发表一下我目前的做法(正在进行中)。

虽然我仍然会遇到系统负载很重的情况,当我点击提交按钮时(例如,login.jsp),所有三个条件(见下文)都返回 true ,但下一个页面(例如,home.jsp)还没有开始加载。

这是一个通用的等待方法,它接受一系列的 ExpectedConditions。

public boolean waitForPageLoad(int waitTimeInSec, ExpectedCondition<Boolean>... conditions) {
    boolean isLoaded = false;
    Wait<WebDriver> wait = new FluentWait<>(driver)
            .withTimeout(waitTimeInSec, TimeUnit.SECONDS)
            .ignoring(StaleElementReferenceException.class)
            .pollingEvery(2, TimeUnit.SECONDS);
    for (ExpectedCondition<Boolean> condition : conditions) {
        isLoaded = wait.until(condition);
        if (isLoaded == false) {
            //Stop checking on first condition returning false.
            break;
        }
    }
    return isLoaded;
}

我定义了几种可重用的ExpectedConditions(其中三个如下)。 在此示例中,这三个预期条件包括document.readyState ='complete',不存在“wait_dialog”,以及没有显示异步数据正在请求的“spinners”(表示异步数据正在请求的元素)。

只有第一个可以通用地应用于所有网页。

/**
 * Returns 'true' if the value of the 'window.document.readyState' via
 * JavaScript is 'complete'
 */
public static final ExpectedCondition<Boolean> EXPECT_DOC_READY_STATE = new ExpectedCondition<Boolean>() {
    @Override
    public Boolean apply(WebDriver driver) {
        String script = "if (typeof window != 'undefined' && window.document) { return window.document.readyState; } else { return 'notready'; }";
        Boolean result;
        try {
            result = ((JavascriptExecutor) driver).executeScript(script).equals("complete");
        } catch (Exception ex) {
            result = Boolean.FALSE;
        }
        return result;
    }
};
/**
 * Returns 'true' if there is no 'wait_dialog' element present on the page.
 */
public static final ExpectedCondition<Boolean> EXPECT_NOT_WAITING = new ExpectedCondition<Boolean>() {
    @Override
    public Boolean apply(WebDriver driver) {
        Boolean loaded = true;
        try {
            WebElement wait = driver.findElement(By.id("F"));
            if (wait.isDisplayed()) {
                loaded = false;
            }
        } catch (StaleElementReferenceException serex) {
            loaded = false;
        } catch (NoSuchElementException nseex) {
            loaded = true;
        } catch (Exception ex) {
            loaded = false;
            System.out.println("EXPECTED_NOT_WAITING: UNEXPECTED EXCEPTION: " + ex.getMessage());
        }
        return loaded;
    }
};
/**
 * Returns true if there are no elements with the 'spinner' class name.
 */
public static final ExpectedCondition<Boolean> EXPECT_NO_SPINNERS = new ExpectedCondition<Boolean>() {
    @Override
    public Boolean apply(WebDriver driver) {
        Boolean loaded = true;
        try {
        List<WebElement> spinners = driver.findElements(By.className("spinner"));
        for (WebElement spinner : spinners) {
            if (spinner.isDisplayed()) {
                loaded = false;
                break;
            }
        }
        }catch (Exception ex) {
            loaded = false;
        }
        return loaded;
    }
};

根据页面的不同,我可能会使用其中一个或全部:

waitForPageLoad(timeoutInSec,
            EXPECT_DOC_READY_STATE,
            EXPECT_NOT_WAITING,
            EXPECT_NO_SPINNERS
    );

还可以使用以下类中预定义的ExpectedConditions:

org.openqa.selenium.support.ui.ExpectedConditions

1
很棒的回答!我从未见过有人通过方法构造函数传递ExpectedCondition项。太棒了,点赞+1。 - djangofan
Jeff Vincent,请问这个程序可以让页面等待5分钟吗?如果可以,请建议应该发送哪个位置参数? - khanam
这是一个很好的答案。我有类似的功能,但是我必须在每个函数中调用它,以便我的脚本等待页面加载(旋转器)。有没有办法将这个waitForSpinner函数静默地挂钩到ImplicitWait中,以便我不需要在每个函数中都调用它?就像定义函数,将其一次性挂钩到驱动程序上,然后就完成了。 - Bhuvanesh Mani

11

Thread.sleep(2000); 是一种无条件的等待方法。如果你的测试加载速度更快,你仍然需要等待2秒。因此,原则上使用 implicitlyWait 是更好的解决方案。

然而,我不明白为什么 implicitlyWait 在你的情况下不起作用。你是否测量了 findElement 在抛出异常前实际花费两秒钟。如果是这样,你可以尝试使用 WebDriver 的条件等待,如答案所述。


2

点击似乎被阻塞了?- 如果您正在使用WebDriverJS,这里有另一种等待的方法:

driver.findElement(webdriver.By.name('mybutton')).click().then(function(){
  driver.getPageSource().then(function(source) {
    console.log(source);
  });
});

以上代码在按钮被点击后等待下一页加载完毕,然后获取下一页的源代码。

1
代码是如何等待下一页加载的?仅仅是回调函数中第一个调用getPageSource的原因吗? - Isochronous

2

我喜欢使用自定义条件。以下是Python代码示例:

def conditions(driver):
    flag = True
    ticker = driver.find_elements_by_id("textbox")
    if not ticker:
        flag = False
    return flag

... click something to load ...
self.wait = WebDriverWait(driver, timeout)
self.wait.until(conditions)

无论何时需要等待,您都可以通过检查某个元素的存在来显式地执行它(此元素可能因页面而异)。find_elements_by_id返回列表 - 可为空,也可非空,您只需检查即可。

1
隐式等待和Thread.sleep都仅用于同步,但区别在于我们可以在整个程序中使用隐式等待,而Thread.sleep仅适用于单个代码。我的建议是,在程序中只使用一次隐式等待,每当网页刷新时使用Thread.sleep。这样会更好 :)
以下是我的代码:
package beckyOwnProjects;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.interactions.Actions;

public class Flip {

    public static void main(String[] args) throws InterruptedException {
        WebDriver driver=new FirefoxDriver();
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(2, TimeUnit.MINUTES);
        driver.get("https://www.flipkart.com");
    WebElement ele=driver.findElement(By.cssSelector(".menu-text.fk-inline-block"));
    Actions act=new Actions(driver);
    Thread.sleep(5000);
    act.moveToElement(ele).perform();
    }

}

1

使用操作 -

模拟复杂用户手势的面向用户的API。

参见 Actions#pause 方法。


0

答案:使用Selenium WebDriver等待元素可见之前,请等待几秒钟,以下是一些方法。

implicitlyWait():WebDriver实例将等待完整页面加载。您必须使用30到60秒等待完整页面加载。

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

ExplicitlyWait WebDriverWait():WebDriver实例等待页面完全加载。

WebDriverWait wait = new WebDriverWait(driver, 60);

wait.until(ExpectedConditions.visibilityOf(textbox));

driver.findElement(By.id("Year")).sendKeys(allKeys);

注意:请使用ExplicitlyWait WebDriverWait()来处理任何特定的WebElement。


0
有时候隐式等待会失败,提示元素存在但实际上并不存在。
解决方法是避免使用driver.findElement,而是用一个自定义的方法来代替它,该方法隐式地使用显式等待。例如:
import org.openqa.selenium.NoSuchElementException;


public WebElement element(By locator){
    Integer timeoutLimitSeconds = 20;
    WebDriverWait wait = new WebDriverWait(driver, timeoutLimitSeconds);
    try {
        wait.until(ExpectedConditions.presenceOfElementLocated(locator));
    }
    catch(TimeoutException e){
        throw new NoSuchElementException(locator.toString());
    }
    WebElement element = driver.findElement(locator);
    return element;
}

除了偶尔出现的失败(请参见此link)之外,还有其他原因要避免使用隐式等待。

您可以像使用driver.findElement一样使用此“element”方法。例如:

    driver.get("http://yoursite.html");
    element(By.cssSelector("h1.logo")).click();

如果你真的想要等待几秒钟来进行故障排除或其他罕见情况,你可以创建一个类似于Selenium IDE提供的暂停方法:

    public void pause(Integer milliseconds){
    try {
        TimeUnit.MILLISECONDS.sleep(milliseconds);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接