Skip to content

selenium_waits

现代 Web 应用程序通常是动态的。元素可能异步加载、延迟出现,或根据用户操作或后台进程改变状态。如果您的 Selenium 脚本尝试在元素准备好之前与其交互,您将会遇到诸如 NoSuchElementException 或 ElementNotInteractableException 等错误。

Selenium WebDriver 提供了不同的等待策略(waiting strategies)来处理这些时序问题,使您的测试更加可靠和稳定。

一种常见但不好的实践是使用固定的暂停时间,例如 Thread.sleep(5000) (Java) 或 time.sleep(5) (Python)。这会强制您的脚本等待一个预定的时间,无论元素是否已经准备好或需要更多时间。这会导致:

  • 测试慢: 如果元素很快出现,脚本仍然会进行不必要的等待。
  • 测试不稳定 (Flaky): 如果元素加载时间长于固定的等待时间,测试仍然会失败。
  • 测试不可靠: 网络条件和应用程序性能各不相同,固定的等待时间不可靠。

切勿在测试中使用 Thread.sleep()(或等效方法)来与浏览器操作同步。 请使用 Selenium 内置的等待机制。

隐式等待(Implicit wait)告诉 WebDriver 在查找任何不是立即可用的元素时,需要轮询(poll)DOM 一段时间。它在每个 WebDriver 会话中设置一次,并全局应用于所有 findElement 和 findElements 调用。

设置 (Java):

// 导入 java.time.Duration 类
// Import java.time.Duration;
// 设置隐式等待超时时间 (例如,10 秒)
// Set the implicit wait timeout (e.g., 10 seconds)
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

工作原理: 如果您调用 driver.findElement(By.id("myElement")) 且元素没有立即可用,WebDriver 将会持续尝试查找该元素,最长达 10 秒,之后才会抛出 NoSuchElementException。如果元素在此时间内出现,脚本将立即继续执行。

缺点:

  • 仅适用于元素在 DOM 中存在,不检查其可见性、可点击性或其他状态。
  • 可能通过掩盖较长的加载时间来隐藏性能问题。
  • 与显式等待混合使用时,可能导致不可预测的等待时间。
  • 通常不推荐使用,建议优先使用显式等待以获得更精确的控制。

显式等待(Explicit wait)使 WebDriver 暂停执行,并轮询 DOM,直到满足特定的 ExpectedCondition 或发生超时。它为特定的元素或条件局部应用,为您提供细粒度的控制。

设置 (Java):

// 导入必要的类
// Import necessary classes
// 导入 org.openqa.selenium.support.ui.WebDriverWait;
// import org.openqa.selenium.support.ui.WebDriverWait;
// 导入 org.openqa.selenium.support.ui.ExpectedConditions;
// import org.openqa.selenium.support.ui.ExpectedConditions;
// 导入 java.time.Duration;
// import java.time.Duration;
// 导入 org.openqa.selenium.By;
// import org.openqa.selenium.By;
// 导入 org.openqa.selenium.WebElement;
// import org.openqa.selenium.WebElement;
// 创建一个 WebDriverWait 实例 (例如,最多等待 15 秒)
// Create a WebDriverWait instance (e.g., wait up to 15 seconds)
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
// 示例 1: 等待直到元素可见
// Example 1: Wait until an element is visible
WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("dynamicElement")));
// 现在与元素进行交互
// Now interact with the element
element.click();
// 示例 2: 等待直到元素可点击
// Example 2: Wait until an element is clickable
WebElement submitButton = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//button[@type='submit']")));
submitButton.click();
// 示例 3: 等待页面标题包含特定文本
// Example 3: Wait for the page title to contain specific text
Boolean titleContains = wait.until(ExpectedConditions.titleContains("Results Page"));
// 示例 4: 等待弹窗出现
// Example 4: Wait for an alert to be present
Alert alert = wait.until(ExpectedConditions.alertIsPresent());
alert.accept();

工作原理: wait.until() 方法会定期检查条件(由 ExpectedConditions 或自定义函数定义),检查间隔可配置。如果条件在超时时间内变为真,它会返回结果(例如,WebElement、Boolean、Alert)。如果在达到超时时间之前条件未满足,它会抛出 TimeoutException。

常用的 ExpectedConditions (Java):

  • visibilityOfElementLocated(By locator): 元素存在且可见。
  • elementToBeClickable(By locator): 元素存在、可见且已启用。
  • presenceOfElementLocated(By locator): 元素存在于 DOM 中(可能不可见)。
  • invisibilityOfElementLocated(By locator): 元素不可见或不在 DOM 中。
  • alertIsPresent(): 弹窗出现。
  • titleContains(String title): 页面标题包含给定文本。
  • textToBePresentInElementLocated(By locator, String text): 元素包含指定的文本。
  • … 还有很多。

优点:

  • 精确: 只需等待满足特定条件所需的时间。
  • 灵活: 处理元素存在之外的各种条件。
  • 健壮: 通过与应用程序状态适当同步,使测试更加可靠。
  • 可读: 清晰地表明脚本在等待什么。

FluentWait 是一种显式等待,您可以配置超时持续时间、轮询频率(polling frequency)以及在等待条件时要忽略的异常。与 WebDriverWait(它本质上是一个预配置的 FluentWait)相比,它提供了更多的自定义选项。

设置 (Java):

// 导入必要的类
// Import necessary classes
// 导入 org.openqa.selenium.support.ui.FluentWait;
// import org.openqa.selenium.support.ui.FluentWait;
// 导入 org.openqa.selenium.support.ui.Wait;
// import org.openqa.selenium.support.ui.Wait;
// 导入 org.openqa.selenium.NoSuchElementException;
// import org.openqa.selenium.NoSuchElementException;
Wait<WebDriver> fluentWait = new FluentWait<>(driver)
// 最大等待时间
// .withTimeout(Duration.ofSeconds(30)) // Max wait time
.withTimeout(Duration.ofSeconds(30))
// 每 2 秒检查一次条件
// .pollingEvery(Duration.ofSeconds(2)) // Check condition every 2 seconds
.pollingEvery(Duration.ofSeconds(2))
// 在轮询期间忽略此异常
// .ignoring(NoSuchElementException.class); // Ignore this exception during polling
.ignoring(NoSuchElementException.class);
// 将 fluentWait 实例与 ExpectedConditions 或自定义函数一起使用
// Use the fluentWait instance with ExpectedConditions or a custom function
WebElement element = fluentWait.until(ExpectedConditions.elementToBeClickable(By.id("someId")));
element.click();
// 或者使用自定义函数 (Lambda 表达式)
// Or using a custom function (Lambda expression)
// WebElement customElement = fluentWait.until(d -> d.findElement(By.id("customId")));

何时使用: 在特定场景下非常有用,例如需要更精细地控制轮询,或者在等待元素最终出现时需要暂时忽略某些异常(如 NoSuchElementException)。

对于健壮(robust)和可维护(maintainable)的 Selenium 测试,一般建议是:

  • 主要使用显式等待 (WebDriverWait) 来与特定的元素状态或条件同步。
  • 如果广泛使用显式等待,避免使用隐式等待或将其设置为零,以防止不可预测的行为。
  • 切勿使用 Thread.sleep() 进行同步。

正确实现的等待机制对于创建稳定可靠的自动化 Web 测试至关重要。