在自动化测试中,网页的加载速度、网络延迟或异步调用的影响会导致元素往往不能立刻出现在 DOM 树或不能立刻达到可交互状态。如果此时脚本尝试去操作该元素,就会抛出 NoSuchElementException 或 ElementNotInteractableException 等异常。
为了解决这个问题,Selenium 提供了三种主要的等待方式:
- 强制等待 (time.sleep)
- 隐式等待 (Implicit Wait)
- 显式等待 (Explicit Wait) (最常用、最推荐)
下面我们详细介绍这三种等待方式,并给出 Python 代码示例。
1. 强制等待 (time.sleep)
说明 :调用 time.sleep(秒数) 让程序在执行到该行时,无条件地暂停指定的时间。
优点 :使用非常简单。
缺点 :非常死板。如果设定的时间太长,会严重拖慢整个测试执行的效率;如果时间太短,遇到网络卡顿还是会报错。
适用场景:调试脚本时临时使用,或者处理一些实在无法用其他方式捕捉的特殊动画/弹窗过渡效果。但在正式的自动化项目中应尽量避免使用。
代码示例:
python
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://www.example.com")
# 强制等待 3 秒钟,无论元素是否已经出现
time.sleep(3)
element = driver.find_element(By.ID, "some_id")
element.click()
driver.quit()
2. 隐式等待 (Implicit Wait)
说明 :在创建 WebDriver 实例后设置一次即可。它告诉 WebDriver 在尝试查找任何元素时,如果没有立即找到,则轮询 DOM 一段时间直到元素出现。
优点 :只需要在代码开头声明一次,对整个 WebDriver 生命周期的所有 find_element 操作都起作用。
缺点:
- 只能判断元素是否在 DOM 树中存在,不能判断元素是否可见、是否可点击。
- 如果页面加载过程中有些元素隐藏在 DOM 中但需要等待特定状态改变,这往往无能为力。
- 一旦设置,会影响所有的查找操作。
适用场景:全局的、基本的元素存在性等待。
代码示例:
python
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
# 设置隐式等待时间为 10 秒
driver.implicitly_wait(10)
driver.get("https://www.example.com")
# 在接下来的 10 秒内,如果元素没有立即出现,WebDriver 会不断重试查找
# 如果 10 秒后还是找不到,才会抛出 NoSuchElementException
element = driver.find_element(By.ID, "some_id")
element.click()
driver.quit()
3. 显式等待 (Explicit Wait)
说明 :针对页面上某个特定的元素 设置等待条件和最长等待时间。程序会每隔一段时间(默认 0.5 秒)去检查条件是否成立,如果成立则立即继续执行,如果不成立则继续等待直到达到最大超时时间抛出 TimeoutException。
优点:
- 非常灵活,可以指定各种各样的等待条件(存在、可见、可点击、包含特定文本等)。
- 等待效率高,条件一旦满足立刻执行,不会浪费时间。
缺点 :代码相对复杂,需要针对每个需要等待的元素编写特定的等待代码。
适用场景 :目前主流和最推荐的等待方式。特别是处理 Ajax 异步加载、动态渲染的网页。
主要依赖类:
WebDriverWait:负责控制等待的时机。expected_conditions(通常缩写为EC):提供了一系列预定义的条件。
常用的 EC 条件:
presence_of_element_located:判断元素是否在 DOM 中存在。visibility_of_element_located:判断元素是否可见。element_to_be_clickable:判断元素是否可见且可以被点击。text_to_be_present_in_element:判断特定的文本是否出现在某个元素中。
代码示例:
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://www.example.com")
# 初始化 WebDriverWait,设置最大超时时间为 10 秒,通常可以提取为一个通用方法供调用
wait = WebDriverWait(driver, 10)
try:
# 等待直到 ID 为 "submit_button" 的元素变得可点击
# (By.ID, "submit_button") 是一个元组,代表定位器
button = wait.until(
EC.element_to_be_clickable((By.ID, "submit_button"))
)
button.click()
# 等待直到某个提示信息可见
message_element = wait.until(
EC.visibility_of_element_located((By.CSS_SELECTOR, ".success-msg"))
)
print("操作成功,提示信息为:", message_element.text)
except Exception as e:
print(f"等待元素时发生错误或超时: {e}")
finally:
driver.quit()
4. 自定义轮询频率与忽略异常 (类似 Fluent Wait)
在 Python 的 Selenium 中,WebDriverWait 其实已经可以通过传参实现 Fluent Wait 的理念。你可以自定义轮询的频率,以及在等待期间忽略特定的异常。
代码示例:
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException
driver = webdriver.Chrome()
driver.get("https://www.example.com")
# 配置一种更加定制化的显式等待
# 超时设为 15s,每 1s 轮询一次,并且在查找过程中忽略 NoSuchElementException 错误
fluent_wait = WebDriverWait(driver, timeout=15, poll_frequency=1, ignored_exceptions=[NoSuchElementException])
element = fluent_wait.until(EC.presence_of_element_located((By.ID, "dynamic-element")))
element.click()
driver.quit()
💡 最佳实践与建议总结
- 绝不混用 :不要把隐式等待和显式等待混合在一起使用。这样做会导致不可预测的等待超时行为,排查问题非常困难。
- 首选显式等待 :项目中强烈推荐使用 显式等待 (
WebDriverWait结合expected_conditions)。它可以精确控制条件,等待效率最高。 - 二次封装 :在实际的项目(如 PO 模式结构设计)中,因为每次使用
WebDriverWait都要写很多代码,建议将相关的定位、等待方法封装成基类中的公共基础方法,提高代码的复用性和整洁度。