pthon自动化测试:UI测试(二)
selenium的等待机制
好的,我们来深入探讨 Selenium 中至关重要的一环——等待机制。
这是区分一个自动化脚本是“玩具”还是“工具”的关键。如果不加等待,你的脚本会因为页面加载速度跟不上代码执行速度而到处报错;但如果等待不当,又会让你的脚本运行缓慢,效率低下。
为什么需要等待?
在 Web 应用中,页面元素的加载往往是异步的。这意味着:
- HTML 结构 先加载完毕。
- JavaScript、CSS、图片 等资源随后异步加载。
- AJAX/Fetch 请求会在页面加载完成后,动态地更新页面内容(如显示搜索结果、加载评论)。
你的 Selenium 脚本执行得非常快。如果没有等待,可能会出现以下情况:
- 代码试图点击一个按钮,但这个按钮的 HTML 还没被加载到 DOM 树中。→ 报错:
NoSuchElementException - 代码试图获取一个元素的文本,但这个文本是通过 AJAX 请求后才填充进去的。→ 获取到空值。
- 代码试图点击一个还在动画中的菜单项。→ 点击无效或报错。
因此,我们必须引入等待机制,让脚本在执行下一步操作前,“耐心地”等待某个条件成立。
Selenium 的三种等待机制
Selenium 提供了三种等待方式,我们按照推荐度从高到低介绍。
1. 强制等待 (Implicit Wait) - time.sleep()【不推荐用于正式脚本】
-
原理:让当前执行的线程休眠(暂停)一个固定的时间。
-
实现:使用 Python 的内置库
time。import time time.sleep(10) # 强制等待10秒 -
优点:
- 简单粗暴,易于理解和调试。
-
致命缺点:
- 极其低效和不稳定:无论元素是否已经加载好,脚本都必须死等满设定的时间。如果网络快,元素3秒就出来了,你却白等了7秒。如果网络慢,元素15秒才出来,你等10秒后依然会报错。
-
结论:仅可用于调试目的,或者在某些极端特殊、无法用其他方式处理的场景下临时使用。永远不要在你的正式自动化脚本中使用它。
2. 隐式等待 (Implicit Wait) - driver.implicitly_wait()【有一定用处,但有局限】
-
原理:设置一个全局性的等待超时时间。Selenium 会在查找元素时(调用
find_element方法),轮询 DOM 一段时间,直到元素被找到或超时。一旦设置,它对整个 WebDriver 生命周期内的所有find_element和find_elements调用都生效。 -
实现:
from selenium import webdriver driver = webdriver.Chrome() # 在整个会话中,设置隐式等待时间为10秒 driver.implicitly_wait(10) driver.get("https://www.example.com") # 即使这个元素需要5秒才加载出来,隐式等待也会让它最多等10秒 my_element = driver.find_element(By.ID, "my-dynamic-element") -
优点:
- 一次设置,全局生效,减少了代码的重复。
-
缺点和局限性:
- 只针对元素查找:它只对
find_element系列方法有效。对于等待元素的可见性、可点击性、URL变化、alert出现等其他复杂条件,它无能为力。 - 不够灵活:全局超时时间是一把双刃剑。如果一个页面大部分元素很快,但某一个特定元素总是很慢,你不得不把全局时间设得很长,这会影响脚本的整体执行效率。
- 行为混淆:如果在设置隐式等待后,又使用了显式等待,它们的超时时间可能会叠加,导致难以预测的等待时长。
- 只针对元素查找:它只对
-
结论:可以作为项目的保底策略设置一个较小的值(如2-5秒),但绝不能依赖它来处理复杂的动态加载场景。
3. 显式等待 (Explicit Wait) - WebDriverWait【强烈推荐,最佳实践】
-
原理:在代码的特定位置,为某个特定条件设置一个等待。它让脚本“聪明地”等待,直到某个条件满足或超时。这是最灵活、最强大的等待方式。
-
核心组件:
WebDriverWait类:用于设置超时时间和轮询频率。expected_conditions类(通常简写为EC):提供了一系列预定义好的、常用的等待条件。- 你的代码逻辑:告诉
WebDriverWait要等待什么条件。
-
实现:
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") try: # 声明一个显式等待,最多等10秒,每0.5秒检查一次条件 wait = WebDriverWait(driver, timeout=10, poll_frequency=0.5) # 等待,直到某个元素出现在DOM中并且可见 element = wait.until( EC.visibility_of_element_located((By.ID, "my-dynamic-element")) ) # 一旦条件满足,立刻执行下面的代码,不会继续等待剩下的时间 element.click() except TimeoutException: print("等待超时,元素仍未出现!") finally: driver.quit() -
常用
expected_conditions(EC) 条件:- 元素定位相关:
EC.presence_of_element_located(locator): 等待元素存在于DOM中(不一定可见)。EC.visibility_of_element_located(locator): 等待元素存在于DOM中并且可见(宽高不为0)。【最常用】EC.element_to_be_clickable(locator): 等待元素可见且启用(不是disabled状态)。【点击前必备】
- 页面状态相关:
EC.url_contains("keyword"): 等待URL包含特定字符串。EC.title_is("Expected Title"): 等待页面标题完全等于期望值。EC.title_contains("Part of Title"): 等待页面标题包含特定字符串。
- 其他常用:
EC.invisibility_of_element_located(locator): 等待元素不可见或消失(常用于等待加载动画消失)。EC.alert_is_present(): 等待 alert/confirm/prompt 弹窗出现。
- 元素定位相关:
-
优点:
- 高效:只在需要时等待,条件满足后立即执行,不浪费一毫秒。
- 灵活强大:可以等待任何你能想到的条件。
- 可读性强:代码清晰地表达了“我在这里等待某个特定事件发生”。
-
结论:这是编写健壮、高效 Selenium 脚本的首选和标准做法。
总结与实践建议
| 等待类型 | 原理 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
强制等待 (time.sleep()) |
固定休眠 | 简单 | 效率极低,不稳定 | ⭐ (仅用于调试) |
隐式等待 (implicitly_wait()) |
全局元素查找等待 | 一次设置,全局生效 | 不灵活,仅对查找元素有效 | ⭐⭐⭐ (可作保底) |
显式等待 (WebDriverWait) |
针对特定条件的智能等待 | 高效、灵活、强大、可读性好 | 需要编写更多代码 | ⭐⭐⭐⭐⭐ (首选) |
最佳实践黄金法则:
- 摒弃
time.sleep():将它从你的生产代码中彻底删除。 - 慎用隐式等待:可以设置一个很短的全局隐式等待(如2秒)作为最后一道防线,但不要依赖它。
- 拥抱显式等待:在任何需要与页面元素交互(尤其是动态加载的元素)的地方,毫不犹豫地使用
WebDriverWait+expected_conditions。 - 组合使用:在实际项目中,通常会将全局隐式等待和局部显式等待结合使用,但必须对它们的行为和潜在冲突有清晰的认识。对于严肃的项目,建议只使用显式等待,以获得最可控、最清晰的行为。
记住:一个好的自动化工程师和一个普通的“脚本小子”的区别,就在于是否会正确使用等待机制。
selenium的浏览器窗口和导航控制
好的,我们来详细讲解 Selenium 中如何控制和操作浏览器窗口以及进行页面导航。这部分技能对于处理多窗口应用、页面跳转和数据核对至关重要。
我们将操作分为两大类:浏览器窗口控制 和 浏览器导航控制。
1. 浏览器窗口控制 (Window Control)
这类操作用于改变浏览器本身的形态和行为,如大小、位置,以及处理多窗口/标签页。
设置和获取窗口大小
-
driver.maximize_window()-
作用:将浏览器窗口最大化。这在测试开始时是一个非常好的习惯,可以确保所有元素都能在视口中显示,避免因窗口过小导致的布局错乱或元素不可见。
-
示例:
driver.get("https://www.baidu.com") driver.maximize_window() # 打开网页后立即最大化
-
-
driver.minimize_window()- 作用:将浏览器窗口最小化。在某些特殊场景下可能会用到。
-
driver.set_window_size(width, height)-
作用:将浏览器窗口设置为指定的宽度和高度(单位:像素)。这对于测试响应式布局在不同屏幕尺寸下的表现非常有用。
-
示例:模拟手机竖屏
# 设置一个较小的尺寸,比如 iPhone SE 的尺寸 driver.set_window_size(375, 667) -
示例:模拟平板电脑横屏
driver.set_window_size(1024, 768)
-
-
driver.get_window_size()-
作用:获取当前浏览器窗口的尺寸,返回一个字典,如
{'width': 1280, 'height': 800}。 -
示例:
size = driver.get_window_size() print(f"当前窗口宽度: {size['width']}, 高度: {size['height']}")
-
设置和获取窗口位置
-
driver.set_window_position(x, y)-
作用:移动浏览器窗口到屏幕的特定坐标位置(屏幕左上角为原点
(0, 0))。 -
示例:将窗口移动到屏幕左上角
driver.set_window_position(0, 0)
-
-
driver.get_window_position()-
作用:获取当前浏览器窗口在屏幕上的坐标位置。
-
示例:
position = driver.get_window_position() print(f"当前窗口 X 坐标: {position['x']}, Y 坐标: {position['y']}")
-
处理多窗口和多标签页 (Multi-Window/Tab Handling)
这是 Web 应用中常见的场景,比如点击一个链接会在新标签页中打开。Selenium 默认将所有窗口的控制权都交给同一个 driver实例,你需要主动切换。
核心概念:每个窗口都有一个唯一的 Window Handle(窗口句柄),它是一个字符串标识符。
核心方法:
driver.current_window_handle: 获取当前正在控制的窗口的句柄。driver.window_handles: 获取当前会话中所有打开的窗口的句柄列表(按打开顺序排列)。driver.switch_to.window(handle): 将控制权切换到指定的窗口句柄。
处理流程(经典四步法):
- 记录原始句柄:在打开新窗口前,先保存当前窗口的句柄,以便后续切回去。
- 执行打开新窗口的操作:比如点击一个会
target="_blank"的链接。 - 获取所有句柄并切换:等待新窗口出现,然后获取所有句柄,遍历找到新的那个并进行切换。
- 在新窗口操作并切回:完成操作后,切换回主窗口继续其他任务。
代码示例:点击一个在新标签页打开的链接
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome()
driver.get("https://www.example.com")
driver.maximize_window()
# 1. 记录原始窗口句柄
main_window_handle = driver.current_window_handle
print(f"主窗口句柄: {main_window_handle}")
# 假设页面上有一个链接,点击它会在新标签页打开
# <a href="https://news.example.com" target="_blank">最新资讯</a>
open_news_link = driver.find_element(By.LINK_TEXT, "最新资讯")
open_news_link.click()
# 2. 等待一下,确保新窗口有足够时间打开
time.sleep(2) # 实际项目中应使用显式等待
# 3. 获取所有窗口句柄
all_window_handles = driver.window_handles
print(f"所有窗口句柄: {all_window_handles}")
# 4. 遍历句柄,找到新窗口并切换过去
new_window_handle = None
for handle in all_window_handles:
if handle != main_window_handle:
new_window_handle = handle
break
if new_window_handle:
driver.switch_to.window(new_window_handle)
print(f"已切换到新窗口,当前URL: {driver.current_url}")
# ... 在这里对新窗口进行操作 ...
# 比如获取标题
print(driver.title)
# 5. 操作完毕后,切换回主窗口
driver.switch_to.window(main_window_handle)
print(f"已切换回主窗口,当前URL: {driver.current_url}")
driver.quit()
2. 浏览器导航控制 (Navigation Control)
这类操作模拟用户在浏览器顶部地址栏和工具栏进行的操作,如前进、后退、刷新和获取地址。
页面导航 (Navigating History)
-
driver.back()-
作用:模拟点击浏览器的后退按钮。
-
示例:
driver.get("https://www.page-a.com") # ... 在 page-a.com 上进行一些操作 ... driver.get("https://www.page-b.com") # 跳转到 page-b.com # ... 在 page-b.com 上进行一些操作 ... driver.back() # 回到 page-a.com
-
-
driver.forward()-
作用:模拟点击浏览器的前进按钮。必须在执行过
back()之后才有意义。 -
示例:
driver.back() # 从 B 退回到 A # ... 在 page-a.com 上... driver.forward() # 从 A 前进到 B
-
-
driver.refresh()-
作用:模拟点击浏览器的刷新按钮,重新加载当前页面。
-
示例:
driver.refresh()
-
获取页面信息
-
driver.current_url-
作用:获取浏览器当前页面的 URL 地址。这是非常重要的一个属性,常用于断言页面是否跳转正确。
-
示例:
driver.get("https://www.baidu.com") # 用户输入搜索词并提交后,URL会改变 assert "wd=selenium" in driver.current_url.lower()
-
-
driver.title-
作用:获取浏览器当前页面的 标题(即 HTML
<title>标签的内容)。同样常用于断言。 -
示例:
driver.get("https://www.google.com") print(f"当前页面标题是: {driver.title}") # 输出: 当前页面标题是: Google assert "Google" in driver.title
-
总结与最佳实践
| 类别 | 方法/属性 | 核心作用 | 典型应用场景 |
|---|---|---|---|
| 窗口控制 | .maximize_window() |
最大化窗口 | 测试初始化,保证元素可见性 |
.set_window_size(w, h) |
设置窗口尺寸 | 响应式布局测试、移动端模拟 | |
.window_handles |
获取所有窗口句柄 | 处理多窗口/标签页的前提 | |
.switch_to.window(h) |
切换窗口焦点 | 在多窗口间进行操作和切换 | |
| 导航控制 | .get(url) |
打开新URL | 最基本的页面跳转 |
.back()/ .forward() |
浏览器后退/前进 | 测试浏览器历史记录功能 | |
.refresh() |
刷新当前页 | 测试页面刷新后的状态保持 | |
.current_url |
获取当前URL | 断言页面是否成功跳转 | |
.title |
获取页面标题 | 断言页面是否正确加载 |
最佳实践建议:
- 测试开始时最大化窗口:养成
get(url)之后立即maximize_window()的习惯。 - 使用
current_url和title进行断言:不要仅仅依靠页面元素是否存在来判断页面加载成功。一个更健壮的方法是先判断 URL 和标题是否符合预期,再进行元素操作。 - 谨慎使用
time.sleep()切换窗口:在上面的示例中我们使用了sleep,但在真实项目中,应该使用显式等待来等待新窗口句柄的出现,例如等待len(driver.window_handles) > 1。 - 切换后务必切回:在完成新窗口的操作后,记得切换回主窗口,否则后续的
find_element等操作都会在错误的窗口中进行,导致失败。可以将主窗口句柄保存在一个变量中,方便随时切换回来。

浙公网安备 33010602011771号