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等待机制时,请遵循这些实践:
- 始终使用等待:等待元素加载是自动化测试的关键步骤
- 优先使用显式等待:隐式等待在元素不存在时会延长测试失败时间
- 灵活使用流畅等待:当需要反复验证特定条件时,流畅等待提供更精细控制
- 设置合理超时:
- ❌ 超时过短 → 误报失败
- ❌ 超时过长 → 测试执行效率低
- 善用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");
}
✅ 进阶方案: 封装WebElement
和WebDriver
,内部处理异常。这样测试代码无需关心StaleElementReferenceException
,只需在封装类中处理可能抛出该异常的方法。
6. 总结
等待机制是编写高效Selenium测试的关键环节。合理使用等待可以:
- 避免时序问题
- 确保元素加载完成再交互
- 降低测试失败率
显式等待比隐式等待提供更精确的控制。当需要更精细控制时,流畅等待允许自定义轮询频率检查特定条件。
遵循最佳实践能显著提升自动化测试的效率和可靠性。所有示例代码可在GitHub获取。