ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Selenium自动化测试从零入门:搭建Web自动化框架完整指南

Selenium自动化测试从零入门:搭建Web自动化框架完整指南 老有人问我做测试是不是得先学会写代码第一个自动化项目到底从哪下手其实大多数新手第一个接触的自动化工具就是Selenium它也是Web自动化测试绕不开的“基本功”。Selenium能模拟真实用户在浏览器里的点击、输入、滚动、切页这些操作把重复的手工回归测试交给脚本去跑省下来的时间可以去干更有价值的事。这篇内容我打算一口气讲透从零开始用Selenium做自动化测试的完整路径环境怎么配、第一个脚本怎么写、元素定位怎么稳、怎么用pytest把脚本变成框架、踩过的坑有哪些。不夸张地说跟着走一遍你不仅能写出能跑的脚本还能搭出一个能上项目的框架雏形。适合正在转测试开发的人、已经在做功能测试想搞自动化的朋友以及自学测试找不到方向的新人。1. 先搞清楚Selenium到底解决什么问题要学哪些东西1.1 Selenium是什么为什么自动化测试首选它Selenium是一套用于Web浏览器自动化的工具集核心是模拟用户在浏览器中的真实操作。它不是一个“测试框架”本身而是提供了一套操作浏览器的API你可以用Python、Java、C#、Ruby这些语言去调它驱动真实的浏览器去执行动作。正因为它是通过“真实浏览器”来运行的所以不需要去改被测系统的源码也不需要后端提供什么特殊接口只要你能用浏览器打开页面它就能帮你操作页面。为什么很多团队和个人在自动化测试时首选Selenium我个人的理解是三个原因。第一社区生态足够大从2004年出现到现在快二十年了遇到问题几乎都能搜到答案踩坑成本低。第二它兼容主流浏览器Chrome、Firefox、Edge都能跑而且支持多种编程语言团队里用什么语言都能接上。第三也是最关键的一点它贴近“真实用户视角”脚本跑出来的结果可信度高不是那种mock来mock去的“假测试”。做Web自动化测试Selenium最常见的应用场景是回归测试。比如一个电商系统每次发版前核心流程——登录、搜索商品、加购物车、下单、支付——如果全靠手工点至少得半小时重复又枯燥。用Selenium写一遍脚本之后每次发版让脚本自动跑一遍省时省力还能顺便把稳定性问题暴露出来。它也可以用于数据采集但那是另一个方向自动化测试才是它的老本行。1.2 版本选择Selenium 3还是4选新不选旧很多新手在网上找教程结果搜出来的是Selenium 2的代码运行直接报错。这里必须先说清楚版本问题。2021年以后Selenium正式发布了4.x版本到现在的Selenium 4.6以上版本驱动管理方式发生了很大变化。Selenium 4相对于3代最大的改进之一是内置了Selenium Manager可以自动匹配并下载浏览器驱动。这意味着你用4.6以上的版本写代码时通常不需要再手动去百度下载ChromeDriver框架会自动处理驱动的版本对应关系。所以我的建议很简单直接用最新稳定版的Selenium 4不要再看Selenium 2或者3的老教程里的API写法了。比如Selenium 3时代常用的find_element_by_id这种写法在Selenium 4里已经废弃了需要用driver.find_element(By.ID, xxx)的方式。如果你照着旧教程写终端会给你一个DeprecationWarning代码虽然能跑但是不优雅以后升级还会出问题。什么时候还要用Selenium 3老项目维护、某些公司内部遗留框架还锁在旧版本上这种情况你不得不兼容。但作为新入门直接学4。另外还有一个小点Selenium Grid的用法在新老版本里也不一样。Selenium Grid是用于分布式执行测试的可以让测试脚本在多个浏览器、多台机器上同时跑。新手入门可以先不管Grid等把基础玩熟了再研究怎么把用例并行起来。1.3 入门要准备的知识栈自动化测试不是只有Selenium它是一个综合技能的集合。我理了一下新手至少要有这些基础一门编程语言的基础语法新手我推荐Python因为生态好、代码简洁、资料多。不需要学到多深变量、数据类型、函数、类、异常处理、文件读写这些会用就行。HTML和CSS基础不用会写漂亮页面但要知道标签是什么、id和class的区别、怎么通过属性去定位元素。毕竟Selenium所有定位都是基于DOM结构的。浏览器开发者工具的使用找到F12打开后的Elements面板学会快速复制XPath学会看接口请求和Console报错。这是每个自动化测试工程师每天都要用的技能。一点点命令行知识会安装Python包、会跑命令不然连环境都配不起来。耐心排查问题的习惯自动化测试报错是常态没有排查能力走不远。别一上来就学一堆框架Selenium本身已经够你消化一阵了。我见过很多人第一天就想装pytest、装allure、搭PageObject结果连一个脚本还没跑通。先把基础路径走通再加花活。2. 环境准备Python、Selenium库和浏览器驱动一个都不能少2.1 安装Python环境与pip环境配置是很多新手的第一道坎但真没有那么难。如果你用Windows去Python官网下载安装包建议下载3.9以上的版本不要下载最新的测试版。安装的时候记得勾选“Add Python to PATH”这个选项勾上以后才能在命令行里直接用python命令。安装完成后打开命令行窗口输入python --version能输出版本号就说明Python装好了。如果提示“python不是内部或外部命令”多半是PATH没勾上重新跑一遍安装包勾上PATH或者手动把Python的安装目录加进系统环境变量。pip是Python的包管理器装Python的时候一般会自带。你可以用pip --version验证一下。如果pip版本太老可以用python -m pip install --upgrade pip升级。国内网络环境下pip默认下载源可能很慢我一般会配置清华源或阿里源这样装包速度快很多。配置方式是创建C:\Users\你的用户名\pip\pip.ini文件写入[global] index-url https://pypi.tuna.tsinghua.edu.cn/simple2.2 安装selenium库Python环境就绪后安装Selenium库是最简单的一步。在命令行里执行pip install selenium装完之后可以用pip show selenium查看版本号。如果你之前装过老版本建议先升级到最新版pip install -U selenium这里要注意pip install selenium装上的是Selenium的Python客户端库它只是“遥控器”本身不包含浏览器。你电脑上还需要有Chrome或者Firefox这类浏览器。Chrome在国内是常用的下面都以Chrome为例。还需要注意Selenium 4.6以上的版本自带Selenium Manager当你在代码里指定了浏览器类型但找不到驱动时它会自动去下载匹配的驱动文件。所以从这个版本开始环境准备其实已经简化很多了。但是自动下载有时候在网络不好或公司内网环境下会失败手动配置驱动这个技能还是要会。2.3 下载浏览器驱动ChromeDriver为例如果你用的是Selenium 4.6以下版本或者Selenium Manager自动下载失败就需要手动下载ChromeDriver。首先要确认你本机Chrome浏览器的版本。打开Chrome点击右上角三个点选择“帮助” - “关于Google Chrome”会看到类似“版本 120.0.6099.130正式版本”这样的信息。然后去ChromeDriver的下载页面找到对应版本的驱动包。下载的时候注意平台选win32还是linuxWindows系统一般选chromedriver_win32.zip不要被名字误导64位系统也能用这个包。下载解压后你会得到一个chromedriver.exe文件。接下来有两种方案。一种是把chromedriver.exe放到Python的Scripts目录下或者某个已经加入PATH的目录里这样代码在调用的时候可以自动找到。另一种是在代码里直接指定驱动路径from selenium import webdriver driver webdriver.Chrome()如果你确定驱动已经放好或者Selenium Manager生效了上面这个写法就够了。如果不行就用显式路径driver webdriver.Chrome(executable_pathrD:\chromedriver\chromedriver.exe)不过executable_path这个参数在Selenium 4里已经废弃了推荐用service参数from selenium import webdriver from selenium.webdriver.chrome.service import Service service Service(rD:\chromedriver\chromedriver.exe) driver webdriver.Chrome(serviceservice)这里就是网上教程最容易坑新人的地方版本和写法新旧混在一起一报错就懵。我的建议是如果你用的是Selenium 4.6以上优先什么都不配置直接driver webdriver.Chrome()让Selenium Manager自动处理如果自动处理失败再手动下载驱动并用Service方式指定。2.4 第一个冒烟验证脚本环境配完别急着写复杂功能先跑一个冒烟脚本确认“整个链路是通的”。新建一个test_smoke.py文件写入from selenium import webdriver driver webdriver.Chrome() driver.get(https://www.baidu.com) print(页面标题, driver.title) driver.quit()运行python test_smoke.py如果程序能打开Chrome浏览器访问百度打印出“百度一下你就知道”然后自动关闭浏览器那恭喜你环境全部通了。如果报了SessionNotCreatedException基本就是ChromeDriver版本和浏览器版本不匹配回到第2.3节去换版本。如果秒退或者没有反应先检查一下是不是浏览器路径没找到。这一步虽然简单但我建议每个人都真正跑一遍不要跳过去因为它能隔离“环境问题”和“代码问题”后面学起来会顺畅很多。3. 第一个自动化脚本跑通“打开-操作-断言-退出”闭环3.1 一段完整可跑的Selenium脚本环境通了接下来就写第一个真正有测试意义的脚本。我用百度搜索来演示因为所有人都能复现。脚本做的事情很简单打开百度首页在搜索框输入“Selenium自动化测试”点击搜索按钮等待结果加载然后断言页面上出现了“自动化测试”相关的关键词。import time from selenium import webdriver from selenium.webdriver.common.by import By driver webdriver.Chrome() driver.get(https://www.baidu.com) # 找到搜索输入框输入关键词 search_input driver.find_element(By.ID, kw) search_input.send_keys(Selenium自动化测试) # 点击搜索按钮 search_button driver.find_element(By.ID, su) search_button.click() # 等搜索结果加载先用强制等待演示后面会讲更好的方式 time.sleep(3) # 获取页面并断言 page_source driver.page_source assert 自动化测试 in page_source, 搜索结果中没有找到关键字 print(测试通过找到了搜索结果) driver.quit()3.2 代码逐行发生了什么这里我给第一次接触Selenium的同学逐行拆一下。driver webdriver.Chrome()做的事是启动浏览器进程并把浏览器控制权交给我们。driver.get(https://www.baidu.com)是让浏览器去访问指定URL这个方法会阻塞到页面完全加载完才返回至少在同步等待的策略下是这样的。接下来driver.find_element(By.ID, kw)是使用By枚举来告诉Selenium我们想用ID定位器去寻找元素。百度搜索框的DOM元素有一个idkw属性这是最简单可靠的定位方式。找到元素后send_keys()方法模拟键盘输入把“Selenium自动化测试”这几个字敲进去。然后find_element(By.ID, su)找到搜索按钮click()方法模拟鼠标点击。点击之后页面会发起请求并跳转到搜索结果页这时候不能立刻去操作新页面里的元素因为页面还在加载所以要先等一会儿。示例里用的是time.sleep(3)强制等待3秒很粗暴但在第一步演示时足够简单能让你看到一个完整闭环长什么样。最后一步非常关键。自动化测试如果没有断言脚本跑完你也不知道它是通过还是失败。assert 自动化测试 in page_source是Python的断言语法意思是检查搜索结果页的HTML源码字符串里是否包含“自动化测试”这个关键词。如果包含程序继续往下走打印成功信息如果不包含抛出AssertionError测试失败。这就像质检员检查完产品后在报告上盖一个“合格”或者“不合格”的章。3.3 新手最容易忽略的收尾问题我看过很多新人的第一版脚本写完跑通了就高兴地关了终端完全不知道浏览器还挂在后台。这里要强调driver.quit()必须被执行它会关闭浏览器并清理所有会话资源。如果脚本中途报错了driver.quit()没被调用显示器上就可能看到一堆僵尸Chrome进程。比较优雅的写法是用try...finally甚至用上下文管理器保证不管报不报错浏览器都能关掉try: driver webdriver.Chrome() driver.get(https://www.baidu.com) # 你的测试逻辑 finally: driver.quit()我见过最典型的事故是有人跑了个批量脚本中途异常退出没写quit()结果运行了几百次之后电脑内存直接被占满。原因就是每个异常分支都残留了浏览器进程。所以从这个最简单的脚本开始就要养成“最后一定关浏览器”的习惯。另一个容易忽略的问题是driver.get()虽然会等页面加载完但它等的是“浏览器加载完成状态”不关心你要找的元素是否已经存在。真正上项目的时候不能用一句time.sleep(3)去硬等具体怎么处理下一章细讲。4. 元素定位和等待机制稳不稳全看这里4.1 八大定位方式怎么选Selenium自动化测试里80%以上的报错都发生在“元素定位”这一步。Selenium 4支持的定位策略有八种我列在下面方便对照定位策略By写法说明IDBy.ID按元素id属性最快最稳定Class NameBy.CLASS_NAME按元素class属性注意可能有多个classNameBy.NAME按元素name属性常用于表单Tag NameBy.TAG_NAME按标签名如div、inputLink TextBy.LINK_TEXT按链接的完整文本Partial Link TextBy.PARTIAL_LINK_TEXT按链接文本的部分内容XPathBy.XPATH用表达式定位功能最强CSS SelectorBy.CSS_SELECTOR用CSS选择器定位性能好选哪种优先级我的习惯是有id用id没有id优先看name或者稳定的class再不行才用CSS Selector最后才用XPath。为什么XPath被排在后面不是因为它不好而是它太容易写出“脆”的表达式。比如你在开发者工具里右键复制XPath经常拿到一长串绝对路径/html/body/div[2]/div[1]/div[2]/div[3]/div[1]/div[5]/div[1]/div[2]/div[1]/div[3]/div[1]/input这种东西页面结构稍微调整一次就崩。我建议尽量自己写相对XPath核心思想是“找到这个元素附近的锚点再通过它找到目标”。比如定位一个文本框可以用driver.find_element(By.XPATH, //input[idkw])这里的//input表示在整个文档里找所有input标签[idkw]是添加一个过滤条件要求id属性等于kw。这种表达式简洁而且不容易受到祖先节点变动的影响。CSS Selector也是一样的道理性能通常比XPath更快比如input#kw、button.s-btn。新手想进阶可以重点学一下CSS Selector。4.2 强制等待、隐式等待、显式等待的区别这一节是整个Selenium入门里最值得反复理解的地方。网上很多人写time.sleep(5)然后抱怨脚本不稳定原因就是固定等待时间根本不知道页面什么时候加载完网络快慢不确定内容渲染也有快有慢写少了报错写多了浪费时间。Selenium里真正的等待方案有三种我拆开说。强制等待就是上面用的time.sleep(seconds)不管页面好没好事睡够了就醒。它适合在调试环境临时用或者确认“这一步必须等N秒让动画播完”的场景但绝不适合作为主要等待机制。隐式等待是设置一个全局的超时时间只要通过driver查找元素时元素没有立刻出现它就会在设定的时间范围内不断轮询DOM直到元素出现或超时。用法是在初始化driver后写一行driver.implicitly_wait(10)这意味着所有find_element操作在10秒内找不到元素都会继续尝试不会立刻报错。它写起来简单但有缺点它只对“元素存在与否”生效对元素“可见但不可点击”这种状态无能为力而且会对每次查找都生效偶尔会让脚本变慢。显式等待是最推荐的方案。它专门针对某个条件进行等待比如等待某个元素可见、可点击、存在、包含某个文本等等。底层用起来这样写from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait WebDriverWait(driver, 10) element wait.until(EC.element_to_be_clickable((By.ID, su)))这行的意思是在10秒之内最多每0.5秒检查一次“这个按钮可不可以点击”可以了就立刻返回元素并往下走超时了就抛TimeoutException。这样做测试脚本既稳又快不会傻等也不容易提早操作。显式等待的Expected Conditions还有很多比如presence_of_element_located、visibility_of_element_located、text_to_be_present_in_element等等都是日常高频用的。我平时写项目的基本组合是全局设置implicitly_wait(5)作为兜底关键步骤再用显式等待精确控制。特别注意一点隐式等待和显式等待不要混用时把超时时间设得太大不然叠加起来等待时间会很长排查问题时也不容易定位是哪一层在等。4.3 常用交互操作和特殊场景处理日常测试脚本里除了定位元素还会涉及很多交互动作。这里我把高频操作整理一下。输入与清空element.send_keys(文本)用于输入element.clear()用于清空。键盘事件send_keys(Keys.ENTER)可以模拟回车Keys.CONTROL, a可以全选。下拉框原生HTML下拉框用Selenium的Select类from selenium.webdriver.support.ui import Select select Select(driver.find_element(By.ID, province)) select.select_by_visible_text(广东省) select.select_by_value(gd) select.select_by_index(3)如果是自定义的下拉框点击后展开的div下拉菜单无法用Select类只能先点击再定位下拉项并点击。文件上传如果页面有input typefile标签直接send_keys(文件绝对路径)即可不需要模拟点击弹窗。鼠标悬停、右键、拖拽用ActionChains类。比如悬停到菜单项上让子菜单展开from selenium.webdriver.common.action_chains import ActionChains actions ActionChains(driver) menu driver.find_element(By.XPATH, //div[classmenu]) actions.move_to_element(menu).perform()弹窗处理driver.switch_to.alert.accept()接收弹窗dismiss()取消弹窗switch_to.alert.text获取文本。多窗口切换当一个操作打开了新标签页时需要切换句柄handles driver.window_handles driver.switch_to.window(handles[-1])iframe处理如果定位不到元素先检查它是不是在iframe里。需要切换进iframedriver.switch_to.frame(iframe的id或name) # 操作完记得切回默认内容 driver.switch_to.default_content()这里我想强调一个排查思路定位不到元素时第一反应不要是换更复杂的定位表达式而是去开发者工具里看这个元素到底在不在当前DOM里。如果在iframe里就切iframe如果在弹窗里就先处理弹窗如果在折叠区域里就先展开很多“定位不到”其实是操作顺序不对不是定位器写错了。5. 用pytest把脚本变成测试框架5.1 pytest selenium的基本用例脚本写顺了接下来就该把它从“能跑的一段代码”升级成“有结构、有断言、有报告的测试框架”。pytest是Python生态里最流行的测试框架它本身和Selenium没有直接关系但两者搭配是Web自动化测试最常见的组合。为什么大家都这么搭因为pytest提供了用例收集、断言增强、fixture、参数化、插件扩展这些能力而Selenium只负责打开浏览器、操作页面。测试用例的“组织、执行、报告、失败重试”这些活儿都交给pytest。一个最基础的pytest分类测试用例长这样import pytest from selenium import webdriver from selenium.webdriver.common.by import By def test_baidu_search(): driver webdriver.Chrome() driver.get(https://www.baidu.com) driver.find_element(By.ID, kw).send_keys(pytest 自动化测试) driver.find_element(By.ID, su).click() assert pytest in driver.page_source driver.quit()运行的时候在终端执行pytest test_demo.py -v它会自动发现所有以test_开头的函数并执行。-v参数会显示每个用例的执行结果通过就是PASSED失败就是FAILED还会带出详细的失败堆栈。pytest的命名规则很简单测试文件用test_开头或_test结尾测试函数用test_开头测试类用Test开头。如果你没按这个规则命名pytest默认不会收集它新手经常在这里踩坑明明写了用例运行却显示“collected 0 items”。5.2 conftest与fixture管理浏览器生命周期如果每个测试用例里都写一套“启动浏览器、操作、退出浏览器”代码会显得很冗余而且每跑一个用例就开一次浏览器非常浪费时间。pytest的fixture机制能很好地解决这个问题。fixture可以理解为一个“带初始化和清理逻辑”的函数它能在测试函数执行前后自动做事情。我把浏览器启动做成一个fixture放在conftest.py文件里这样所有测试文件都能共享它import pytest from selenium import webdriver pytest.fixture def driver(): driver webdriver.Chrome() driver.implicitly_wait(5) yield driver driver.quit()然后测试函数里只需要接收一个名为driver的参数pytest会自动调用这个fixturedef test_baidu_search(driver): driver.get(https://www.baidu.com) driver.find_element(By.ID, kw).send_keys(pytest fixture) driver.find_element(By.ID, su).click() assert pytest in driver.page_source这里面的关键点是yield。在yield之前的代码是“前置操作”会在测试用例执行前运行yield之后的代码是“清理操作”会在用例结束后运行哪怕用例运行过程中抛了异常也会执行。这正好解决了上一章强调的“忘记关浏览器”的问题。再进一步fixture还可以设置作用域。默认scopefunction每个用例都执行一次。如果你希望整个测试会话只启动一次浏览器多个用例共用可以设成scopesession或scopeclasspytest.fixture(scopefunction) def driver(): ...要不要共用浏览器取决于你的测试场景。如果用例之间有依赖或者测试的是多个页面之间的流转那复用同一个浏览器能大幅提高效率。但如果用例彼此独立每个用例开了新浏览器更干净互不影响。日常UI自动化我一般推荐把浏览器启动放在scopeclass一个测试类里的一组用例复用一次浏览器既快又干净。5.3 断言怎么写才有意义很多新手写的自动化测试“能跑但没意义”因为他们的断言是assert driver.page_source里草草检查一个宽泛的字符串或者干脆不写断言只跑操作。要注意自动化测试的价值不在于“脚本跑了”而在于“这个脚本能不能准确地判断业务有没有出问题”。pytest里最常用的是Python内置的assert语句简单直接。但UI自动化里我更推荐结合selenium条件和pytest的断言信息来写。比如要断言“登录后用户名正确”不推荐只查页面源码里有没有包含“admin”因为源码匹配不够精确可能你在页面上没显示但源码里有或者匹配到的是其他相似文本。比较好的做法是定位到具体元素再取它的文本或者属性来断言user_info driver.find_element(By.CLASS_NAME, user-name) assert user_info.text admin, f期望用户名是admin实际却是{user_info.text}这样失败信息非常明确一眼就能看到期望值和实际值的差异。pytest在断言失败时会自动显示assert两侧的表达式的值所以你只要在断言里写下有意义的比较逻辑报错信息就不会让人一头雾水。如果你需要写“断言元素在不在页面上”可以用pytest的raises配合Selenium的异常import pytest from selenium.common.exceptions import NoSuchElementException with pytest.raises(NoSuchElementException): driver.find_element(By.ID, not_exist_id)这个用例的意思就是“我期望页面上不存在这个元素”如果它确实不存在测试通过如果它存在测试失败。这种写法在做“删除成功”这类业务验证时特别好用比如删除一条记录后断言记录已经不在列表里了。5.4 生成可视化测试报告脚本稳定了用例多了就需要一份能给别人看的报告。pytest生态里有两个主流选择pytest-html和Allure。pytest-html用起来最简单安装后执行命令时加一个参数就行pip install pytest-html pytest test_demo.py -v --htmlreport.html运行完会在当前目录生成一个report.html用浏览器打开就能看到用例总数、通过率、失败用例的堆栈和截图路径。想要在失败时自动截图可以配合pytest的hook写一个conftest.py的钩子函数import pytest from pathlib import Path pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: driver item.funcargs.get(driver) if driver: screenshot_dir Path(screenshots) screenshot_dir.mkdir(exist_okTrue) driver.save_screenshot(str(screenshot_dir / f{item.name}.png))这段代码的意思是每个用例执行失败时从fixture里取到driver然后调用save_screenshot把当时的页面截图存下来。之后看报告时配合截图就能知道崩在什么界面排查效率翻倍。Allure的报告更好看、信息更全但需要安装Allure命令行工具还要在pytest里装allure-pytest插件新手可以先不折腾。先用pytest-html把报告跑起来等以后需要更专业的报告展示再迁移到Allure也不迟。UI自动化测试的报告里一定要有截图和日志这是最基本的要求没有这两个东西报告做得再花哨也等于白做。6. 常见问题排查与避坑实录6.1 驱动版本不匹配报错信息长这样selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 122这个问题的原因就是ChromeDriver的版本和Chrome浏览器版本不一致。ChromeDriver只能支持对应大版本的Chrome比如Chrome 122对应的驱动大概是122.x的ChromeDriver。解决方式就是去下载和你浏览器大版本完全一致的驱动替换掉旧驱动。如果你用的是Selenium 4.6以上这个错误大概率不会出现因为Selenium Manager会自动下载匹配驱动但如果你在公司内网、或者浏览器是特殊的Beta版本还是有翻车的可能。我自己就遇到过一种情况浏览器自动升级到了126但本地的ChromeDriver还是122白天还好好的脚本第二天一跑就崩了。建议定期检查浏览器版本或者干脆把浏览器自动更新关掉避免被版本问题偷袭。6.2 元素定位不到或不可交互这是最高频的一类问题。常见报错有NoSuchElementException找不到元素ElementNotInteractableException元素不可交互ElementClickInterceptedException点击被其他元素拦截StaleElementReferenceException元素引用过期页面已刷新排查思路我建议按这个顺序来。第一先确认页面真的打开了有时是环境代理、登录态丢了导致跳到了别的页面。第二打开开发者工具的Elements面板搜索这个定位器能不能找到唯一一个元素如果找到多个那定位器不够精确需要用[0]索引或者更精细的属性。第三检查元素是否在iframe或shadow DOM里这种特殊封装结构会让普通定位方式失效。第四检查是不是操作太快了元素还没渲染出来换显式等待。第五如果元素在页面底部需要先滚动到它可见再操作from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains element driver.find_element(By.ID, submit-btn) ActionChains(driver).move_to_element(element).perform() element.click()第六对于点击被拦截的问题很多时候是页面上有遮罩层、弹窗或者加载动画等它消失或者关闭后就能点击了。用显式等待等待element_to_be_clickable比盲目click()稳得多。6.3 页面加载慢与网络抖动测试脚本跑得快不快、稳不稳定很大程度上取决于网络环境。页面资源没加载完元素自然找不到。我推荐的方案是把隐式等待设置为5秒左右把关键操作改成显式等待并且等待时间不需要太长一般设10秒。如果网络真的很慢显式等待超时时脚本会抛异常这时要判断是“真的出Bug了”还是“环境抖动”。为了区分这两种情况我通常会在异常处理里加上重试逻辑。pytest有pytest-rerunfailures插件安装后可以直接给用例加装饰器pip install pytest-rerunfailurespytest.mark.flaky(reruns2, reruns_delay2) def test_flaky_case(driver): ...意思是失败后自动重试两次每次间隔2秒。如果重试后还是失败说明大概率是真Bug如果重试后通过了那基本可以判定是网络或渲染问题。UI自动化里这种“不稳定用例”很常见花点时间把重试机制加上能让你的测试报告干净很多。6.4 headless模式下的小坑很多人跑自动化测试喜欢用无头模式因为不弹浏览器窗口跑起来不耽误干别的在CI服务器上更是标配。无头模式的开启方式from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headless) driver webdriver.Chrome(optionsoptions)但无头模式有几个坑要注意。第一无头模式没有真实的窗口大小默认是800x600有些页面在窄屏下布局会变化元素位置也可能变动导致定位失败。解决办法是显式设置窗口大小options.add_argument(--window-size1920,1080)第二无头模式下有些弹窗、文件下载行为会和有头模式不一样比如下载文件需要额外配置浏览器下载目录参数。第三无头模式下截图是正常的但截图分辨率也是窗口大小所以想截图好看的还是把窗口设大点。我自己在本地调试时都跑有头模式只有提交到CI之前才切无头这也是一个避坑小技巧。我把上面这些问题整理成一个速查表方便你直接参考症状可能原因优先排查方案SessionNotCreatedException驱动版本不匹配检查浏览器版本换对应驱动NoSuchElementException元素定位器错误 / 页面未加载用DevTools验证元素改用显式等待ElementNotInteractableException元素隐藏、只读、未渲染等元素可见后再操作ElementClickInterceptedException遮罩层/弹窗拦截先关闭弹窗等待可点击StaleElementReferenceException页面刷新/跳转后元素引用过期重新定位元素TimeoutException等待超时检查条件是否写对增加超时时间WebDriverException驱动路径/权限问题检查driver路径是否在PATH中7. 进阶路线从能跑脚本到能写框架7.1 Page Object模式必须学脚本能稳定跑起来只是第一步一旦测试用例多起来你会立刻发现代码重复得可怕同一个页面元素这个用例里定位一次那个用例里又定位一次登录逻辑在十个用例里复制了十遍。这时候如果产品页面改了某个元素的id你要去改几十处改到崩溃。所以项目级自动化测试一定需要使用Page Object模式也就是页面对象模式。Page Object的核心思想很简单把每个页面抽象成一个类页面上所有元素定位和操作方法都封装在这个类里测试用例只调用这些方法不直接写find_element。这样做的好处是页面元素变了只改页面类里的定位器测试用例只关心业务动作清晰易读多个用例可以复用同一套页面操作代码减少重复量。举个最小例子。假设要写一个登录功能的测试先定义登录页面类from selenium.webdriver.common.by import By class LoginPage: def __init__(self, driver): self.driver driver def input_username(self, username): self.driver.find_element(By.ID, username).send_keys(username) def input_password(self, password): self.driver.find_element(By.ID, password).send_keys(password) def click_login(self): self.driver.find_element(By.ID, login-btn).click()测试用例就变得很干净def test_login_success(driver): driver.get(https://example.com/login) login LoginPage(driver) login.input_username(admin) login.input_password(123456) login.click_login() assert 欢迎 in driver.page_source等你真正写出了第一个Page Object类你会立刻感受到“代码能维护”和“代码能跑”之间的差别。这个模式不复杂但它是从“测试脚本”跨越到“测试工程”的分水岭。7.2 参数化与数据驱动另一个快速提升测试价值的方向是参数化。所谓参数化就是同一个测试逻辑用多组数据去跑。比如“登录”测试用正确的用户名密码、空的用户名、密码错误、账号锁定各种组合。手工写N个用例又累又冗余pytest的参数化功能一行就能搞定import pytest pytest.mark.parametrize(username,password,expected, [ (admin, 123456, 登录成功), (admin, wrong, 密码错误), (, 123456, 用户名不能为空), ]) def test_login(username, password, expected): # 这里调用Page Object操作页面 ...参数化配合Page Object就是数据驱动测试的雏形。再往深了走可以把测试数据抽到JSON、YAML或Excel文件里用pytest读取文件内容生成测试用例这样即使不懂代码的测试人员也能通过改数据文件来添加测试场景。这一步做完了你的自动化测试就从一个“脚本集合”变成一个“数据驱动的测试平台”了。7.3 从Web扩展到AppAppiumWeb自动化跑顺之后很多人会想移动端App能不能也用类似的方式做自动化答案是可以而且思路非常接近。Appium就是移动端的“Selenium”它复用了WebDriver的协议把App里的控件当成“元素”用类似的定位器方式去操作。也就是说你现在学的Selenium技能不是只能吃Web这碗饭它学习Appium时有很大的“迁移红利”。Appium的架构是Appium Server作为中间服务接收测试代码发送的请求再把这些指令通过各平台对应的自动化驱动iOS的XCUITest、Android的UIAutomator转发给设备上的App。和Selenium不同的地方在于你需要配置Android SDK或者Xcode环境需要在模拟器或真机上运行还要处理App的启动、权限弹窗、Toast消息这些移动端特有的问题。如果你在Web端已经熟练掌握了元素定位、显式等待、Page Object学Appium时会发现很多概念都是现成的只是把driver.get(url)换成了driver.launch_app()把By.ID换成了By.ID加App的包名。7.4 新手学习路线的三点建议最后我想给新手一点个人建议纯经验之谈。第一条不要在一开始就追求完美框架。我见过太多人第一周就开始折腾Page Object、数据驱动、CI集成结果基础定位还没学扎实遇到问题都不知道怎么排查。先跑通最简脚本再逐步加层这个顺序比什么都重要。第二条一定要学会看报错信息。很多新手一报错就把整个报错截图发群里问但真正的排查起点是读第一条错误信息搞清楚是“找不到元素”还是“点击无效”还是“驱动问题”不同类型的错误处理方式完全不同。第三条多维护几个自己的测试脚本拿身边真实的项目练手。你不用非等到公司安排任务才去学随便找一个Web网站自己写一遍核心流程的自动化再把不稳定用例修到稳定这就是最好的面试作品。自动化测试这条路上没有捷径但也没有想象中那么难。把Selenium玩明白只是第一关后面还有pytest、框架设计、接口自动化、持续集成一整个知识体系在等着你。希望这篇内容能帮你把第一块基石打稳。
返回列表