15. 隐式等待与显式等待

它说:“时间具有向我们展示真正重要内容的绝妙方式”。 但是,等到一切变为现实,这一点同样重要。 如今,网页大多使用 JavaScript 和 AJAX 技术开发。 结果,页面上的不同元素在不同的时间加载。 当我们使用 Selenium WebDriver 自动化我们的手动测试并将其作为测试套件运行时,是时候提防您了,因为您可能会遇到 Selenium 的怪异行为。

您的测试用例可以成功运行,并且一次执行一行时可以按预期工作,但是作为套件/批处理运行时,它可能会失败。 挠头以了解其背后的原因? 让我们潜入更多!

当我们使用driver.get("url_we_wish_to_access")driver.navigate().to("url")或单击超链接等时,这些调用将在该加载活动完成之前加载启动页面并返回。 因此,如果页面的加载线程尚未填充 Web 元素,而您尝试使用driver.findElement("locating_strategy")找到它,则得到的只是异常NoSuchElementExceptionElementNotVisibleException等。

所有这些意味着我们必须找到一种告诉 Selenium 的方法,我们希望它等待一定的时间,或者直到 Web 元素变得可访问/可单击/显示为止,然后再引发可怕的异常。 在那里,不要惊慌。 我看到一些汗珠滴到你的额头上! 那么,如何使这种压力成为我们自己的大力水手菠菜的版本呢? 感谢隐式,显式和流畅的等待

隐式等待

了解我们使用findElementfindElements命令在 WebDriver 中定位 Web 元素非常重要(后面的文章将详细介绍该主题以及各种定位策略)。 因此,当尝试在页面上查找特定的 Web 元素时,如果该 Web 元素不是立即可用的,则隐式等待告诉 WebDriver 在指定的时间内轮询 DOM。 万一在指定的等待时间内找到该元素,测试将继续执行。 如果不是,则抛出NoSuchElementException

下面是带有隐式等待命令的代码片段,以使您更好地理解,

    @Before
    public void setUp() throws Exception{
        // Selenium version 3 beta releases require system property set up
        System.setProperty("webdriver.gecko.driver", "E:\\Selenium\\geckodriver-v0.10.0-win64\\geckodriver.exe");
        // Create a new instance for the class FirefoxDriver
        // that implements WebDriver interface
        driver = new FirefoxDriver();
        // Implicit wait for 3 seconds
        driver.manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
        // Assign the URL to be invoked to a String variable
        baseUrl = "https://www.google.com";
        pageTitle = "";
        expectedTitle = "Google";
    }

如突出显示的那样,使用隐式等待的语法是:

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

隐式等待有两个参数。 在此示例中,我们的第一个参数为 3 ,这是它必须等待的时间,第二个参数为TimeUnit.SECONDS。 这是时间测量。 可以指定为DAYSHOURSMINUTESSECONDSMILLISECONDSMICROSECONDSNANOSECONDS等。

此外,在键入此语句时,“TimeUnit”下方会出现一条弯曲的线。 悬停时,蚀将建议所有可能的快速修复。 单击建议导入java.util.concurrent.TimeUnit包的第一个修复程序。

  1. 隐式等待只需要初始化一次。 设置后,它将适用于WebDriver对象实例的寿命。 换句话说,它将在浏览器打开的整个过程中就位。 因此,WebDriver 将等待该额外时间,然后对所有findElementfindElements抛出异常
  2. 默认时间设置为 0。
  3. 隐式等待仅影响finddElement(s),它不影响其他WebDriver方法,例如driver.getCurrentUrl()等。
  4. 在大多数情况下,尤其是在调试时,仅“NoSuchElementException”并不是很有用。 我们可能希望使用更多信息来自定义此错误消息,例如捕获屏幕快照或添加更多详细信息等。可以通过在每次finddElement(s)调用周围使用try-catch块来实现此目的,但这不是我们使用隐式等待的目标,因为这是一个全球性的时间设置。
  5. 由于有大量的 JavaScript,因此可能会出现一些奇怪的情况。 即使该元素位于 DOM 中,也可能无法立即单击或显示或启用它。 结果我们可能会看到ElementNotVisibleExceptionWebDriverExceptionStaleElementReferenceException
  6. 由于不同的元素可能在不同的时间加载,因此我们可能会说服将等待时间设置为较高的值(或等效于最慢的 Web 元素的加载时间),例如 20 秒左右。 我们对此的理由是,“无论如何,所有元素的加载速度都将比此时快,并且测试将按预期进行。” 但是您甚至猜不到性能 - 这种方法不是一个好主意吗? 我在这里给您举几个示例,以便更好地了解,
  • 情况 1 :如果希望找到一个元素,但无论结果如何,都可以继续进行测试。
  • 情况 2 :如果仅需要验证是否缺少某个元素,例如等待警报框出现并关闭它。 即使这样,WebDriver 也将不得不等到超时,即在这种情况下为 20 秒。 在庞大的测试套件中运行时,这将产生巨大的影响。

显式等待

事不宜迟,让我们利用“显式等待”的力量。 显式等待需要更多的编码,但是与隐式等待相比,它具有巨大的优势。 在这里,我们可以等到某种情况发生后再进行测试。 如果在指定的超时值内未满足条件,则将引发异常。语法如下:

WebDriverWait wait = new WebDriverWait(driver, 15);
WebElement element = wait.until(ExpectedConditions.elementToBeClickable(By.id("element_id")));

因此,WebDriverWait类用于指定最大超时值,在这种情况下为 15 秒。 ExpectedCondition类的方法涵盖了我们希望在测试中出现之前要等待的大多数条件。 这些条件与WebDriverWait一起使用。

上面的代码一直等到元素变为可点击(即显示并启用)并返回结果。 默认情况下,WebDriverWait每 500 毫秒调用ExpectedCondition,直到成功返回。 在这种情况下,它会在抛出TimeoutException之前尝试长达 15 秒。 成功的返回值是布尔值true或非null对象。

ExpectedCondition预定义方法的一些示例是:

  • elementToBeClickable(By locator) – 用于检查元素的期望是可见的并已启用,以便您可以单击它。
  • elementToBeSelected(WebElement element) - 检查是否选择了给定元素。
  • presenceOfElementLocated(Bylocator) – 检查页面的 DOM 上是否存在元素。
  • urlToBe(java.lang.String url) – 检查当前页面的 URL 是一个特定的 URL。
  • visibilityOf(WebElement element) - 用于检查存在于页面的 DOM 上的已知元素的是可见的。

您可以在此处,找到适用于 Java 的ExpectedConditions包的所有可用方法及其用法的详细信息。

注意 :根据 Selenium 的官方文档,我们警告您不要混合使用隐式和显式等待,因为这可能会导致不可预测的等待时间。

流利的等待

谁不喜欢自定义? 对于显式等待,如果这是您要搜索的内容,那么我建议您使用流利的等待。 我们可以通过流畅的等待来配置以下内容,

  • 在抛出异常之前我们希望等待(超时)条件发生的最长时间,
  • 检查指定条件的频率,以及
  • 我们在等待条件发生时要忽略的异常类型

示例代码段如下所示:

public WebElement fluentWait(final By locator) {
    Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
            .withTimeout(20, TimeUnit.SECONDS)
            .pollingEvery(2, TimeUnit.SECONDS)
            .ignoring(NoSuchElementException.class)
            .until(new Function<WebDriver, WebElement>() {
                public WebElement apply(WebDriver driver) {
                return driver.findElement(locator);
                }
            });
    }

在这里,我们声明超时值为 20 秒的流畅等待,每 2 秒(频率)轮询 DOM 直到找到元素为止,并在此时间间隔内忽略NoSuchElementException。 我们在上面的代码片段中创建了一个新函数,以在.until()中标识此 Web 元素。

有几点值得您花时间

  1. 与隐式等待不同,此方法适用于findElement(s)以及您可能会想到的在自动化网页时进行枚举的任何其他条件。
  2. 可以自定义默认超时值和频率,并且仅将其应用于特定元素,这与隐式等待不同,隐式等待在启动后就适用于WebDriver对象实例的寿命。
  3. 我们可以实现自己的条件,也可以使用ExpectedConditions类中的预定义条件之一。
  4. 使用FluentWait,我们可以忽略某些异常并改为指定自定义错误消息。 这将我们的调试经验提升到一个全新的水平。
  5. 通过忽略特定的异常直到满足我们的条件,流利的等待比隐式等待更可靠。

同意的显式等待包括对代码的更多修饰,但是您认为值得这样做吗?

注意您可能会想,为什么要经历所有这些痛苦? 我不能只使用Thread.sleep()吗? 嗯,不,不建议这样做,因为它会使整个测试脚本无条件进入休眠状态。 在大多数情况下,指定的睡眠时间不够长(导致异常),或者变得太长,导致即使元素加载速度更快,测试也要等待。

令我全神贯注 :虽然显式等待涉及更多的编程部分,但由于它能够解决很多问题,使我们的生活变得更加轻松,我宁愿选择此方法,而不是隐式等待(基于测试自动化的需求和复杂性涉及眨眼 )。

您的视线是否模糊? 不要惊慌,因为所有水域都会落在后面的职位上。 我们将在屏幕截图中看到更实际的等待示例。 一旦涵盖了定位器的类型和策略,就可以更好地理解和掌握该主题。

下一节:如果您进入了 Selenium 世界,那么您一定已经遇到了 Web 元素! 文本框,按钮,链接,复选框,单选按钮,下拉菜单,警报等,所有内容均视为网络元素。 使用定位器类型和策略,我们可以轻松识别页面上任何所需的元素。 这是与页面进行交互并在代码中进一步引用这些元素的最关键步骤。 是的,它与我们在 Selenium IDE 中看到的非常相似,除了语法和概念的应用方式。