1. 概述

Web应用测试的一大挑战在于处理页面的动态特性。页面加载需要时间,元素可能延迟出现。为此,Selenium提供了等待机制,帮助我们在元素出现、消失或可点击时再继续执行测试。

本文将深入探讨不同等待类型的区别及使用方法,对比隐式等待与显式等待,并分享Selenium测试中使用等待的最佳实践。

2. Selenium 中的等待类型

Selenium提供了多种等待机制,用于等待元素出现、消失或变为可点击状态。这些机制可分为三类:隐式等待显式等待流畅等待

为便于演示,我们先定义几个页面定位常量:

private static final By LOCATOR_ABOUT = By.xpath("//a[starts-with(., 'About')]");
private static final By LOCATOR_ABOUT_BAELDUNG = By.xpath("//h3[normalize-space()='About Baeldung']");
private static final By LOCATOR_ABOUT_HEADER = By.xpath("//h1");

2.1 隐式等待

隐式等待是全局设置,作用于整个Selenium脚本中的所有元素。 当元素未找到时,它会等待指定时间后才抛出异常。每会话只能设置一次,后续无法修改,默认值为0

以下代码将隐式等待设为10秒:务必在初始化WebDriver实例后立即设置

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

使用隐式等待后,测试代码中无需显式等待任何元素。

下面这个简单测试访问www.baeldung.com并通过头部菜单跳转到About页面。可以看到代码中没有显式等待指令,在10秒隐式等待下测试通过:

void givenPage_whenNavigatingWithImplicitWait_ThenOK() {
    final String expected = "About Baeldung";
    driver.navigate().to("https://www.baeldung.com/");

    driver.findElement(LOCATOR_ABOUT).click();
    driver.findElement(LOCATOR_ABOUT_BAELDUNG).click();

    final String actual = driver.findElement(LOCATOR_ABOUT_HEADER).getText();
    assertEquals(expected, actual);
}

⚠️ 踩坑提醒:不设置隐式等待会导致测试失败!

2.2 显式等待

显式等待是更灵活的等待方式,允许等待特定条件满足后再继续执行测试。

我们可以使用ExpectedConditions类定义条件(如元素存在/消失)。若指定时间内条件未满足,将抛出异常。

WebDriver检查预期条件的轮询频率固定为500 ms。由于显式等待非全局设置,可为不同操作设置不同条件和超时时间。

回顾前文测试,没有隐式等待时会失败。现在我们改用显式等待实现:

首先创建超时10秒的Wait实例:

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(TIMEOUT));

然后使用此实例调用until()配合ExpectedConditions。此处使用visibilityOfElementLocated(..)

在操作元素(如点击)前,需等待元素可见或可点击:

void givenPage_whenNavigatingWithExplicitWait_thenOK() {
    final String expected = "About Baeldung";
    driver.navigate().to("https://www.baeldung.com/");

    driver.findElement(LOCATOR_ABOUT).click();
    wait.until(ExpectedConditions.visibilityOfElementLocated(LOCATOR_ABOUT_BAELDUNG));

    driver.findElement(LOCATOR_ABOUT_BAELDUNG).click(); 
    wait.until(ExpectedConditions.visibilityOfElementLocated(LOCATOR_ABOUT_HEADER));

    final String actual = driver.findElement(LOCATOR_ABOUT_HEADER).getText();
    assertEquals(expected, actual);
}

虽然需要手动管理等待,但灵活性大幅提升,能显著改善测试性能。

ExpectedConditions类提供了多种实用方法:

  • elementToBeClickable()
  • invisibilityOf()
  • presenceOfElementLocated()
  • textToBePresentInElement()
  • visibilityOf()

2.3 流畅等待

流畅等待是显式等待的增强版,提供更精细的等待控制。 它允许我们自定义预期条件和轮询机制。

同样改造前文测试,只需修改Wait实例的创建方式,指定轮询频率:

Wait<WebDriver> wait = new FluentWait<>(driver)
  .withTimeout(Duration.ofSeconds(TIMEOUT))
  .pollingEvery(Duration.ofMillis(POLL_FREQUENCY));

测试代码与显式等待完全一致:

void givenPage_whenNavigatingWithFluentWait_thenOK() {
    final String expected = "About Baeldung";
    driver.navigate().to("https://www.baeldung.com/");

    driver.findElement(LOCATOR_ABOUT).click();
    wait.until(ExpectedConditions.visibilityOfElementLocated(LOCATOR_ABOUT_BAELDUNG));

    driver.findElement(LOCATOR_ABOUT_BAELDUNG).click();
    wait.until(ExpectedConditions.visibilityOfElementLocated(LOCATOR_ABOUT_HEADER));

    final String actual = driver.findElement(LOCATOR_ABOUT_HEADER).getText();
    assertEquals(expected, actual);
}

3. 隐式等待 vs 显式等待

两者都用于等待元素出现,但存在关键差异:

对比维度 隐式等待 显式等待
超时设置 全局默认超时 针对特定条件的局部超时
等待条件 仅等待元素出现 可等待多种条件(可见/可点击等)
作用域 整个会话全局生效 仅作用于指定元素
异常类型 NoSuchElementException TimeoutException

简单总结:

  • 隐式等待适合简单场景,只需元素出现
  • 显式等待适合复杂场景,需精确控制条件

⚠️ 官方警告:不要混用两种等待!

警告:不要混用隐式和显式等待。混用会导致不可预测的等待时间。例如设置隐式等待10秒+显式等待15秒,可能实际等待20秒才超时。

4. 最佳实践

使用Selenium等待机制时,请遵循这些实践:

  1. 始终使用等待:等待元素加载是自动化测试的关键步骤
  2. 优先使用显式等待:隐式等待在元素不存在时会延长测试失败时间
  3. 灵活使用流畅等待:当需要反复验证特定条件时,流畅等待提供更精细控制
  4. 设置合理超时
    • ❌ 超时过短 → 误报失败
    • ❌ 超时过长 → 测试执行效率低
  5. 善用ExpectedConditions:确保测试等待正确条件,并在条件未满足时快速失败

5. 处理 StaleElementReferenceException

StaleElementReferenceException(陈旧元素引用异常)发生在定位的元素失效时,常见于DOM更新后。

解决方案:每次异常时重新定位元素。 捕获异常后重新定位并重试操作,但需限制重试次数:

boolean stale = true;
int retries = 0;
while(stale && retries < 5) {
    try {
        element.click();
        stale = false;
    } catch (StaleElementReferenceException ex) {
        retries++;
    }
}
if (stale) {
    throw new Exception("Element is still stale after 5 attempts");
}

进阶方案: 封装WebElementWebDriver,内部处理异常。这样测试代码无需关心StaleElementReferenceException,只需在封装类中处理可能抛出该异常的方法。

6. 总结

等待机制是编写高效Selenium测试的关键环节。合理使用等待可以:

  • 避免时序问题
  • 确保元素加载完成再交互
  • 降低测试失败率

显式等待比隐式等待提供更精确的控制。当需要更精细控制时,流畅等待允许自定义轮询频率检查特定条件。

遵循最佳实践能显著提升自动化测试的效率和可靠性。所有示例代码可在GitHub获取。


原始标题:Implicit Wait vs Explicit Wait in Selenium Webdriver