【问题标题】:Selenium - Is it okay to mix implicit wait and explicit wait like this?Selenium - 可以像这样混合隐式等待和显式等待吗?
【发布时间】:2020-06-30 22:13:48
【问题描述】:

这是一个udemy course(来自“Lets Kode It”),用于使用 selenium 和 Java 开发 Web 自动化框架。 但是,这不是 java 问题。您只需要了解这些语言中的任何一种 - javascript、python、ruby、c# 和 java 的 selenium。

讲师开发了一个 CustomDriver 类,该类具有下面给出的方法/功能。该方法等待元素可点击,而无需在我们的代码中到处写WebDriverWait 语句。它首先将隐式等待设置为零,进行显式等待,然后将隐式等待设置为框架中使用的原始值。

这种方法对我来说似乎没问题,但我不确定。像这样混合隐式和显式等待会导致任何问题吗?

更新(2020 年 3 月 24 日)- 我已经知道混合隐式和显式等待被认为是一种不好的做法,因为它可能导致无法预测的等待时间。我不是在问不可预测的等待时间,因为已经有很多关于这方面的问题和文章。

相反,我要问的是,如果每次执行显式等待之前都将隐式等待设置为零,那可以吗?这还会导致不可预测的等待问题吗?会不会引起其他问题?

/*
 Click element when element is clickable
 @param locator - locator strategy, id=>example, name=>example, css=>#example,
                       tag=>example, xpath=>//example, link=>example
 @param timeout - Duration to try before timeout
 */
public void clickWhenReady(By locator, int timeout) {
    try {
        driver.manage().timeouts().implicitlyWait(0, TimeUnit.SECONDS);
        WebElement element = null;
        System.out.println("Waiting for max:: " + timeout + " seconds for element to be clickable");

        WebDriverWait wait = new WebDriverWait(driver, 15);
        element = wait.until(
                ExpectedConditions.elementToBeClickable(locator));
        element.click();
        System.out.println("Element clicked on the web page");
        driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
    } catch (Exception e) {
        System.out.println("Element not appeared on the web page");
        driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
    }
}

【问题讨论】:

  • 建议不要混合使用,但这似乎是为了确保隐式等待不会与显式等待混淆。隐式的默认值为 0。
  • @pcalkins - 是的,我明白代码中发生了什么。但是,我还需要知道代码是否正常。
  • 很难说没有看到其余的代码。我更喜欢总是使用显式等待,但是当您只需要知道元素是否存在时,我可以看到在哪里设置隐式可能更方便。这可能比仅使用显式更有效/优雅是有原因的。
  • @pcalkins - 为什么不看其余代码就很难说?您能否举例说明了解其余代码有何帮助?示例 - 我期待这样的回复 -> 当您的测试并行运行时,此代码将无法正常工作,否则会弄乱您的日志记录。您是否在其余代码中执行任何这些操作?这就是为什么我需要查看您的其余代码的原因。
  • 基本上我的意思是,如果大部分代码只依赖于隐式等待,而这种特殊的显式等待很少使用,它可能会更优雅/高效,因为你只需要设置隐式等待一次,无需担心为每个操作/过程定义显式等待。

标签: selenium selenium-webdriver webdriverwait


【解决方案1】:

我不建议将它们混合使用。由于Implicit wait 大部分时间在WebDriver 系统的remote 端实现,这意味着它们在基于浏览器的驱动程序(例如:chromedriver.exe、IEDriverServer.exe)中进行处理,而Explicit Wait 在本地语言绑定,如 Java、ruby、python 等。

以下是使用remote server 运行脚本时发生的典型示例。

本地代码 -> 远程服务器 -> 远程服务器上的本地语言绑定 -> 远程组件,如 chromedriver.exe 或 IEDriverServer.exe。如果涉及网格,事情会变得更加复杂,因为它可能是链之间的另一层。

因此,当您混合使用隐式和显式等待时,您最终可能会遇到未定义的行为。此外,由于隐式等待是在驱动程序级别实现的,它们可能随时更改并对您的脚本产生影响。因此,最好坚持显式等待并完全控制。

使用当前技术,元素可能会在元素出现后才呈现。因此,使用implicit wait 是不够的,因此强烈推荐使用explicit wait。可能有一些边缘情况我们可能不得不使用implicit wait,但如果您计划将来扩展您的脚本以在网格上运行/使用远程服务器,请不要将它们混合使用。

【讨论】:

  • 这里是Selenium reference,您可以在其中找到更多详细信息。
  • 请在答案中添加链接。
  • 本地代码和远程驱动之间的通信有图吗?什么是“未定义的行为”,为什么会发生?
  • 这是 selenium 文档中undefined behavior 的示例。 Warning: 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.-- Source Seleniu documentation
  • 我知道这一点。我已经更新了我的问题以使其更清楚。请阅读问题的更新部分。
【解决方案2】:

使用隐式等待和显式等待的问题归结为 ExpectedConditions 在 Selenium 源代码中实现的缺陷。

让我通过分析以下代码来解释问题:

WebDriver driver = new ChromeDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
WebElement element =  new WebDriverWait(driver, 5, 1000).until(ExpectedConditions.elementToBeClickable(By.id("some_id")));
  1. 我们初始化驱动程序并将隐式等待设置为 10 秒。这意味着driver.findElement() 在抛出NoSuchElementException 之前将等待10 秒直到找到元素。 这是一个非常重要的注意点
  2. 然后,我们设置了一个持续时间为 5 秒的显式等待,轮询之间的睡眠时间为 1000 毫秒(1 秒)。这意味着WebDriverWait 将每隔 1 秒到 5 秒轮询ExpectedConditions 是否为真。如果ExpectedConditions 返回true,则轮询将停止并返回ExpectedConditions 中指定的Object。在上面的示例代码中,返回的对象是WebElement。如果ExpectedConditions 在 5 秒后为 false,则会抛出 TimeoutException或者我们期望)。现在是时候看看ExpectedConditions 会发生什么了。
  3. ExpectedConditions.elementToBeClickable() 代码具有以下语法。

    public static ExpectedCondition<WebElement> elementToBeClickable(final By locator) {
        return new ExpectedCondition<WebElement>() {
            @Override
            public WebElement apply(WebDriver driver) {
                WebElement element = visibilityOfElementLocated(locator).apply(driver);
                try {
                    if (element != null && element.isEnabled()) {
                        return element;
                    }
                    return null;
                } catch (StaleElementReferenceException e) {
                    return null;
                }
            }
        };
    }
    
  4. 上面的elementToBeClickable依次调用visibilityOfElementLocated()方法来确认元素是否可见。

    public static ExpectedCondition<WebElement> visibilityOfElementLocated(final By locator) {
        return new ExpectedCondition<WebElement>() {
            @Override
            public WebElement apply(WebDriver driver) {
                try {
                    return elementIfVisible(driver.findElement(locator));
                } catch (StaleElementReferenceException e) {
                    return null;
                }
            }
        };
    }
    

    5.注意driver.findElement(locator) 是如何在上面的visibilityOfElementLocated() 方法中调用的。如果未找到该元素,则将应用 10 秒的隐式等待。所以驱动程序将等待 10 秒,直到它抛出 NoSuchElementException

但是等等(双关语不是故意的)!我们的显式等待不是在elementToBeClickable() 条件下设置为5 秒超时吗?是的,但是会首先应用隐式等待。 WebDriverWait 将捕获 NoSuchElementException 并在 10 秒后抛出 TimeoutException,而不是设置显式等待 5 秒。这就是问题中的解决方案试图解决的问题。该解决方案尝试将隐式等待设置为 0 秒,以便正确执行显式等待条件,然后重置隐式等待。

问题中提供的实现确实可以在没有一个细节的情况下完成工作。隐式等待被硬编码为 3 秒,这并不理想。如何将隐式等待作为全局通用常量提供是非常具体的。我们正在为驱动程序设置隐式等待,我们可以期待像“驱动程序”driver.manage().timeouts().getImplicitWait() 这样的隐式等待。虽然很理想,但不幸的是,这不可能直接实现。有一些变通方法,@forresthopkinsa 有一个漂亮的interesting solution 关于创建扩展驱动程序以获得隐式等待

更新(2020 年 3 月 24 日)

问:这种方法对我来说似乎没问题,但我不确定。像这样混合隐式和显式等待会导致任何问题吗?

我要问的是,如果每次执行显式等待之前都将隐式等待设置为零,那可以吗?这还会导致不可预测的等待问题吗?会不会引发其他问题?

据我了解,将隐式等待设置为 0 然后执行显式等待然后切换回来应该没有任何问题,因为在测试执行期间的任何时间点设置隐式等待都没有限制。

此外,如果您考虑一下,从代码执行的角度来看,您实际上并没有在解决方案中混合隐式等待和显式等待。事实上,你正在做相反的事情!如果您将隐式等待设置为某个非零值,然后执行显式等待,这就是真正的混合发生的地方。如果适用,首先执行隐式执行,然后执行显式等待导致不一致。如果将隐式等待设置为 0,则从超时方程中取出隐式等待!

OP 中的解决方案不会出现不可预知的等待问题。

【讨论】:

  • 顺便说一句,我已经更新了我的问题以使其更清楚。请阅读问题的更新部分。
【解决方案3】:

当我们说混合隐式和显式等待不好时,我们的实际意思是在使用显式等待时不应设置非零的隐式等待值。

如果您在调用显式等待之前将显式等待使用的驱动程序对象的隐式等待值重置为 0(即您实际上开始等待),从技术上讲它应该可以正常工作,因为您并没有真正混合隐式等待和明确的等待。如果你能成功,这是一个可行的策略。

话虽如此,要不断调整隐式等待值并检查您是否准确地跟踪它可能需要做很多工作。在共享代码库中更糟糕的是,另一个人不知道这是您的策略(或不理解它)。这就是为什么一般建议只是不要使用隐式等待,而是使用显式等待。

其次,看看你实际上在做什么;如果您在每次要等待特定元素出现的交互之前显式更改隐式等待值,那么使用显式等待实际上并没有那么远。我建议通过使用显式等待,它将使您的代码的意图对将来阅读它的人更清楚,从而更容易维护。

【讨论】:

    猜你喜欢
    • 2013-12-14
    • 1970-01-01
    • 2018-01-24
    • 1970-01-01
    • 2017-04-19
    • 2015-06-11
    • 2023-04-08
    • 1970-01-01
    相关资源
    最近更新 更多