杰瑞科技汇

Selenium等待如何解决元素定位不稳定问题?

为什么需要等待?

当你用 Selenium 操作浏览器时,你的代码执行速度远快于浏览器渲染和处理页面的速度,你点击一个按钮,页面需要 2 秒钟才能加载出弹窗,如果你的代码在点击后立即就去查找弹窗中的元素,那么因为弹窗还没出现,代码就会失败,抛出 NoSuchElementException 异常。

Selenium等待如何解决元素定位不稳定问题?-图1
(图片来源网络,侵删)

等待就是用来解决这个问题的:它让代码在执行下一步操作前,暂停一段时间,直到某个条件(比如元素出现)满足为止。


Selenium 提供了三种主要的等待方式

  1. 强制等待
  2. 隐式等待
  3. 显式等待

下面我们逐一详细介绍。


强制等待

这是最简单、最直接的等待方式,它在代码中插入一个固定的“暂停”。

如何使用

使用 time.sleep() 方法,需要先导入 time 模块。

Selenium等待如何解决元素定位不稳定问题?-图2
(图片来源网络,侵删)
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://www.example.com")
# 强制等待 5 秒,无论页面是否加载完成
time.sleep(5) 
# 在等待 5 秒后,尝试查找页面上的标题
try:
    heading = driver.find_element(By.TAG_NAME, "h1")
    print(f"标题是: {heading.text}")
except Exception as e:
    print(f"发生错误: {e}")
driver.quit()

优点

  • 简单易用:一行代码就能搞定。
  • 直观:你能明确知道脚本会暂停多久。

缺点

  • 非常不灵活:你设置的等待时间可能太长(浪费测试时间),也可能太短(如果网络慢了,依然会失败)。
  • 降低脚本的效率:即使页面元素 1 秒后就加载出来了,脚本也必须傻等 5 秒。

只适合在调试、快速验证脚本时使用,不推荐在生产环境的自动化测试脚本中大量使用。


隐式等待

隐式等待是全局性的设置,它告诉 WebDriver 在查找任何元素时,如果没有立即找到,就轮询(每隔一小段时间)查找,直到元素出现或超过设定的最大时间。

如何使用

使用 driver.implicitly_wait() 方法。通常只需要设置一次,在 driver.get() 之前设置即可。

from selenium import webdriver
from selenium.webdriver.common.by import By
# 创建驱动实例
driver = webdriver.Chrome()
# 设置隐式等待,最长等待 10 秒
# 这会对整个 driver 的生命周期生效
driver.implicitly_wait(10) 
driver.get("https://www.example.com")
# 当查找元素时,如果找不到,Selenium 会等待最多 10 秒
# 如果页面需要 3 秒才加载出 h1,那么这行代码会等待 3 秒
# 如果超过 10 秒还没出现,才会抛出 NoSuchElementException
heading = driver.find_element(By.TAG_NAME, "h1")
print(f"标题是: {heading.text}")
# 同样适用于查找一组元素
paragraphs = driver.find_elements(By.TAG_NAME, "p")
print(f"找到 {len(paragraphs)} 个段落")
driver.quit()

优点

  • 代码简洁:只需要设置一次,后续的元素查找都不需要再写等待逻辑。
  • 全局生效:方便管理。

缺点

  • 不够精确:它只对 find_elementfind_elements 方法有效,对于其他操作(如 click()send_keys() 等)无效,如果元素找到了但不可点击(比如被其他元素遮挡),click() 操作依然会失败。
  • 固定等待时间:如果元素 1 秒就加载好了,它还是会等到轮询周期结束(通常几百毫秒),虽然比 time.sleep(10) 好,但仍有优化空间。
  • 对多个元素无效:如果你等待的是一组元素,隐式等待会在第一个元素出现时就返回,而不是等待所有元素都出现。

比强制等待好很多,适合简单的场景,但对于复杂的、需要精确判断元素状态的场景,显式等待是更好的选择

Selenium等待如何解决元素定位不稳定问题?-图3
(图片来源网络,侵删)

显式等待

这是最强大、最灵活、最推荐的等待方式,它允许你等待一个特定的条件(如元素可见、可点击、包含特定文本等)成立,一旦条件满足,脚本就立即继续执行,如果超过了设定的最大时间,才会抛出 TimeoutException 异常。

如何使用

需要导入 WebDriverWaitexpected_conditions (通常简写为 EC)。

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 对象
# 参数:1. driver 实例, 2. 最长等待时间(秒)
wait = WebDriverWait(driver, 10)
# 等待条件:ID 为 "myDynamicElement" 的元素变得可见
# 这是最常用的场景之一
try:
    # 直到元素可见,才会返回该元素
    element = wait.until(
        EC.visibility_of_element_located((By.ID, "myDynamicElement"))
    )
    # 找到元素后,就可以进行操作
    element.click()
    print("成功点击了元素!")
except Exception as e:
    print(f"等待超时或发生错误: {e}")
driver.quit()

expected_conditions 提供了丰富的条件

EC 模块提供了大量预定义的条件,你可以根据需要选择:

条件 方法 描述
元素可见 visibility_of_element_located((By, 'value')) 元素存在于 DOM 中,并且是可见的(宽和高都大于0)。
元素可点击 element_to_be_clickable((By, 'value')) 元素可见且是可点击的。
元素存在 presence_of_element_located((By, 'value')) 元素存在于 DOM 中,但不一定可见。
元素包含特定文本 text_to_be_present_in_element((By, 'value'), 'text') 元素的文本包含指定的字符串。
元素可被选中 element_to_be_selected(element) 用于复选框或单选框,检查其是否被选中。
元素不可见 invisibility_of_element_located((By, 'value')) 元素不存在于 DOM 中或存在但不可见。

你也可以自定义等待条件,只需要创建一个返回 TrueFalse 的函数即可。

# 自定义等待条件
def element_has_class(driver, locator, class_name):
    element = driver.find_element(*locator)
    return class_name in element.get_attribute("class")
# 使用自定义条件
wait.until(lambda d: element_has_class(d, (By.ID, "my_button"), "active"))

优点

  • 高度灵活:可以等待任意你想要的条件。
  • 精确控制:只在你需要的条件满足时才继续,效率最高。
  • 代码可读性强wait.until(EC.visibility_of_element_located(...)) 非常清晰地表达了你的意图。

缺点

  • 代码稍显冗长:每次等待都需要写几行代码。

对于任何严肃的自动化测试项目,显式等待都是首选,它能极大地提高脚本的稳定性和可维护性。


总结与最佳实践

等待类型 优点 缺点 适用场景
强制等待 简单、直观 不灵活、效率低 调试、快速验证
隐式等待 代码简洁、全局生效 不够精确、对非 find 操作无效 简单的脚本、全局等待元素出现
显式等待 最灵活、最精确、最稳定 代码稍长 复杂场景、需要精确判断元素状态、生产环境脚本

最佳实践建议

  1. 优先使用显式等待:对于任何关键操作(点击、输入、断言),都应该使用显式等待来确保元素处于期望的状态(如可见、可点击)。
  2. 谨慎使用隐式等待:如果你决定使用隐式等待,最好只设置一次,并且不要和显式等待混用,混用可能会导致意想不到的等待时间叠加。
  3. 避免使用强制等待:除了在调试时,尽量不要在你的最终测试脚本中使用 time.sleep()
  4. 组合使用:在某些复杂场景下,可以组合使用,先用隐式等待设置一个全局的“底线”等待时间(10 秒),然后对于关键操作,再使用显式等待进行精确控制,但请注意,这可能会导致总等待时间超过预期。

一个综合示例

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 TimeoutException
driver = webdriver.Chrome()
driver.maximize_window()
try:
    driver.get("https://www.baidu.com")
    # 使用显式等待等待搜索框出现并可见
    search_box = WebDriverWait(driver, 10).until(
        EC.visibility_of_element_located((By.ID, "kw"))
    )
    # 使用显式等待等待“百度一下”按钮可点击
    search_button = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.ID, "su"))
    )
    # 执行操作
    search_box.send_keys("Selenium 等待")
    search_button.click()
    # 使用显式等待等待新的页面标题变化
    WebDriverWait(driver, 10).until(
        EC.title_contains("Selenium 等待")
    )
    print("页面标题已更新,搜索成功!")
    # 获取结果并打印
    results = WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".result h3"))
    )
    print(f"找到 {len(results)} 个搜索结果。")
    for i, result in enumerate(results):
        print(f"结果 {i+1}: {result.text}")
except TimeoutException:
    print("等待超时!元素未在指定时间内出现或达到条件。")
except Exception as e:
    print(f"发生未知错误: {e}")
finally:
    # 无论成功与否,最后都关闭浏览器
    driver.quit()

通过遵循这些原则,你的 Selenium 脚本将变得更加健壮和可靠。

分享:
扫描分享到社交APP
上一篇
下一篇