pthon自动化测试:UI测试(二)

selenium的等待机制

好的,我们来深入探讨 Selenium 中至关重要的一环——等待机制

这是区分一个自动化脚本是“玩具”还是“工具”的关键。如果不加等待,你的脚本会因为页面加载速度跟不上代码执行速度而到处报错;但如果等待不当,又会让你的脚本运行缓慢,效率低下

为什么需要等待?

在 Web 应用中,页面元素的加载往往是异步的。这意味着:

  1. HTML 结构 先加载完毕。
  2. JavaScript、CSS、图片 等资源随后异步加载。
  3. 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_elementfind_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")
    
  • 优点

    • 一次设置,全局生效,减少了代码的重复。
  • 缺点和局限性

    1. 只针对元素查找:它只对 find_element系列方法有效。对于等待元素的可见性、可点击性、URL变化、alert出现等其他复杂条件,它无能为力。
    2. 不够灵活:全局超时时间是一把双刃剑。如果一个页面大部分元素很快,但某一个特定元素总是很慢,你不得不把全局时间设得很长,这会影响脚本的整体执行效率。
    3. 行为混淆:如果在设置隐式等待后,又使用了显式等待,它们的超时时间可能会叠加,导致难以预测的等待时长。
  • 结论:可以作为项目的保底策略设置一个较小的值(如2-5秒),但绝不能依赖它来处理复杂的动态加载场景

3. 显式等待 (Explicit Wait) - WebDriverWait【强烈推荐,最佳实践】

  • 原理:在代码的特定位置,为某个特定条件设置一个等待。它让脚本“聪明地”等待,直到某个条件满足或超时。这是最灵活、最强大的等待方式。

  • 核心组件

    1. WebDriverWait类:用于设置超时时间和轮询频率。
    2. expected_conditions类(通常简写为 EC):提供了一系列预定义好的、常用的等待条件。
    3. 你的代码逻辑:告诉 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) 针对特定条件的智能等待 高效、灵活、强大、可读性好 需要编写更多代码 ⭐⭐⭐⭐⭐ (首选)

最佳实践黄金法则:

  1. 摒弃 time.sleep():将它从你的生产代码中彻底删除。
  2. 慎用隐式等待:可以设置一个很短的全局隐式等待(如2秒)作为最后一道防线,但不要依赖它。
  3. 拥抱显式等待:在任何需要与页面元素交互(尤其是动态加载的元素)的地方,毫不犹豫地使用 WebDriverWait+ expected_conditions
  4. 组合使用:在实际项目中,通常会将全局隐式等待局部显式等待结合使用,但必须对它们的行为和潜在冲突有清晰的认识。对于严肃的项目,建议只使用显式等待,以获得最可控、最清晰的行为。

记住:一个好的自动化工程师和一个普通的“脚本小子”的区别,就在于是否会正确使用等待机制。

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): 将控制权切换到指定的窗口句柄。

处理流程(经典四步法):

  1. 记录原始句柄:在打开新窗口前,先保存当前窗口的句柄,以便后续切回去。
  2. 执行打开新窗口的操作:比如点击一个会 target="_blank"的链接。
  3. 获取所有句柄并切换:等待新窗口出现,然后获取所有句柄,遍历找到新的那个并进行切换。
  4. 在新窗口操作并切回:完成操作后,切换回主窗口继续其他任务。

代码示例:点击一个在新标签页打开的链接

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 获取页面标题 断言页面是否正确加载

最佳实践建议:

  1. 测试开始时最大化窗口:养成 get(url)之后立即 maximize_window()的习惯。
  2. 使用 current_urltitle进行断言:不要仅仅依靠页面元素是否存在来判断页面加载成功。一个更健壮的方法是先判断 URL 和标题是否符合预期,再进行元素操作。
  3. 谨慎使用 time.sleep()切换窗口:在上面的示例中我们使用了 sleep,但在真实项目中,应该使用显式等待来等待新窗口句柄的出现,例如等待 len(driver.window_handles) > 1
  4. 切换后务必切回:在完成新窗口的操作后,记得切换回主窗口,否则后续的 find_element等操作都会在错误的窗口中进行,导致失败。可以将主窗口句柄保存在一个变量中,方便随时切换回来。
posted @ 2025-12-02 18:49  GDms  阅读(21)  评论(0)    收藏  举报