Selenium UI自动化测试入门:从环境搭建到实战脚本编写
1. 项目概述:为什么现在学Selenium UI自动化测试依然有价值?
如果你是一名测试工程师,或者是一名想提升效率的开发,最近可能被各种AI测试工具、低代码平台的消息刷屏。很多人会问,现在学Selenium这种“传统”的UI自动化测试框架,是不是过时了?我的答案是:不仅没过时,现在反而是打好基础、理解自动化测试本质的最佳时机。Selenium就像编程里的C语言,它可能不是最高效、最酷炫的工具,但它能让你透彻理解浏览器自动化是如何在底层工作的。当你理解了Selenium如何定位一个按钮、如何模拟一次点击,你再去用那些封装好的AI测试工具,就会明白它们到底在帮你做什么,以及当它们“失灵”时,问题可能出在哪里。
这个项目,就是带你从零开始,搭建一个可运行、可维护的Selenium UI自动化测试脚本。我们不追求大而全的框架,而是聚焦于解决一个核心问题:如何让代码代替你的手,在浏览器里完成一系列操作,并告诉你结果对不对。你会学到环境搭建、元素定位、常用操作、等待机制以及如何组织简单的测试用例。学完之后,你不仅能写出自己的第一个自动化脚本,更能获得一种“自动化思维”,这种思维是应对未来任何测试工具变迁的底层能力。
2. 环境准备与核心工具链解析
开始写代码之前,先把“战场”布置好。一个稳定的环境是自动化测试成功的一半,很多新手卡在第一步就是因为环境没配好。
2.1 浏览器与驱动:自动化测试的“手”和“脚”
Selenium本身只是一个发出指令的“大脑”,它需要两个关键部件才能操作浏览器:
- 浏览器:被操作的对象,如Chrome、Firefox。建议使用Chrome,因为其生态最完善,问题也最容易搜索到解决方案。
- 浏览器驱动:连接“大脑”和“手”的桥梁。Selenium通过驱动来控制浏览器。每个浏览器版本都需要对应版本的驱动。
驱动下载与配置的黄金法则:驱动版本必须与浏览器主版本号一致。比如你用的是Chrome 125版本,就必须下载ChromeDriver 125.x.x.x版本。
注意:千万不要在搜索引擎里随便下载驱动,一定要去官方或镜像站。最稳妥的方式是使用包管理工具,或者从淘宝NPM镜像等可信源下载。
这里以Chrome为例,给出一个万无一失的配置流程:
- 查看你的Chrome版本:在浏览器地址栏输入
chrome://settings/help。 - 根据版本号,访问ChromeDriver的官方下载站或国内镜像站,下载对应版本的驱动。
- 将下载的
chromedriver.exe(Windows)或chromedriver(Mac/Linux)文件放在一个你记得住的目录,比如D:\WebDriver。 - 将这个目录的路径添加到系统的环境变量
PATH中。这一步至关重要,它让Python能在任何位置找到驱动。
实操心得:我习惯将驱动放在项目根目录下的一个drivers文件夹里,然后在代码中指定绝对路径。这样做的好处是项目环境独立,换机器时直接把整个文件夹拷走就行,避免了因系统环境变量不同而导致的问题。
2.2 Python与Selenium库:测试脚本的“大脑”
我们选择Python作为编程语言,因为它语法简洁,生态丰富,是自动化测试领域的事实标准。
- 安装Python:从官网下载安装,务必勾选“Add Python to PATH”。安装后,在命令行输入
python --version确认安装成功。 - 安装Selenium库:打开命令行,运行
pip install selenium。国内如果速度慢,可以使用清华镜像源:pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple。
2.3 IDE的选择:写代码的“书房”
对于初学者,我强烈推荐使用PyCharm(社区版免费)或VS Code。
- PyCharm:开箱即用,对Python支持极好,调试功能强大。新建一个纯Python项目,就可以开始写了。
- VS Code:轻量灵活,需要安装Python扩展,但配置好后同样强大。
选择哪一个取决于你的习惯。我的建议是,如果你主要做Python开发,PyCharm更省心;如果你需要频繁切换多种语言,VS Code更合适。
3. 第一个Selenium脚本:从“Hello World”到真实操作
环境就绪,我们来写第一个脚本。这个脚本的目标是打开百度,搜索一个关键词,并验证搜索结果页的标题。
3.1 脚本骨架与核心对象:WebDriver
所有Selenium脚本都始于一个WebDriver对象。你可以把它想象成你赋予灵魂的一个“虚拟用户”。
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys import time # 1. 创建WebDriver实例,启动浏览器 # 指定驱动的绝对路径,这是最稳妥的方式 driver = webdriver.Chrome(executable_path=r‘D:\WebDriver\chromedriver.exe‘) # 如果你已将驱动加入PATH,也可以简写为:driver = webdriver.Chrome() # 2. 控制浏览器打开目标网址 driver.get(‘https://www.baidu.com‘) # 最大化窗口,避免元素因窗口太小而被隐藏 driver.maximize_window() # 等待2秒,让页面加载(这是初级做法,后面会改进) time.sleep(2) # 3. 定位搜索框,并输入关键词 # 通过元素的‘id‘属性定位,这是最快最准的方式 search_box = driver.find_element(By.ID, ‘kw‘) # 先清空搜索框(避免有默认文本) search_box.clear() # 输入文本 search_box.send_keys(‘Selenium自动化测试‘) # 模拟键盘回车键进行搜索 search_box.send_keys(Keys.RETURN) # 4. 等待结果加载,并验证 time.sleep(3) # 等待搜索结果加载 # 验证页面标题是否包含关键词 assert ‘Selenium自动化测试‘ in driver.title print(‘测试通过!页面标题包含搜索关键词。‘) # 5. 查看当前页面URL(可选,用于调试) print(‘当前页面URL:‘, driver.current_url) # 6. 关闭浏览器 driver.quit()逐行解析:
webdriver.Chrome():初始化一个Chrome浏览器的驱动对象。这是所有操作的起点。driver.get(url):让浏览器导航到指定URL。driver.find_element(By.ID, ‘kw‘):这是元素定位,是UI自动化的核心。这里我们用By.ID定位器,找到了百度首页搜索框(其HTML的id属性是kw)。send_keys():向输入框输入文本或模拟按键。driver.title:获取当前浏览器标签页的标题。driver.quit():关闭浏览器并释放驱动资源。务必调用此方法,否则后台会残留浏览器进程。
第一个坑:运行后你可能看到浏览器一闪而过。这是因为脚本执行速度很快,执行到driver.quit()就结束了。我们用了time.sleep()来让脚本暂停,以便观察。但在真实测试中,绝对不要用sleep来等待页面加载,这是极不稳定的做法。我们马上会讲更科学的等待方式。
3.2 元素定位:自动化测试的“眼睛”
找不到元素,一切操作都是空谈。Selenium提供了8种主要的定位方式,我把它们分为三个梯队:
第一梯队(首选,稳定可靠):
By.ID:通过HTML元素的id属性定位。id在理想情况下应该是唯一的,定位速度最快。如果元素有id,优先用它。By.NAME:通过name属性定位。常用于表单元素。By.CLASS_NAME:通过class属性定位。注意,一个元素可能有多个class,这里匹配的是其中一个。
第二梯队(灵活,但需谨慎):
By.TAG_NAME:通过标签名定位,如<input>,<a>。通常一个页面有很多同类标签,不唯一,常与其他方法结合使用或用于查找多个元素。By.LINK_TEXT:通过超链接的完整文本定位。如<a href=“...”>登录</a>,可以用By.LINK_TEXT, “登录”。By.PARTIAL_LINK_TEXT:通过超链接的部分文本定位。如上例,“录”也能定位到。比LINK_TEXT宽松。
第三梯队(强大但复杂):
By.CSS_SELECTOR:通过CSS选择器定位。功能极其强大,可以组合各种条件,是前端工程师的最爱。例如#kw表示id为kw的元素,.s_ipt表示class包含s_ipt的元素。By.XPATH:通过XML路径语言定位。功能最强大,可以遍历整个DOM树,定位任何元素。但表达式可能很复杂,且性能稍差。例如//input[@id=‘kw‘]。
定位策略建议:
- 优先级:ID > Name > Class Name > CSS Selector > XPath > Link Text > Tag Name。
- 如何查看元素属性:在浏览器页面按F12打开开发者工具,使用左上角的箭头工具点击页面元素,即可在右侧的“Elements”面板看到其HTML代码和属性。
- 绝对不要用浏览器自动生成的XPath!开发者工具可以复制XPath,但那些路径往往又长又脆弱,页面结构微调就会失效。应该学习编写简洁、有弹性的XPath或CSS Selector。
4. 核心操作与等待机制:让脚本稳定运行
学会了定位,我们就要让元素“动”起来。同时,解决页面加载的等待问题,是脚本从“玩具”变为“工具”的关键。
4.1 常用浏览器操作API
除了之前用到的send_keys,以下操作也极为常用:
- 点击:
element.click() - 清空输入框:
element.clear() - 获取元素文本:
element.text - 获取元素属性值:
element.get_attribute(‘attribute_name‘),例如获取一个链接的href:get_attribute(‘href‘) - 判断元素是否可见/可用:
element.is_displayed(),element.is_enabled() - 浏览器导航:
driver.back():后退。driver.forward():前进。driver.refresh():刷新。
- 窗口操作:
driver.maximize_window():最大化。driver.set_window_size(width, height):设置大小。driver.get_window_position(),driver.set_window_position(x, y):获取/设置位置。
4.2 等待机制:告别time.sleep,拥抱智能等待
time.sleep(秒数)是固定等待,无论页面是否加载完成,都要傻等那么久。这会导致测试效率极低且不稳定。Selenium提供了两种智能等待:
1. 隐式等待 (Implicit Wait)在创建WebDriver后设置一次,对整个驱动生命周期有效。它告诉WebDriver:在查找任何一个元素时,如果元素没有立即出现,就轮询等待一段时间(比如10秒),直到找到为止。如果超过时间还没找到,才抛出异常。
driver = webdriver.Chrome() driver.implicitly_wait(10) # 单位:秒 # 此后,所有 driver.find_element 操作都会最多等待10秒2. 显式等待 (Explicit Wait)更精细、更强大的等待方式。你可以为某个特定的操作设置等待条件,比如“等待这个按钮可点击”、“等待这个文本出现”。这是推荐的最佳实践。
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待最多10秒,直到ID为‘su‘的元素(百度一下按钮)可被点击 wait = WebDriverWait(driver, 10) search_button = wait.until(EC.element_to_be_clickable((By.ID, ‘su‘))) search_button.click() # 其他常用条件: # EC.presence_of_element_located((By.ID, ...)) # 元素出现在DOM中 # EC.visibility_of_element_located((By.ID, ...)) # 元素可见 # EC.title_contains(‘百度一下‘) # 标题包含某文字 # EC.alert_is_present() # 出现警告框等待策略黄金组合:
- 全局设置一个较短的隐式等待,例如
driver.implicitly_wait(5),作为兜底。 - 在关键步骤(如点击后页面跳转、异步加载)使用显式等待,指定明确的条件。
- 彻底弃用
time.sleep,除非在极少数需要固定暂停的调试场景。
4.3 处理弹窗、iframe和下拉框
弹窗(Alert/Confirm/Prompt):
# 切换到弹窗 alert = driver.switch_to.alert # 获取弹窗文本 print(alert.text) # 点击确认 alert.accept() # 点击取消(如果是confirm) # alert.dismiss() # 输入文本(如果是prompt) # alert.send_keys(‘text‘)iframe(内嵌框架): 如果元素位于iframe内部,你必须先“切换”到那个iframe上下文,才能操作其中的元素。
# 通过ID或Name切换 driver.switch_to.frame(‘iframe_id_or_name‘) # 通过元素对象切换 iframe_element = driver.find_element(By.TAG_NAME, ‘iframe‘) driver.switch_to.frame(iframe_element) # 操作完iframe内的元素后,切回主页面 driver.switch_to.default_content()下拉选择框(Select): 对于标准的<select>标签,使用Select类更方便。
from selenium.webdriver.support.ui import Select select_element = driver.find_element(By.ID, ‘city‘) select = Select(select_element) # 通过可见文本选择 select.select_by_visible_text(‘北京‘) # 通过value属性选择 # select.select_by_value(‘beijing‘) # 通过索引选择(从0开始) # select.select_by_index(1)5. 构建可维护的测试用例与常见问题排查
单个脚本跑通只是开始。如何组织多个测试用例,以及如何应对执行过程中的各种问题,才是工程化的体现。
5.1 使用Unittest/Pytest组织测试用例
我们不把代码全写在一个文件里。使用测试框架可以让用例管理、执行和报告更规范。这里以Python自带的unittest为例。
创建一个文件test_baidu_search.py:
import unittest from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class TestBaiduSearch(unittest.TestCase): """百度搜索测试用例类""" def setUp(self): """每个测试方法执行前运行,用于初始化""" self.driver = webdriver.Chrome() self.driver.implicitly_wait(5) self.driver.maximize_window() self.wait = WebDriverWait(self.driver, 10) def tearDown(self): """每个测试方法执行后运行,用于清理""" self.driver.quit() def test_search_by_keyword(self): """测试通过关键词搜索""" driver = self.driver wait = self.wait driver.get(‘https://www.baidu.com‘) search_box = driver.find_element(By.ID, ‘kw‘) search_box.clear() search_box.send_keys(‘Python‘ + Keys.RETURN) # 显式等待搜索结果标题出现 wait.until(EC.title_contains(‘Python‘)) self.assertIn(‘Python‘, driver.title) def test_search_empty_keyword(self): """测试搜索空关键词(应停留在首页)""" driver = self.driver driver.get(‘https://www.baidu.com‘) search_box = driver.find_element(By.ID, ‘kw‘) search_box.clear() search_button = driver.find_element(By.ID, ‘su‘) search_button.click() # 点击后应该还是百度首页 self.assertEqual(driver.title, ‘百度一下,你就知道‘) if __name__ == ‘__main__‘: unittest.main()这样,你可以通过命令行运行所有测试:python -m unittest test_baidu_search.py。setUp和tearDown保证了每个测试用例的独立性。
5.2 常见问题与排查技巧实录
即使代码写得再好,运行时也会遇到各种“妖魔鬼怪”。下面是我踩过无数坑后总结的排查清单。
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
NoSuchElementException(找不到元素) | 1. 定位表达式写错了。 2. 元素在iframe里。 3. 页面还没加载完,元素不存在。 4. 元素是动态生成的(JS渲染)。 5. 页面有多个相同定位的元素,找到的是第一个但不可操作。 | 1.F12检查:确认定位器是否能唯一定位到目标元素。 2.检查iframe:看目标元素是否在 <iframe>内,需要切换。3.增加等待:使用显式等待,条件设为元素可见或可点击。 4.等待动态内容:可能需要等待某个特定元素出现作为标志。 5.改用 find_elements:获取列表后操作指定索引的元素。 |
ElementNotInteractableException(元素不可交互) | 1. 元素被遮挡(如弹窗、蒙层)。 2. 元素不可见( display: none或visibility: hidden)。3. 元素未处于可操作状态(如disabled)。 | 1.滚动到元素:driver.execute_script(“arguments[0].scrollIntoView();“, element)。2.等待遮挡物消失:检查并关闭可能遮挡的弹窗。 3.检查元素状态:用 is_displayed()和is_enabled()判断。4.尝试JS点击: driver.execute_script(“arguments[0].click();“, element)(终极手段)。 |
| 脚本被网站识别为自动化工具 | 一些网站会检测Selenium的特征(如window.navigator.webdriver属性)。 | 1.添加实验性选项:初始化驱动时添加参数来隐藏特征(此方法可能随浏览器更新失效)。 2.使用 undetected-chromedriver:一个专门应对检测的第三方库。3.降低操作频率:在关键步骤间加入随机等待时间,模拟真人操作。 |
| 浏览器启动后立刻闪退 | 1. 浏览器驱动版本与浏览器不匹配。 2. 驱动路径错误或未加入PATH。 3. 代码最后没有 driver.quit(),但进程被强制结束。 | 1.核对版本:严格检查Chrome和ChromeDriver的主版本号。 2.指定绝对路径:在代码中明确指定 executable_path。3.使用 try...finally:确保无论如何quit()都会执行。 |
| 运行速度慢 | 1. 大量使用time.sleep。2. 网络环境差。 3. 隐式等待时间设置过长。 | 1.全面改用显式等待。 2.优化定位器:优先使用ID等快速定位方式。 3.适当缩短隐式等待:设置为3-5秒。 |
一个高级调试技巧:在代码中插入driver.save_screenshot(‘debug.png‘),可以在出错时截屏,直观地看到当时浏览器页面的状态,对于排查动态页面问题非常有用。
6. 迈向下一步:从脚本到框架的思考
当你能够熟练编写并运行多个类似上面的测试用例后,你可能会遇到新的问题:测试数据怎么管理?报告怎么生成?用例怎么在服务器上定时跑?这时,你就需要向“测试框架”进化了。
这不是一蹴而就的,但你可以从这些方向逐步演进:
- 页面对象模型:这是UI自动化测试最重要的设计模式。将每个页面(或页面组件)封装成一个类,页面的元素定位和操作作为这个类的方法。测试脚本只调用这些方法,不与具体的定位器耦合。这样,当页面UI改动时,你只需要修改对应的页面类,而不需要修改大量测试脚本。
- 数据驱动:将测试数据(如搜索关键词、登录账号)从代码中分离出来,存放在Excel、JSON、YAML或数据库中。测试脚本读取这些数据来执行,实现一套脚本测试多组数据。
- 测试报告:使用
HTMLTestRunner、Allure等库生成美观的HTML测试报告,清晰地展示用例通过率、失败原因和截图。 - 持续集成:将你的测试框架接入Jenkins、GitLab CI等工具,实现代码提交后自动触发测试,或者每天定时执行,并将结果通过邮件、钉钉等通知团队。
入门Selenium,你获得的不仅仅是一个工具的使用技能,更是一套解决“如何让程序模拟人操作软件”这一问题的思维模型。这个模型是通用的,无论将来你是去玩Appium做移动端自动化,还是研究Playwright这个更现代的工具,亦或是去理解那些宣称“AI自动化”的测试平台,你都会发现万变不离其宗。核心永远是:定位元素、执行操作、验证结果。把Selenium的基础打牢,未来你在自动化测试的路上,才能走得更稳、更远。
