欢迎光临
我们一直在努力

从零到一:基于Python 3与Selenium 3的Web自动化测试实战完全指南

文章目录

    • 引言:为什么是自动化测试,为什么是Selenium + Python?
    • 第一篇:磨刀不误砍柴工——环境与基础篇
      • 第1章 自动化测试全景图与Selenium的崛起
        • 1.1 什么是自动化测试?
        • 1.2 自动化测试的分类
        • 1.3 自动化测试并非银弹:适用条件
        • 1.4 为什么选择Selenium?
      • 第2章 初探自动化:Selenium IDE——录制与回放
        • 2.1 Selenium IDE是什么?
        • 2.2 IDE的局限与定位
      • 第3章 工欲善其事,必先利其器:环境搭建全攻略
        • 3.1 Python 3安装
        • 3.2 安装Selenium库
        • 3.3 安装浏览器驱动
        • 3.4 选择你的代码编辑器(IDE)
      • 第4章 前端技术扫盲:HTML, CSS与JavaScript
        • 4.1 HTML:网页的骨架
        • 4.2 CSS:网页的皮肤
        • 4.3 JavaScript:网页的神经
      • 第5章 核心中的核心:Selenium八大元素定位
        • 5.1 id定位
        • 5.2 name定位
        • 5.3 class_name定位
        • 5.4 tag_name定位
        • 5.5 link_text 与 partial_link_text 定位
        • 5.6 CSS Selector定位
        • 5.7 XPath定位
      • 第6章 与元素共舞:Selenium常用方法与进阶操作
        • 6.1 基础操作
        • 6.2 浏览器操作
        • 6.3 处理下拉框(Select)
        • 6.4 处理弹窗(Alert/Confirm/Prompt)
        • 6.5 处理框架(iframe)
        • 6.6 鼠标与键盘事件(ActionChains)
        • 6.7 执行JavaScript
        • 6.8 处理Cookie
        • 6.9 多窗口/标签页切换
      • 第7章 直面挑战:WebDriver进阶应用与疑难杂症处理
        • 7.1 处理“时间等待”——告别`time.sleep`
        • 7.2 处理文件上传
        • 7.3 处理滑块验证码
        • 7.4 处理普通验证码
        • 7.5 页面截图
    • 第二篇:真枪实弹——项目实战与框架优化篇
      • 第8章 项目实战:携程火车票预订自动化
        • 8.1 第一版:直来直去的“流水账”脚本
      • 第9章 代码优化与项目重构
        • 9.1 重构一:分离元素定位与操作
        • 9.2 重构二:引入配置文件,管理测试数据
        • 9.3 重构三:封装常用操作和页面动作
        • 9.4 重构四:创建页面对象(Page Object)类
        • 9.5 重构五:编写业务测试用例
      • 第10章 数据驱动测试
        • 10.1 使用Python内置模块处理数据文件
        • 10.2 使用DDT (Data-Driven Tests) 库
        • 10.3 将数据驱动整合进我们的项目
      • 第11章 Page Object设计模式深度解析
        • 11.1 PO模式的核心思想
        • 11.2 PO模式的优点
        • 11.3 PO模式的常见项目结构
      • 第12章 行为驱动开发
    • 第三篇:更上一层楼——平台集成与未来展望
      • 第13章 自动化测试平台与持续集成
        • 13.1 版本控制:Git
        • 13.2 持续集成:Jenkins
        • 13.3 容器化:Docker
      • 第14章 总结与展望:你的自动化测试之路

引言:为什么是自动化测试,为什么是Selenium + Python?

在当今软件迭代速度以“天”甚至“小时”计的时代,质量与效率成为了企业生存和发展的生命线。想象一下,每一次版本发布,测试团队都需要对成百上千的功能点进行重复的手工验证,不仅耗时费力、容易出错,更严重拖慢了产品交付的步伐。正是在这样的背景下,自动化测试从一种“锦上添花”的技术,演变为支撑现代软件开发的“核心基础设施”。

在自动化测试的广阔天地中,Web UI自动化测试始终占据着独特而重要的位置。尽管测试左移、API测试兴起,但最终用户感知和交互的界面层验证,其重要性从未减弱。一个按钮点击不了、一个表单提交失败,对用户体验的伤害是直接而致命的。因此,掌握一套稳定、高效、可维护的Web UI自动化测试技术,是每一位测试工程师向更高阶发展的必经之路。

而在众多Web自动化测试工具中,Selenium无疑是皇冠上的明珠。自2004年诞生以来,它从最初的一个JavaScript小库,成长为如今支持几乎所有主流浏览器、几乎所有主流编程语言的事实行业标准。它的核心优势在于:

  • 开源免费:零成本获取,拥有活跃的社区和海量资源。
  • 跨浏览器:完美支持Chrome、Firefox、Edge、Safari等,确保你的应用在任何环境下表现一致。
  • 多语言绑定:Java, Python, C#, JavaScript, Ruby… 你可以用自己最熟悉的语言来编写测试脚本。
  • 强大的生态系统:围绕Selenium WebDriver,衍生出了Selenium Grid(分布式测试)、Selenium IDE(录制回放)等强大工具,以及Page Object、数据驱动等成熟的设计模式。

准备好了吗?让我们启动浏览器,开启这段从“测试菜鸟”到“自动化高手”的奇幻之旅。


第一篇:磨刀不误砍柴工——环境与基础篇

第1章 自动化测试全景图与Selenium的崛起

在挥动Selenium这把“利剑”之前,我们必须先了解我们要面对的“战场”——自动化测试本身。

1.1 什么是自动化测试?

简单来说,自动化测试就是利用软件工具或脚本,部分或全部替代人工,来执行测试活动、比较实际结果与预期结果的过程。它的核心价值在于:

  • 解放重复劳动:将测试人员从枯燥、重复的回归测试中解放出来,从事更有价值的探索性测试、测试设计等工作。
  • 提升效率与覆盖率:可以在无人值守时(如夜间)运行成千上万个测试用例,快速获得反馈,极大地提高了测试效率和覆盖率。
  • 保证一致性:机器执行避免了人为疏忽,确保了每次测试执行步骤和验证点的一致性。
  • 支持持续集成/持续部署(CI/CD):自动化测试是CI/CD流水线中快速验证代码质量的基石。
1.2 自动化测试的分类

从不同维度看,自动化测试有多种分类方式:

  • 按测试金字塔分层:
    • 单元自动化测试:针对代码中的函数、方法或类进行测试,主要由开发人员完成。
    • 接口自动化测试:测试系统组件间接口(API)的请求与响应,稳定性高,回报率高,是目前自动化测试的主力。
    • UI自动化测试:模拟真实用户操作图形界面进行验证,更贴近用户感知,但维护成本相对较高。这正是Selenium的主战场。
  • 按测试目的:
    • 功能自动化测试:验证软件功能是否正确。
    • 性能自动化测试:验证系统在高负载下的表现。
    • 安全自动化测试:自动化地进行安全漏洞扫描。
1.3 自动化测试并非银弹:适用条件

在欢呼自动化测试带来的便利时,我们必须清醒地认识到,不是所有项目都适合开展UI自动化测试。启动前请评估:

  • 项目周期长,功能相对稳定:频繁变动的UI会导致脚本维护成本激增。
  • 具备足够的资源投入:自动化测试前期在人力、时间、技能学习上投入较大。
  • 测试用例可自动化且重复执行价值高:那些只执行一两次的用例,自动化的性价比很低。
  • 团队具备一定的技术能力:测试人员需要掌握编程和脚本开发能力。
  • 1.4 为什么选择Selenium?

    市场上自动化测试工具很多,如UFT(QTP)、Robot Framework等。Selenium能脱颖而出,源于其开源、跨平台、跨浏览器、多语言支持的天然基因,以及背后庞大的开源社区力量。它几乎成为了Web UI自动化测试的“普通话”。

    Selenium的进化简史:

    • Selenium 1.0 (Selenium RC):核心是一个JavaScript程序(Selenium Core),通过一个代理服务器来“欺骗”浏览器绕过同源策略,实现控制。架构复杂,速度慢。
    • Selenium 2.0 (Selenium WebDriver):革命性的版本!引入了WebDriver,直接通过各浏览器原生支持的方式操作浏览器,抛弃了JavaScript代理,速度更快,更稳定。Selenium RC被逐步废弃。
    • Selenium 3.0:彻底移除了Selenium RC,全面拥抱WebDriver,并开始遵循W3C标准,驱动与浏览器的对接更规范。本指南基于Selenium 3。
    • Selenium 4.0: (前瞻) 提供了更多新的API,如相对定位器、对CDP协议的支持等,进一步增强能力。

    第2章 初探自动化:Selenium IDE——录制与回放

    对于完全零基础的初学者,直接从代码开始可能会让人望而生畏。Selenium提供了一个很好的“缓坡”入门工具——Selenium IDE。

    2.1 Selenium IDE是什么?

    它是一个浏览器插件(支持Chrome和Firefox),提供了录制、回放、编辑测试脚本的功能。你像正常操作浏览器一样点点按按,IDE会把你所有操作录制下来,生成可重复执行的测试脚本。

    主要功能界面:

    • 录制/停止按钮:控制录制的开始与结束。
    • 命令、目标、值:录制的每一个操作(如click, type)都会被解析成这三栏。
    • 脚本保存与导出:可以将录制好的脚本保存为.side文件,或者导出为多种编程语言的代码,如Python、Java、C#等。这是IDE一个非常强大的功能,可以快速生成脚本骨架。

    一个简单的录制示例:

  • 打开Selenium IDE,点击录制。
  • 在浏览器地址栏输入 www.baidu.com 并访问。
  • 在搜索框输入“Selenium”。
  • 点击“百度一下”。
  • 停止录制。 你会看到IDE中记录了一系列命令:open、type、click等。你可以直接回放,脚本会自动重复刚才的操作。
  • 2.2 IDE的局限与定位

    Selenium IDE非常适合:

    • 快速原型验证:快速验证某个流程是否可自动化。
    • 生成脚本初稿:通过导出功能,获得基础脚本,再进行增强。
    • 自动化测试概念学习。

    但它不适合大型、长期的自动化项目,因为:

    • 脚本脆弱:录制的脚本严重依赖页面元素的精准位置(如XPath索引),页面结构微调就可能导致脚本失败。
    • 缺乏编程逻辑:难以实现条件判断、循环、数据驱动等复杂逻辑。
    • 不易集成:难以与版本控制系统(Git)、持续集成工具(Jenkins)等集成。

    结论:Selenium IDE是绝佳的“启蒙老师”和“快速原型工具”,但要构建健壮的企业级自动化测试框架,我们必须步入“代码的世界”。

    第3章 工欲善其事,必先利其器:环境搭建全攻略

    现在,让我们告别图形化界面,真正开始用代码驾驭浏览器。第一步,就是搭建开发环境。

    3.1 Python 3安装

    访问Python官网(https://www.python.org)下载对应操作系统(Windows/macOS/Linux)的安装包。务必注意勾选“Add Python 3.x to PATH”,这将把Python添加到系统环境变量,方便在命令行任何位置调用。

    安装完成后,打开命令行(CMD、Terminal),输入 python –version,如果显示Python 3.x.x,则安装成功。

    3.2 安装Selenium库

    Python通过pip包管理工具来安装第三方库。在命令行中执行:

    pip install selenium

    这条命令会从Python官方的软件仓库(PyPI)下载并安装最新稳定版的Selenium。如果你身处国内,觉得下载速度慢,可以使用国内的镜像源,例如清华源:

    pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple

    3.3 安装浏览器驱动

    Selenium WebDriver需要通过一个特定的“驱动”文件来与浏览器对话。每个浏览器都需要自己的驱动。

    • Chrome驱动 (ChromeDriver):

    • 查看你Chrome浏览器的版本(帮助 -> 关于Google Chrome)。
    • 访问 https://sites.google.com/a/chromium.org/chromedriver/downloads,下载与你的Chrome版本匹配的驱动。
    • 将下载的chromedriver.exe文件:
      • 推荐:放在你的Python安装目录下(或已添加到PATH的任意目录),这样在代码中只需webdriver.Chrome()即可。
      • 或:放在项目目录,在代码中指定路径:webdriver.Chrome(executable_path=‘./chromedriver’)
    • Firefox驱动 (geckodriver): 访问 https://github.com/mozilla/geckodriver/releases 下载,放置方式同ChromeDriver。

    • 其他浏览器:Edge、IE等均有对应驱动,可在Selenium官网找到链接。

    3.4 选择你的代码编辑器(IDE)

    一个强大的代码编辑器能极大提升开发效率。推荐:

    • PyCharm (社区版免费):功能最全,专为Python设计,智能提示、调试、项目管理等功能一流,非常适合大型项目。
    • Visual Studio Code (免费):轻量、快速,通过安装Python插件也能获得近乎IDE的体验,资源占用小。

    环境验证:创建一个名为first_script.py的文件,写入以下代码:

    # first_script.py
    from selenium import webdriver
    import time

    # 创建Chrome浏览器驱动对象
    driver = webdriver.Chrome() # 如果驱动不在PATH,需指定路径
    # driver = webdriver.Chrome(executable_path=‘你的路径/chromedriver‘)

    # 访问百度
    driver.get("https://www.baidu.com")

    # 等待3秒,方便观察
    time.sleep(3)

    # 关闭浏览器
    driver.quit()
    print("你的第一个Selenium脚本运行成功!")

    运行此脚本,如果成功弹出一个Chrome浏览器并打开百度首页,随后关闭,恭喜你,环境搭建成功!

    第4章 前端技术扫盲:HTML, CSS与JavaScript

    要定位和操作页面元素,你必须对它们赖以生存的“土壤”——前端三剑客有最基本的了解。别担心,我们不需要成为前端专家,只需能看懂即可。

    4.1 HTML:网页的骨架

    HTML(超文本标记语言)用各种“标签”来定义网页的结构和内容。

    • 标签:用尖括号包围,如<html>, <body>, <div>, <a>。通常成对出现:<开始标签>内容</结束标签>。
    • 属性:为标签提供额外信息,位于开始标签内。如<input id=“kw” name=“wd” class=“s_ipt”>。id、name、class正是我们定位元素最常用的属性!
    • 常见元素:
      • <input>:输入框、单选框、复选框。
      • <a>:超链接。
      • <button>:按钮。
      • <select>:下拉列表。
      • <img>:图片。
      • <iframe>:内联框架(一个页面中嵌套另一个页面)。

    查看元素:在浏览器页面按F12打开开发者工具,使用“元素检查”功能(箭头图标),点击页面上任意部位,即可看到其对应的HTML代码。这是自动化测试的“显微镜”。

    4.2 CSS:网页的皮肤

    CSS(层叠样式表)控制HTML元素的显示样式(颜色、大小、位置等)。

    • 选择器:CSS通过“选择器”来定位HTML元素并施加样式。例如:
      • #kw:选择id=“kw”的元素。
      • .s_ipt:选择class=“s_ipt”的元素。
      • input[name=‘wd’]:选择name属性为wd的input元素。 CSS选择器的语法,可以直接被Selenium的CSS定位方式使用!
    4.3 JavaScript:网页的神经

    JavaScript让网页“动”起来,实现交互逻辑。Selenium可以通过执行JavaScript代码来直接操作页面,这在处理一些WebDriver原生API难以处理的场景时(如滚动页面、修改元素属性)非常有用。

    总结:把网页看作一栋房子,HTML是钢筋水泥的骨架,CSS是内外装修的皮肤,JavaScript则是水电灯光控制系统。我们(Selenium)作为操控者,需要看懂建筑图纸(HTML),知道开关在哪(元素定位),才能控制这栋房子。

    第5章 核心中的核心:Selenium八大元素定位

    元素定位是UI自动化的基石。如果找不到元素,后续的所有点击、输入都无从谈起。Selenium提供了8种强大的定位方式。

    定位原则:优先级通常为 id > name > class > link text > partial link text > css selector > xpath。id和name通常是唯一的,最稳定。tag_name通常用于定位一组元素。

    让我们以百度首页(https://www.baidu.com)为例,逐一攻克。

    5.1 id定位

    id属性在HTML中应该是唯一的。通过find_element_by_id方法定位。

    driver.find_element_by_id(“kw”).send_keys(“Python”) # 在搜索框输入

    5.2 name定位

    name属性也常用于表单元素。通过find_element_by_name方法定位。

    driver.find_element_by_name(“wd”).send_keys(“Selenium”)

    5.3 class_name定位

    class属性用于定义CSS样式,一个元素可以有多个class。通过find_element_by_class_name方法定位。注意:如果class包含空格(表示多个类),只能使用其中一个。

    driver.find_element_by_class_name(“s_ipt”).send_keys(“自动化测试”)

    5.4 tag_name定位

    通过标签名定位,如input, div, a。通常用于获取一组元素。通过find_element_by_tag_name方法定位单个,find_elements_by_tag_name定位多个。

    inputs = driver.find_elements_by_tag_name(input)
    print(f“页面共有{len(inputs)}input元素”)

    5.5 link_text 与 partial_link_text 定位

    专门用于定位超链接(<a>标签)。

    • link_text:精确匹配链接的全部文本。
    • partial_link_text:模糊匹配链接文本的一部分。

    driver.find_element_by_link_text(“新闻”).click() # 点击“新闻”链接
    driver.find_element_by_partial_link_text(“新”).click() # 同样点击“新闻”链接

    5.6 CSS Selector定位

    功能极为强大且高效的定位方式。它直接使用了CSS选择器的语法。

    • 通过id:#id值
    • 通过class:.class值 (多个类用.连接,如.class1.class2)
    • 通过属性:[属性名=‘属性值’]
    • 组合:标签名[属性名=‘属性值’]
    • 层级:父元素>子元素 或 祖先元素 后代元素

    driver.find_element_by_css_selector(#kw”).send_keys(“CSS”) # id
    driver.find_element_by_css_selector(.s_ipt”).send_keys(“CSS”) # class
    driver.find_element_by_css_selector(input[name=‘wd’]).send_keys(“CSS”) # 属性
    driver.find_element_by_css_selector(“form#form>span>input”).send_keys(“CSS”) # 层级

    为什么推荐CSS? 通常比XPath解析速度更快,语法更简洁。

    5.7 XPath定位

    XML Path Language,一种在XML文档中查找信息的语言。由于HTML可以视为XML的一种实现,因此XPath也可用于定位HTML元素。功能最强大,几乎可以定位任何元素,但速度相对较慢。

    • 绝对路径:从根节点/html开始写,非常脆弱,不推荐。
    • 相对路径:从任意节点开始,常用//表示。
    • 基本语法:
      • //标签名[@属性名=‘属性值’]
      • 运算符:and, or, not()
      • 函数:text(), contains(), starts-with()

    driver.find_element_by_xpath(//input[@id=‘kw’]).send_keys(“XPath”) # 属性
    driver.find_element_by_xpath(//*[@id=‘kw’]).send_keys(“XPath”) # 用*匹配任意标签
    driver.find_element_by_xpath(//a[text()=‘新闻’]).click() # 文本匹配
    driver.find_element_by_xpath(//input[contains(@class, ‘s_ipt’)]).send_keys(“XPath”) # class包含
    driver.find_element_by_xpath(//input[startswith(@id, ‘k’)]).send_keys(“XPath”) # id以k开头

    什么时候用XPath? 当元素没有id、name、class,或者结构复杂,CSS难以表达时,XPath是终极武器。

    实战建议:多使用浏览器开发者工具的“复制”功能(在元素上右键->Copy->Copy selector / Copy XPath),可以快速获得元素的CSS或XPath表达式,作为你编写脚本的参考起点。但要小心自动生成的XPath可能过于依赖位置索引(如div[3]/div[2]),不够健壮,需手动优化。

    第6章 与元素共舞:Selenium常用方法与进阶操作

    定位到元素后,我们就可以对它进行各种操作,模拟用户的真实行为。

    6.1 基础操作
    • 点击:.click()
    • 输入/清空:.send_keys(“文本”) / .clear()
    • 获取文本/属性:.text / .get_attribute(“属性名”)
    • 判断状态:.is_displayed() (是否显示), .is_enabled() (是否可用), .is_selected() (是否被选中,用于复选框/单选框)

    search_box = driver.find_element_by_id(“kw”)
    search_box.send_keys(“Hello World”)
    print(search_box.get_attribute(“value”)) # 获取输入框的值
    search_box.clear()
    search_box.send_keys(“New Text”)
    driver.find_element_by_id(“su”).click() # 点击搜索按钮

    6.2 浏览器操作
    • driver.get(url):访问网页
    • driver.back() / driver.forward():后退/前进
    • driver.refresh():刷新
    • driver.title:获取页面标题
    • driver.current_url:获取当前URL
    • driver.maximize_window():窗口最大化
    • driver.quit():关闭浏览器并退出驱动(重要!每次脚本结束务必调用)
    6.3 处理下拉框(Select)

    对于HTML标准的下拉框<select>,Selenium提供了Select类,操作非常方便。

    from selenium.webdriver.support.select import Select

    select_element = driver.find_element_by_id(“city”)
    select_obj = Select(select_element)

    # 三种选择方式
    select_obj.select_by_index(1) # 通过索引(从0开始)
    select_obj.select_by_value(“shanghai”) # 通过option的value属性
    select_obj.select_by_visible_text(“上海”) # 通过显示的文本

    # 获取选项
    all_options = select_obj.options
    for option in all_options:
    print(option.text)

    6.4 处理弹窗(Alert/Confirm/Prompt)

    alert = driver.switch_to.alert # 切换到弹窗
    print(alert.text) # 获取弹窗文本
    alert.accept() # 点击“确定”
    # alert.dismiss() # 点击“取消”
    # alert.send_keys(“输入内容”) # 适用于Prompt

    6.5 处理框架(iframe)

    如果元素位于<iframe>或<frame>内,必须先切换到对应的框架,才能定位其中的元素。

    # 通过id/name切换
    driver.switch_to.frame(“login_frame”)
    # 通过元素对象切换
    iframe = driver.find_element_by_tag_name(“iframe”)
    driver.switch_to.frame(iframe)
    # 操作框架内的元素…
    driver.find_element_by_id(“u”).send_keys(“username”)

    # 操作完成后,切回主页面
    driver.switch_to.default_content()
    # 或切回父级框架
    driver.switch_to.parent_frame()

    6.6 鼠标与键盘事件(ActionChains)

    用于模拟复杂的用户交互,如鼠标悬停、双击、拖拽、右键、组合键等。

    from selenium.webdriver.common.action_chains import ActionChains
    from selenium.webdriver.common.keys import Keys

    element = driver.find_element_by_link_text(“设置”)
    # 鼠标悬停
    ActionChains(driver).move_to_element(element).perform()
    # 右键点击
    ActionChains(driver).context_click(element).perform()
    # 双击
    ActionChains(driver).double_click(element).perform()
    # 拖放元素A到元素B
    source = driver.find_element_by_id(“draggable”)
    target = driver.find_element_by_id(“droppable”)
    ActionChains(driver).drag_and_drop(source, target).perform()

    # 键盘操作:输入后按回车
    driver.find_element_by_id(“kw”).send_keys(“搜索词” + Keys.ENTER)
    # 全选 (Ctrl+A)
    driver.find_element_by_id(“kw”).send_keys(Keys.CONTROL, ‘a’)

    6.7 执行JavaScript

    当WebDriver API无法满足需求时,可以直接注入JavaScript代码执行。

    # 滚动到页面底部
    driver.execute_script(“window.scrollTo(0, document.body.scrollHeight);)
    # 滚动到指定元素
    element = driver.find_element_by_id(“footer”)
    driver.execute_script(“arguments[0].scrollIntoView();, element)
    # 修改元素属性
    driver.execute_script(“document.getElementById(‘kw’).value=‘JS赋值’;)
    # 点击被遮挡的元素
    driver.execute_script(“arguments[0].click();, element)

    6.8 处理Cookie

    可用于模拟登录状态等。

    # 获取所有cookie
    for cookie in driver.get_cookies():
    print(cookie)
    # 获取指定cookie
    value = driver.get_cookie(“session_id”)
    # 添加cookie (常用于登录状态保持)
    driver.add_cookie({‘name’: ‘my_cookie’, ‘value’: ‘my_value’, ‘domain’:.baidu.com’})
    # 删除所有cookie
    driver.delete_all_cookies()

    6.9 多窗口/标签页切换

    # 获取当前窗口句柄
    current_handle = driver.current_window_handle
    # 点击某个打开新窗口的链接
    driver.find_element_by_link_text(“新窗口”).click()
    # 获取所有窗口句柄
    all_handles = driver.window_handles
    # 切换到新窗口
    for handle in all_handles:
    if handle != current_handle:
    driver.switch_to.window(handle)
    break
    # 在新窗口操作…
    # 切回原窗口
    driver.switch_to.window(current_handle)

    第7章 直面挑战:WebDriver进阶应用与疑难杂症处理

    掌握了基本操作,我们来看看如何应对自动化测试中的一些“拦路虎”。

    7.1 处理“时间等待”——告别time.sleep

    随意使用time.sleep(秒数)会让脚本变得又慢又不可靠。Selenium提供了两种智能等待。

    • 隐式等待 (Implicit Wait):为driver设置一个全局的等待时间,在查找任何元素时,如果元素没有立即出现,WebDriver会轮询查找,直到超时或找到元素。只需设置一次。

      driver.implicitly_wait(10) # 单位:秒
      driver.get(...)
      # 在后续的find_element操作中,如果元素10秒内出现,则立即继续;否则抛出异常。

    • 显式等待 (Explicit Wait):更灵活、更精准。针对某个特定条件进行等待,直到条件满足或超时。需要用到WebDriverWait和expected_conditions。

      from selenium.webdriver.support.ui import WebDriverWait
      from selenium.webdriver.support import expected_conditions as EC
      from selenium.webdriver.common.by import By

      # 等待id为‘kw’的元素出现,最多等10秒
      element = WebDriverWait(driver, 10).until(
      EC.presence_of_element_located((By.ID, “kw”))
      )
      element.send_keys(“text”)

      # 其他常用条件
      EC.element_to_be_clickable((By.ID, “su”)) # 元素可点击
      EC.visibility_of_element_located((By.NAME, “wd”)) # 元素可见
      EC.title_contains(“百度”) # 标题包含
      EC.alert_is_present() # 出现弹窗

      最佳实践:以显式等待为主,隐式等待为辅(可设一个较短的全局时间,如5秒)。

    7.2 处理文件上传
    • 情况一:如果上传按钮是<input type=“file”>,可以直接send_keys文件路径。driver.find_element_by_name(“uploadFile”).send_keys(/Users/yourname/file.txt”)
    • 情况二:非input标签,需要借助系统级工具,如pyautogui(模拟键盘输入路径),但这会依赖操作系统和屏幕焦点,不够稳定,通常建议让开发改为input类型。
    7.3 处理滑块验证码

    思路:计算滑块需要拖动的距离,然后使用ActionChains的drag_and_drop_by_offset方法。

  • 获取滑块按钮和滑块背景图的元素。
  • 计算背景图的宽度(即总滑动距离)。
  • 执行拖拽动作。
  • slider = driver.find_element_by_id(“sliderButton”)
    track = driver.find_element_by_id(“sliderTrack”)
    # 假设需要拖拽整个背景图的宽度
    ActionChains(driver).drag_and_drop_by_offset(slider, track.size[‘width’], 0).perform()

    注意:更复杂的滑块(如拼图验证)可能需要计算缺口位置,涉及图像识别,难度大增。

    7.4 处理普通验证码

    这是一个经典难题。完全自动化识别验证码成本高、稳定性差。通常有以下策略,按优先级排序:

  • 万能的后门:在测试环境,请开发提供一个万能验证码(如‘0000’)或直接关闭验证码功能。
  • Cookie大法:先手动登录一次,获取登录后的Cookie,保存下来。在自动化脚本中加载这些Cookie,绕过登录和验证码。适用于需要登录态的场景。# 手动登录后,保存cookie
    cookies = driver.get_cookies()
    import json
    with open(‘cookies.json’, ‘w’) as f:
    json.dump(cookies, f)

    # 在自动化脚本中加载cookie
    driver.get(“https://目标网站.com/login”) # 先访问登录页
    with open(‘cookies.json’, ‘r’) as f:
    cookies = json.load(f)
    for cookie in cookies:
    driver.add_cookie(cookie)
    driver.refresh() # 刷新页面,应用cookie,此时应已登录

  • OCR识别:使用第三方OCR库(如pytesseract)或在线打码平台(如斐斐打码、超级鹰)的API进行识别。这是最后的选择,有成本和识别率问题。
  • 7.5 页面截图

    用于调试和报告。

    # 截取整个屏幕
    driver.save_screenshot(‘screenshot.png’)
    # 截取特定元素
    element = driver.find_element_by_id(“kw”)
    element.screenshot(‘element.png’)


    (至此,我们已经完成了“基础篇”的学习,掌握了Selenium的几乎所有单点技能。接下来,我们将进入激动人心的“项目实战篇”,学习如何将这些技能有机地组合起来,构建一个真正的自动化测试项目。)

    第二篇:真枪实弹——项目实战与框架优化篇

    理论学得再多,不经过实战的锤炼都是纸上谈兵。本篇我们将以一个简化但完整的“携程火车票预订”流程作为项目,带你体验从需求分析、脚本编写,到代码优化、框架设计的完整过程。

    第8章 项目实战:携程火车票预订自动化

    项目目标:自动化完成从查询到填写订单信息的火车票预订主流程(非登录状态)。

    业务流程分析:

  • 访问携程火车票搜索页。
  • 输入出发地、目的地、出发日期。
  • 点击“搜索”按钮,进入车次列表页。
  • 在列表中选择一个车次,点击“预订”。
  • 在登录引导页,点击“不登录,直接预订”。
  • 在订单信息页,填写乘客信息。
  • 8.1 第一版:直来直去的“流水账”脚本

    我们先按照业务流程,写出最直观的脚本。这里会遇到一些实际问题,我们边写边解决。

    # booking_v1.py – 第一版,流水账式脚本
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait, Select
    from selenium.webdriver.support import expected_conditions as EC
    import time

    driver = webdriver.Chrome()
    driver.maximize_window()
    wait = WebDriverWait(driver, 10)

    try:
    # 1. 打开搜索页
    driver.get(“https://trains.ctrip.com/TrainBooking/SearchTrain.aspx“)

    # 2. 输入出发地、目的地 (这里元素可能有JS交互,需先点击再输入)
    from_station = wait.until(EC.element_to_be_clickable((By.ID, “notice01”)))
    from_station.click()
    from_station.send_keys(“上海”)
    time.sleep(1) # 等待输入建议框出现
    # 通常需要点击选择下拉建议项,这里简化,假设输入正确后自动填充

    to_station = driver.find_element(By.ID, “notice08”)
    to_station.click()
    to_station.send_keys(“杭州”)
    time.sleep(1)

    # 3. 处理日期输入框(有readonly属性)
    date_input = driver.find_element(By.ID, “dateObj”)
    # 不能直接send_keys,需要先清除readonly属性,或用JS赋值
    driver.execute_script(“arguments[0].removeAttribute(‘readonly’);, date_input)
    date_input.clear()
    date_input.send_keys(20251225) # 假设一个未来日期
    # 或者直接用JS设置值
    # driver.execute_script(“document.getElementById(‘dateObj’).value=‘2025-12-25’;”)

    # 4. 点击搜索
    search_btn = driver.find_element(By.ID, “searchbtn”)
    search_btn.click()

    # 5. 等待车次列表加载,并选择第一个可预订的车次
    # 注意:车次列表是动态加载的,需要等待元素出现
    book_btn = wait.until(
    EC.element_to_be_clickable((By.CSS_SELECTOR,.railway_list .w6 a“))
    )
    book_btn.click()

    # 6. 在登录页选择“不登录,直接预订”
    no_login_btn = wait.until(
    EC.element_to_be_clickable((By.ID, “btn_nologin”))
    )
    no_login_btn.click()

    # 7. 在订单信息页填写乘客姓名
    passenger_name_input = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR,#pasglistdiv input“))
    )
    passenger_name_input.send_keys(“测试乘客”)

    print(“预订流程脚本执行完毕!”)
    time.sleep(5) # 为了观察结果

    except Exception as e:
    print(f“脚本执行出错: {e})
    # 出错时截图
    driver.save_screenshot(‘error.png’)
    finally:
    driver.quit()

    第一版问题分析:

  • 硬编码:测试数据(城市、日期、姓名)直接写在脚本里,不易修改。
  • 稳定性差:使用了time.sleep,网络或页面加载稍慢就会失败。
  • 可维护性低:元素定位语句散落在业务代码中,如果页面元素ID变了,需要到处修改。
  • 没有错误处理:只有最基础的try-catch。
  • 不可复用:这个脚本只能完成一条特定数据的测试。
  • 接下来,我们将像重构一个软件项目一样,一步步优化这个脚本。

    第9章 代码优化与项目重构

    重构的目标是让代码更清晰、更健壮、更易维护。

    9.1 重构一:分离元素定位与操作

    将页面上所有元素的定位方式集中管理。创建一个locators.py文件。

    # locators.py
    from selenium.webdriver.common.by import By

    class SearchPageLocators:
    """火车票搜索页面元素定位器"""
    FROM_STATION_INPUT = (By.ID, “notice01”)
    TO_STATION_INPUT = (By.ID, “notice08”)
    DATE_INPUT = (By.ID, “dateObj”)
    SEARCH_BUTTON = (By.ID, “searchbtn”)

    class TrainListPageLocators:
    """车次列表页面元素定位器"""
    FIRST_BOOK_BUTTON = (By.CSS_SELECTOR,.railway_list .w6 a“)

    class LoginRedirectPageLocators:
    """登录跳转页面元素定位器"""
    NO_LOGIN_BUTTON = (By.ID, “btn_nologin”)

    class OrderInfoPageLocators:
    """订单信息页面元素定位器"""
    PASSENGER_NAME_INPUT = (By.CSS_SELECTOR,#pasglistdiv input“)

    在业务脚本中,导入并使用这些定位器。这样,当元素属性变化时,只需修改这一个文件。

    9.2 重构二:引入配置文件,管理测试数据

    创建config.ini或config.py,或者使用字典管理测试数据。

    # config.py
    TEST_DATA = {
    “from_city”: “上海”,
    “to_city”: “杭州”,
    “depart_date”:20251225,
    “passenger_name”: “自动化测试员”
    }

    9.3 重构三:封装常用操作和页面动作

    创建base_page.py,作为所有页面类的基类,封装公共方法(如等待、点击、输入)。

    # base_page.py
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC

    class BasePage:
    def __init__(self, driver):
    self.driver = driver
    self.wait = WebDriverWait(driver, 10)

    def find_element(self, *locator):
    """查找单个元素,加入显式等待"""
    return self.wait.until(EC.presence_of_element_located(locator))

    def find_clickable_element(self, *locator):
    """查找可点击的元素"""
    return self.wait.until(EC.element_to_be_clickable(locator))

    def click(self, *locator):
    """点击元素"""
    element = self.find_clickable_element(*locator)
    element.click()

    def input_text(self, text, *locator):
    """在输入框输入文本"""
    element = self.find_element(*locator)
    element.clear()
    element.send_keys(text)

    def execute_js(self, script, *args):
    """执行JavaScript"""
    return self.driver.execute_script(script, *args)

    9.4 重构四:创建页面对象(Page Object)类

    为每个页面创建一个类,继承自BasePage,将页面的元素和操作封装起来。这是Page Object设计模式的核心。

    # pages/search_page.py
    from .base_page import BasePage
    from locators import SearchPageLocators

    class SearchPage(BasePage):
    def enter_travel_info(self, from_city, to_city, date):
    """填写出发地、目的地、日期"""
    self.input_text(from_city, *SearchPageLocators.FROM_STATION_INPUT)
    # 这里可以添加选择下拉建议的代码
    self.input_text(to_city, *SearchPageLocators.TO_STATION_INPUT)
    # 处理日期输入框
    date_element = self.find_element(*SearchPageLocators.DATE_INPUT)
    self.execute_js(“arguments[0].removeAttribute(‘readonly’);, date_element)
    date_element.clear()
    date_element.send_keys(date)

    def click_search(self):
    """点击搜索按钮"""
    self.click(*SearchPageLocators.SEARCH_BUTTON)

    类似地,创建TrainListPage, LoginRedirectPage, OrderInfoPage。

    9.5 重构五:编写业务测试用例

    现在,我们的测试脚本变得非常简洁和易读。

    # test_booking.py – 重构后的测试用例
    import sys
    import os
    sys.path.append(os.path.dirname(os.path.abspath(__file__)))

    from selenium import webdriver
    from pages.search_page import SearchPage
    from pages.train_list_page import TrainListPage
    from pages.login_redirect_page import LoginRedirectPage
    from pages.order_info_page import OrderInfoPage
    from config import TEST_DATA

    def test_book_ticket():
    driver = webdriver.Chrome()
    driver.maximize_window()

    try:
    # 初始化各个页面对象
    search_page = SearchPage(driver)
    train_list_page = TrainListPage(driver)
    login_redirect_page = LoginRedirectPage(driver)
    order_info_page = OrderInfoPage(driver)

    # 1. 打开搜索页并填写信息
    driver.get(“https://trains.ctrip.com/TrainBooking/SearchTrain.aspx“)
    search_page.enter_travel_info(
    TEST_DATA[“from_city”],
    TEST_DATA[“to_city”],
    TEST_DATA[“depart_date”]
    )
    search_page.click_search()

    # 2. 选择车次预订
    train_list_page.select_first_train()

    # 3. 不登录直接预订
    login_redirect_page.book_without_login()

    # 4. 填写乘客信息
    order_info_page.fill_passenger_info(TEST_DATA[“passenger_name”])

    print(*** 测试用例执行成功! ***)

    except Exception as e:
    print(f“*** 测试用例执行失败: {e} ***)
    driver.save_screenshot(‘test_failed.png’)
    raise e
    finally:
    driver.quit()

    if __name__ == “__main__“:
    test_book_ticket()

    至此,我们完成了一次漂亮的重构。代码结构清晰,业务逻辑与页面细节分离,可维护性大大增强。但这还不够,一个企业级的自动化测试框架还需要更多。

    第10章 数据驱动测试

    如果我们想用多组数据来测试同一个流程(例如不同的城市组合),难道要复制粘贴多个测试用例吗?不,我们需要数据驱动测试。

    思想:将测试数据从测试脚本中分离出来,存储在外部的文件(如Excel、CSV、JSON、YAML、数据库)中,测试脚本运行时读取这些数据,循环执行。

    10.1 使用Python内置模块处理数据文件
    • CSV文件:import csv
    • JSON文件:import json
    • Excel文件:需要安装第三方库openpyxl 或 xlrd/xlwt。
    10.2 使用DDT (Data-Driven Tests) 库

    ddt是一个扩展Python unittest的库,可以非常优雅地实现数据驱动。它通常与unittest测试框架结合使用。

  • 安装:pip install ddt
  • 使用示例:
  • import unittest
    from ddt import ddt, data, unpack
    import csv

    def get_data_from_csv(file_name):
    data_list = []
    with open(file_name, ‘r’, encoding=‘utf8) as f:
    reader = csv.DictReader(f)
    for row in reader:
    data_list.append(row)
    return data_list

    # 准备测试数据
    test_data = get_data_from_csv(‘test_data.csv’)
    # 或者直接列表
    # test_data = [
    # {“from”: “上海”, “to”: “杭州”, “date”: “2025-12-25”},
    # {“from”: “北京”, “to”: “天津”, “date”: “2025-12-26”},
    # ]

    @ddt
    class TestBooking(unittest.TestCase):
    def setUp(self):
    self.driver = webdriver.Chrome()
    self.driver.maximize_window()

    @data(*test_data) # 使用装饰器注入数据
    @unpack # 解包字典
    def test_book_ticket_with_ddt(self, from_city, to_city, depart_date):
    """使用DDT的测试用例,会自动运行多次"""
    # 这里调用我们之前封装好的页面对象和操作
    search_page = SearchPage(self.driver)
    search_page.open()
    search_page.enter_travel_info(from_city, to_city, depart_date)
    search_page.click_search()
    # … 后续操作
    # 添加断言,例如验证页面标题或某个关键元素
    self.assertIn(“车次列表”, self.driver.title)

    def tearDown(self):
    self.driver.quit()

    if __name__ == “__main__“:
    unittest.main()

    10.3 将数据驱动整合进我们的项目

    我们可以创建一个data目录,在里面存放testdata.csv或testdata.json。修改config.py或创建一个专门的data_reader模块来读取这些数据,供测试用例使用。

    数据驱动的优势:极大增强了测试的覆盖率和灵活性,添加新测试场景只需新增一行数据,而无需修改代码。

    第11章 Page Object设计模式深度解析

    在上一章的重构中,我们已经初步运用了PO模式。现在我们来深入理解其精髓。

    11.1 PO模式的核心思想
    • 页面即对象:将每一个Web页面抽象成一个Class。
    • 元素定位与操作分离:页面的元素定位器(locators)和在这个页面上的操作(methods)封装在这个类中。
    • 业务逻辑与页面细节分离:测试用例只包含高层的业务步骤(在什么页面,做什么操作),不包含具体的元素定位和交互细节。
    11.2 PO模式的优点
    • 高可维护性:当UI变化时,通常只需要修改对应的Page类中的元素定位,所有使用该页面的测试用例都自动生效。
    • 高可读性:测试用例读起来就像产品需求文档,例如login_page.login(“user”, “pass”)。
    • 低冗余:页面公共操作(如等待、点击)封装在基类中,避免代码重复。
    • 团队协作:页面对象可以由专人维护,测试用例编写者可以更关注业务逻辑。
    11.3 PO模式的常见项目结构

    project/

    ├── base/ # 基类
    │ └── base_page.py

    ├── pages/ # 页面对象类
    │ ├── __init__.py
    │ ├── login_page.py
    │ ├── search_page.py
    │ └── …

    ├── locators/ # 元素定位器 (也可放在pages里)
    │ ├── __init__.py
    │ ├── login_page_locators.py
    │ └── …

    ├── tests/ # 测试用例
    │ ├── __init__.py
    │ ├── test_login.py
    │ └── test_booking.py

    ├── data/ # 测试数据文件
    │ ├── testdata.csv
    │ └── users.json

    ├── utils/ # 工具函数
    │ ├── __init__.py
    │ ├── logger.py # 日志记录
    │ └── config_reader.py

    ├── reports/ # 测试报告

    ├── requirements.txt # 项目依赖
    └── run_tests.py # 测试运行入口

    这是一种非常清晰、易于扩展和维护的目录结构。

    第12章 行为驱动开发

    BDD是一种敏捷开发技术,它鼓励开发、测试、产品等所有项目成员用自然语言定义系统的行为。在自动化测试中,我们使用behave或pytest-bdd等工具来实现BDD。

    核心理念:用例用Given-When-Then的格式编写。

    • Given:描述初始状态(前置条件)。
    • When:描述发生的操作(事件)。
    • Then:描述预期的结果(断言)。

    示例 (train_booking.feature):

    Feature: 携程火车票预订
    作为一个用户
    我希望能够预订火车票
    以便于出行

    Scenario: 非登录用户成功预订火车票
    Given 我打开了携程火车票搜索页面
    When 我搜索从“<出发地>”到“<目的地>”在“<日期>”的车票
    And 我选择第一个车次进行预订
    And 我选择不登录直接预订
    And 我填写乘客姓名为“<乘客名>”
    Then 我应该看到订单信息确认页面
    Examples:
    | 出发地 | 目的地 | 日期 | 乘客名 |
    | 上海 | 杭州 | 2025-12-25 | 张三 |
    | 北京 | 天津 | 2025-12-26 | 李四 |

    然后,我们需要编写对应的“步骤定义”代码,将自然语言步骤映射到具体的Selenium操作(即调用我们之前写好的Page Object方法)。

    BDD的优势:

    • 用例即文档:.feature文件本身就是清晰、无歧义的、可执行的需求文档。
    • 促进沟通:非技术人员也能理解和评审测试用例。
    • 降低维护成本:当UI变化时,只需要修改步骤定义背后的代码,feature文件通常不需要改动。

    (项目实战篇告一段落。我们已经从一个只会写简单脚本的“菜鸟”,成长为能够设计出结构清晰、易于维护的PO模式自动化测试框架的“高手”。最后,让我们将目光投向更广阔的“平台”与“未来”。)

    第三篇:更上一层楼——平台集成与未来展望

    第13章 自动化测试平台与持续集成

    个人的脚本写得再好,也只是“手工作坊”。要让自动化测试在团队中发挥最大价值,必须将其“工业化”、“平台化”。

    13.1 版本控制:Git

    使用Git(如GitHub, GitLab, Gitee)管理你的自动化测试代码是基本要求。它带来了:

    • 代码历史与回溯。
    • 团队协作与代码审查。
    • 分支管理,支持不同特性或版本的测试代码并行开发。
    13.2 持续集成:Jenkins

    Jenkins是一个开源的自动化服务器,用于实现持续集成和持续交付。我们可以将自动化测试集成到Jenkins中。

    • 定时执行:例如,每天凌晨2点自动运行全套回归测试。
    • 触发执行:代码提交到Git后,自动触发测试。
    • 测试报告:Jenkins收集测试结果,生成趋势图和报告,并通过邮件、钉钉、企业微信等通知相关人员。
    • 分布式执行:可以配置多个节点(机器),并行执行测试,大幅缩短测试总时间。

    在Jenkins中配置一个自动化测试任务:

  • 新建一个“自由风格”项目。
  • 源码管理:配置Git仓库地址和凭证。
  • 构建触发器:设置定时构建(如H 2 * * * 每天2点)或轮询SCM。
  • 构建:执行Shell命令或Windows批处理命令。# Linux/Mac示例
    cd /path/to/your/project
    source venv/bin/activate # 激活虚拟环境(如果有)
    pip install -r requirements.txt
    python -m pytest tests/ –alluredir=./reports/allure-results
  • 构建设置:配置Allure或HTML测试报告生成。
  • 构建后操作:配置邮件通知,发布测试报告。
  • 13.3 容器化:Docker

    Docker可以将你的测试环境(包括浏览器、驱动、依赖库)打包成一个镜像。这解决了“在我机器上能跑”的经典问题。

    • 环境一致性:在任何装有Docker的机器上,都能获得完全一致的测试执行环境。
    • 快速部署:新成员加入,无需复杂的环境配置,一条docker run命令即可开始测试。
    • Selenium Grid:结合Docker可以快速搭建Selenium Grid分布式测试环境,实现多浏览器、多版本的并行测试。

    使用Docker运行Selenium测试的简单示例:

    # Dockerfile
    FROM python:3.8-slim
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    COPY . .
    CMD [“python”, “run_tests.py”]

    然后构建镜像并运行。

    第14章 总结与展望:你的自动化测试之路

    恭喜你!通过这本指南,你已经完成了从Selenium小白到具备实战和架构能力的高手的蜕变。让我们回顾一下这条学习路径:

  • 筑基:理解自动化测试价值,搭建Python+Selenium环境,学习前端基础。
  • 核心:深刻掌握八大元素定位和各种浏览器操作API,这是你一切能力的源泉。
  • 实战:通过一个真实项目,将知识点串联起来,编写出可运行的脚本。
  • 优化:引入Page Object设计模式、数据驱动、BDD,将脚本重构为可维护、可扩展的测试框架。
  • 工程化:集成Git、Jenkins、Docker,将个人技能转化为团队的生产力。
  • 前方的路:

    • 移动端自动化:Appium是移动端的“Selenium”,其理念与Selenium WebDriver一脉相承,学习成本较低。
    • API自动化测试:深入学习requests、pytest、httpx等库,构建健壮的接口测试框架。
    • 性能测试:了解Locust、JMeter等工具,从功能验证延伸到性能保障。
    • 测试开发:深入研究测试框架设计、测试平台开发、质量效能提升,这是更广阔的职业发展空间。

    自动化测试不是目的,而是提升软件研发效能与质量的手段。永远不要为了自动化而自动化。保持好奇心,持续学习,乐于分享,你将在软件测试和质量保障的道路上行稳致远。

    现在,是时候将你所学投入真正的项目,去解决实际问题,去创造价值了。祝你成功!

    赞(0)
    未经允许不得转载:171主机测评 » 从零到一:基于Python 3与Selenium 3的Web自动化测试实战完全指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址