☰
Selenium自动化测试入门:环境搭建、元素定位与pytest框架实战
2026/9/26 18:02:12 网站建设 项目流程

Selenium 在自动化测试里的地位,有点像建筑行业里的水泥——看着不起眼,但几乎所有人入门都绕不开它。它本质上是一套浏览器自动化工具,通过代码驱动真实浏览器去完成点击、输入、滚动、切换页面这些操作,再把结果交给断言去校验。它解决的核心问题很朴素:把重复、耗时、容易出错的回归测试变成机器自动执行的任务,让测试人员从机械点击里解放出来。如果你是想转自动化测试的测试工程师、想补测试能力的开发同学,或者是刚接触软件测试的在校生,从 Selenium 入门几乎是成本最低、资料最全的一条路。这篇内容我会从环境搭建一路讲到 pytest 框架和报告生成,中途会把元素定位、等待策略、常见坑位整个过一遍。

1. 自动化测试的整体设计思路:动手之前先盘一盘

1.1 为什么新手都从 Selenium 入手

很多刚接触自动化测试的人会纠结“怎么选工具”。市面上能听到的选项不少:Appium 主打移动端自动化,Playwright 是微软出品的下一代浏览器自动化方案,TSMaster 则偏向汽车总线与硬件测试,跟网页 UI 自动化关系不大。可一旦打开招聘软件看岗位要求,Selenium 依然是出现频率最高的关键词,没有之一。

这里有个容易被忽略的点:Selenium 是开源项目,背后有庞大的社区和十几年的真实项目沉淀。你踩过的坑,大概率早就有人踩过并留下了解决方案。这种“老”反而是新手的红利。相比之下,Playwright 虽然在 API 设计和稳定性上有优势,但很多公司内部遗留的自动化资产、脚本、框架底座都是基于 Selenium 的,入职后你还是要先接住这些存量代码。先学 Selenium,再去看 Playwright,通路是顺畅的;反过来,一上来就只玩新工具,进公司面对老框架反而容易懵。

从学习曲线来说,Selenium 的命令风格很直白:找到元素、操作元素、校验结果。三个动作循环往复,覆盖了绝大多数 UI 自动化场景。下面这张表可以帮你快速建立工具认知:

工具定位主要场景上手难度
SeleniumWeb UI 自动化浏览器端回归测试、冒烟测试、兼容性测试低
Appium移动端自动化Android / iOS App 的 UI 测试中
PlaywrightWeb UI 自动化需要多浏览器并发、无头浏览器场景低
TSMaster汽车总线测试车载通信、硬件仿真与诊断高

1.2 自动化测试能解决什么,不能解决什么

有人会把自动化测试想得很神,觉得有了脚本就能替代所有手工测试。实际上它最擅长的领域是回归测试和冒烟测试:版本频繁迭代时,把已经稳定的功能脚本化,每次发版前自动跑一遍,快速确认核心链路没被改坏。它也能干一些人工不爱干的重复活,比如批量导入数据后校验界面展示、多浏览器下截图比对、接口返回与页面显示的一致性检查。

但自动化测试解决不了探索性测试的问题。新功能怎么做用户才顺手、界面配色是否合理、交互是否符合直觉,这些需要人去看、去试、去判断。还有一类场景自动化做起来性价比极低:需求每周大变、页面结构天天调整,脚本改动成本高过手工执行成本,这种情况就别硬上自动化。把自动化测试理解成“把人的判断力留在测试设计阶段,把重复执行交给机器”,定位就准确了。

2. 环境准备:搭建一套能跑通的 Selenium 开发环境

2.1 Python、虚拟环境与依赖安装

Selenium 支持 Java、Python、C#、Ruby 多种语言,但国内测试圈最主流的是 Python,主要原因是语法简单、第三方库丰富,写自动化脚本效率高。环境准备第一步就是装 Python。建议直接装 3.8 以上版本,到官网下载安装包时记得勾选“Add Python to PATH”,省得后面命令行找不到 python。

装完 Python,我强烈建议你先建一个虚拟环境,不要一股脑把包装到全局。虚拟环境隔离项目依赖,换个项目也不会污染全局包,这是自动化测试工程化的第一步。在项目根目录执行这三条命令就能完成初始化:

python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install selenium

这里有个实际经验:很多同学卡在安装成功但运行报 ModuleNotFoundError,十有八九是 IDE 里选的解释器不是当前虚拟环境。在 PyCharm 里要手动选择 venv 下的 Python 路径,在 VSCode 里则要点击右下角解释器重新选择。

2.2 浏览器驱动:最容易踩的版本坑

Selenium 本身只负责发指令,真正把指令翻译成浏览器动作的是一个叫 WebDriver 的程序。Chrome 驱动就叫 chromedriver,Firefox 驱动叫 geckodriver,Edge 驱动叫 msedgedriver。浏览器驱动必须和浏览器版本对应,如果对不上,运行时会直接报类似 SessionNotCreatedException 的错误,提示版本不匹配。

手动下载驱动需要先去 Chrome 地址栏输入 chrome://version 看版本号,再去驱动下载站找对应版本,然后放到 Python 路径或指定路径。这套流程第一遍能跑通,但每到浏览器自动升级之后就会再次踩坑。更省心的方式是用 webdriver-manager 这个库自动匹配版本:

pip install webdriver-manager

有了它,脚本里就不用再操心驱动路径了。下面这段代码就是完整的环境验证:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.get("https://www.baidu.com") print(driver.title) driver.quit()

第一次运行会自动下载对应版本的 chromedriver,之后每次启动先检查本地版本,不匹配就自动更新。这一步能帮你省掉后续非常多的环境问题。

2.3 第一个脚本:打开浏览器并输出页面标题

上面这段代码看起来简单,但它已经把 Selenium 的核心流程带出来了:创建 WebDriver 实例、发起访问、读取信息、退出浏览器。其中最关键的是最后一行 driver.quit(),很多新手会随手写成 driver.close()。close 只关闭当前标签页,quit 才会结束整个浏览器进程。如果用 close,Windows 上经常会看到 chrome.exe 进程残留,跑几次测试电脑就卡得不行。

读取页面信息时,driver.title 可以直接拿到浏览器标签页上的标题,driver.current_url 可以拿到当前地址,这两项常常被用来做最基础的断言。第一个脚本跑通之后,你可以再试着加一句 WebDriverWait 等待页面加载完成,但这部分我放到后面专门讲。先确保脚本能稳定打开浏览器、拿到结果、正常退出,就算跨过第一关了。

3. 元素定位:让脚本精准找到页面控件

3.1 八大定位方式:各怀绝技

自动化测试所有操作的前提是先找到元素。Selenium 提供了八种定位方式,分别是 id、name、class name、tag name、link text、partial link text、xpath、css selector。它们的适用场景差异很大,整理成表会看得更清楚:

定位方式用法示例适用场景注意点
idfind_element(By.ID, "login")元素有唯一的 id 属性首选,定位最稳定
namefind_element(By.NAME, "username")表单控件有 name 属性页面中可能重复
class namefind_element(By.CLASS_NAME, "btn")按 CSS 类名定位类名可能有多个,取一个即可
tag namefind_element(By.TAG_NAME, "input")批量处理同类型标签命中范围过大
link textfind_element(By.LINK_TEXT, "登录")精确定位超链接文本只能用于 a 标签
partial link textfind_element(By.PARTIAL_LINK_TEXT, "登")部分匹配超链接可能匹配到多个
xpathfind_element(By.XPATH, "//input[@id='login']")综合条件定位,可层级搜索语法稍复杂,但最灵活
css selectorfind_element(By.CSS_SELECTOR, "#login")用 CSS 语法定位性能好,但无法按文本定位

我的建议是优先级从高到低:id 能用就不用别的,id 没有再看 name 或 class,都没有最后上 xpath。id 在同一个页面里理论上唯一,定位失败率最低。class name 很可能一个元素同时有多个类名,你只需要传其中一个就行,不用全写。

3.2 XPath 和 CSS 选择器怎么选

XPath 是几乎所有自动化测试人员都要掌握的技能,因为总会有元素既没有 id 也没有 name。它分为绝对路径和相对路径两种写法。绝对路径从 html 根节点开始逐层往下写,页面结构稍微调整就会失效,日常工作中基本只用相对路径。

下面这几个 XPath 写法覆盖了高频场景:

# 按属性精确匹配 driver.find_element(By.XPATH, "//input[@id='kw']") # 模糊匹配属性值 driver.find_element(By.XPATH, "//div[contains(@class, 'search')]") # 文本内容匹配 driver.find_element(By.XPATH, "//button[text()='登录']") # 多个条件组合 driver.find_element(By.XPATH, "//input[@name='username' and @type='text']") # 按位置索引 driver.find_element(By.XPATH, "(//div[@class='item'])[2]")

CSS Selector 对比 XPath 的优势是写法更简洁、执行性能更好。比如 id 定位在 CSS 里写成 #login,class 定位写成 .btn,属性定位写成 [name='username']。但 CSS 有一个天然缺陷:它不能按元素文本内容定位。所以遇到“必须根据页面上显示的文字找按钮”的场景,还是得回头用 XPath。

我自己写定位时有个习惯:先去浏览器开发者工具里右键元素 Copy Copy XPath,拿回来作为参考,再手动改成相对路径版本。直接用浏览器生成的绝对路径虽然能跑,但维护成本很高,一次前端改版就可能整条脚本报废。

3.3 定位不到元素的排查顺序

自动化测试最经典的报错就是 NoSuchElementException,碰到这个先别急着改代码,按顺序排查往往半小时内能定位。首先是看元素是不是在 iframe 里,iframe 相当于页面中嵌套的另一个文档,Selenium 默认只操作主文档,必须先用 driver.switch_to.frame() 切进去才能看到里面的元素。

然后是看元素是否是动态加载的。很多页面采用异步渲染,脚本执行到 find_element 时元素还没出现,这时候正确的做法不是把定位语句复制三遍,而是用显式等待等它出现。再往下就要检查你的定位表达式是否命中了多个元素,find_element 只返回第一个,如果页面有几个相同类名的元素,你操作的很可能不是目标那个。

还有一个容易被忽略的点:元素可能在 DOM 中存在但不可见,比如被遮罩层挡住、处于折叠状态、或者设置了 display:none。判断一个元素是否真正可操作,不仅要看它是否存在,还要看它是否可见、是否可点击,这部分就是下一章等待策略要解决的问题。

4. 浏览器操作与等待策略:把稳定性刻进 DNA

4.1 高频操作:浏览器级与元素级

Selenium 里元素操作没有太多玄机,核心就几个方法:click 点击、send_keys 输入、clear 清空、text 获取文本、is_selected 判断选中状态。真正需要留个心的是浏览器级别的操作,包括页面跳转、窗口尺寸控制、截图以及 Cookie 处理,这些方法在稳定性排查和问题定位时特别有用。

# 浏览器级操作 driver.get("https://www.example.com") # 打开页面 driver.refresh() # 刷新页面 driver.back() # 后退 driver.forward() # 前进 driver.maximize_window() # 最大化窗口 driver.set_window_size(1366, 768) # 指定窗口尺寸 driver.get_screenshot_as_file("page.png") # 截图保存 driver.get_cookies() # 查看当前页面的 Cookie # 元素级操作 element = driver.find_element(By.ID, "kw") element.clear() element.send_keys("Selenium 入门") element.click() print(element.text)

截图是一个非常实用的调试手段。脚本执行到某一步失败时,把当时页面截图和当前 URL 记录到日志里,能省下大量“靠猜”的时间。很多团队会把失败截图自动挂到测试报告上,后面对接 Allure 时可以一起实现。

4.2 三类等待:强制、隐式、显式的本质

页面加载慢是自动化测试不稳定的最大来源。早期很多人图省事,直接在代码里写 time.sleep(3),这就是强制等待。它的逻辑很简单:不管页面加载完了没有,先睡满三秒再说。问题在于网络快的时候白白浪费三秒,网络慢的时候三秒又不够,脚本越跑越脆。

隐式等待是对 driver 的全局设置,设置之后每个 find_element 都会在元素没找到时进行轮询查找,直到超过设定时间才抛出异常。典型写法是 driver.implicitly_wait(10)。它虽然好用,但只针对“元素存在”,不管元素是否可见、是否可点击。如果按钮被遮罩挡住,隐式等待等到天亮也不会帮你处理。

显式等待则是针对特定条件的等待,最常用的是 WebDriverWait 配合 expected_conditions。它每隔一段时间去检查一次指定条件,满足后立即返回,超时才抛出 TimeoutException。这才是解决“动态渲染元素”的正解,下面的代码示意了三种等待的核心用法:

import time from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By driver = webdriver.Chrome() # 强制等待:不推荐在生产脚本中使用 time.sleep(3) # 隐式等待:全局轮询查找元素存在 driver.implicitly_wait(10) # 显式等待:等待元素可点击 element = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "submit")) ) element.click()

4.3 我的等待实践:尽量不写裸 sleep

写自动化脚本久了,我的等待策略基本就一句话:能用显式等待解决就不用隐式等待,能不用强制等待就不用。如果页面首次打开时加载很慢,可以在入口处加一个 WebDriverWait 等待某个页面特征元素出现,比如等待登录框可输入,再去处理后续逻辑。

显式等待里最常用的条件有这么几个:presence_of_element_located 等待元素出现在 DOM 中,visibility_of_element_located 等待元素可见,element_to_be_clickable 等待元素可点击,staleness_of 等待元素从 DOM 中移除,常用于判断页面是否已经开始跳转。

还有一个小经验:不要在网页一打开时就立刻点击,也不要盲目把等待时间设成 60 秒。等待时间设太长,失败用例的执行时间会被拉得很夸张。一般 10 到 15 秒够用,特殊情况再单独调。

5. 用 pytest 加 Selenium 搭起一套可维护的框架

5.1 pytest 是自动化测试的骨架

单个 Selenium 脚本只能做验证,要把它变成一套可持续运行的测试体系,需要引入测试框架。pytest 是 Python 生态里最主流的测试框架,它没有复杂的配置,写起来非常顺。它提供给自动化的核心价值有三个:用例组织、断言报告、脚手架扩展。

pytest 的用例识别规则很简单:文件名以 test_ 开头,或者以test.py 结尾;文件内的测试函数以 test开头。一个最简单的测试长这样:

def test_login_page_title(): assert "登录" in "测试登录页面"

执行 pytest 后,它会自动搜索当前目录下符合条件的测试文件并运行。针对自动化测试,我们会在这个基础上扩展 fixture、参数化、Allure 报告,下面逐块讲。

5.2 fixture 管理浏览器实例的优雅玩法

fixture 是 pytest 对测试前置和后置逻辑的封装。比如每个测试用例前创建浏览器、用例结束后关闭浏览器,这个动作就可以定义成一个 fixture。它最大的优势是能设置作用域,让多个用例复用同一个浏览器实例,避免每个用例都重新启动浏览器导致的时间浪费。

import pytest from selenium import webdriver @pytest.fixture(scope="class") def driver(): driver = webdriver.Chrome() yield driver driver.quit() class TestSearch: def test_search(self, driver): driver.get("https://www.baidu.com") assert "百度" in driver.title

scope="class" 表示同一个测试类下的所有测试方法共享这一个 driver 实例,测试类执行完才执行 driver.quit()。如果场景需要每个用例独立浏览器,就把 scope 改成 function。不用 fixture 时,很多同学会把 driver 定义在全局模块里,写起来简单,但用例之间互相影响很难排查,fixture 可以保证每次测试的生命周期是受控的。

5.3 参数化驱动测试数据

自动化测试经常需要覆盖多组数据,比如不同搜索关键词、不同登录账号、不同金额边界值。pytest 的参数化装饰器能把你从复制多条用例中解放出来。

import pytest @pytest.mark.parametrize("keyword,expect", [ ("Selenium", "Selenium"), ("pytest", "pytest"), ("自动化测试", "自动化测试"), ]) def test_browser_search(driver, keyword, expect): driver.get("https://www.baidu.com") search_box = driver.find_element(By.ID, "kw") search_box.send_keys(keyword) search_box.submit() assert expect in driver.page_source

运行 pytest 时,这个函数会被展开成三条独立用例,参数一和参数二分别传入。将来要加数据,只需要往列表里追加元组,执行自动生成新的用例。参数化让测试数据和测试逻辑分离,代码维护成本直线下降。

5.4 集成 Allure 报告,让结果一目了然

测试跑完没有报告等于白跑,Allure 是目前最主流的自动化测试报告方案。安装依赖后就两步:先指定结果存放目录,再生成 HTML 报告。

pip install allure-pytest pytest --alluredir=./allure-results allure generate ./allure-results -o ./allure-report --clean allure open ./allure-report

Allure 报告最大的价值在于展示层级清晰:环境信息、用例步骤、失败日志、截图都能挂到对应用例里。把上一章提到的截图逻辑封装进 fixture 的钩子函数中,失败时自动截图并挂到报告上,排查问题会非常高效。很多公司面试时问自动化测试框架搭建,基本就是这个套路:pytest 管理用例、Selenium 执行操作、Allure 输出报告、再搭一层 Jenkins 做定时构建。

6. 常见问题排查与防坑手册

6.1 元素找不到:NoSuchElementException 的应对清单

这个异常是新手遇得最多的。除了前面说的 iframe 和动态加载原因,还有一个高频坑是大小写和空格。HTML 属性值是区分大小写的,class="search-btn" 不能写成 "Search-Btn"。另外属性前后可能有空格,比如 class="btn primary",定位时只能用其中一个类名,不能直接复制整个 class 字符串去匹配。

还有一种情况是元素在页面加载过程中被短暂替换。比如旧按钮先出现,框架渲染后替换成新按钮,脚本在替换瞬间抓到了旧按钮,点击时元素已经分离。这种问题表面上是 NoSuchElementException,实际是时序问题,需要用等待机制解决。

6.2 元素找到了但点击无效

元素存在且可见,但点击后没有任何反应,这种“假成功”比直接报错更头疼。常见原因是元素上面覆盖了一个透明遮罩层,尤其在弹窗、浮层出现时最容易发生。Selenium 的点击坐标落在元素中心点,如果遮罩层盖住了这个点,点击事件就被层级上方的元素消费掉了。

碰到这种情况,可以先尝试用 WebDriver.execute_script 让目标元素滚动到可视区域:

element = driver.find_element(By.ID, "submit") driver.execute_script("arguments[0].scrollIntoView();", element) element.click()

如果还是不行,用 JavaScript 直接触发点击事件也能绕过遮罩问题,但这属于非常规手段,能解决业务里特殊交互的卡点,不要滥用。

6.3 iframe 和多窗口切换

iframe 是自动化测试里一个大家族式的坑。页面里嵌了 iframe,直接 find_element 永远是找不到的,必须先切进去。切换的基础写法是 driver.switch_to.frame("frame_id"),切换回主文档用 driver.switch_to.default_content()。多层嵌套 iframe 需要逐层切换,顺序搞反就报错。

多窗口切换则是另一类高频问题。点击链接打开新标签页后,driver 还停留在旧页面,这时需要拿到所有窗口句柄再切换:driver.window_handles 返回当前所有窗口的句柄列表,新窗口在最末尾,切换过去后如果还想回到主窗口,用第一个句柄再切回来。这个流程在登录过程经常会遇到,因为很多第三方登录会弹出新窗口。

6.4 登录态与验证码问题

自动化测试最棘手的问题之一就是验证码。现在很多系统的登录流程都接入了图形验证码或滑块验证,这类验证码识别成本高,也不稳定。工程上的常规做法是绕过验证码本身:测试环境关闭验证码功能,或者由开发提供万能验证码,或者通过调用接口提前拿到登录态,再用 Cookie 写入浏览器实现免登录。

# 提前从接口获取登录后的 Cookie 写入当前浏览器 driver.get("https://www.example.com") driver.add_cookie({"name": "session_id", "value": "真实接口返回的cookie值"}) driver.refresh()

如果公司测试环境不允许关闭验证码,那就在自动化脚本里尽量复用已有会话的登录态,别让每条测试用例都走一遍完整的登录流程。

6.5 脚本不稳定与并发执行

脚本今天能过明天挂了,在 UI 自动化里太常见。跑挂之后先别急着改代码,按顺序排查:是不是页面响应慢导致等待超时,是不是测试数据被前一次执行改掉了,是不是用例之间有先后依赖。测试用例之间最好是完全独立的,每条用例都能单独跑,不要依赖上一条用例的中间状态。

并发执行可以用 pytest-xdist 插件,但它要求浏览器实例彼此隔离。每个进程用独立 driver 实例,不要共享同一个全局 driver。并发虽然能缩短执行时间,但对流量和设备要求高,小团队可以先别碰,先把单机稳定性跑出来再考虑并发。

写到最后,分享一点我的体会

我用 Selenium 做过从零到一的框架,也维护过几百条用例,最大的感受是:自动化测试的难点从来不是写脚本,而是控制不确定性。环境变了、页面变了、数据变了,脚本就跟着闹脾气。与其追求用一个巨复杂的框架把所有情况都兜住,不如先把定位、等待、容错这些基本功磨扎实。学 Selenium 不要只看教程,一定要亲手把环境、脚本、报告这串链路全部点亮一遍。点亮之后你会发现,什么 pytest、Allure、接口测试,其实都是在同一个底层逻辑上生长出来的。后续想深入,可以沿着数据驱动、关键字驱动、Selenium Grid 分布式执行这几条路继续走,但先把今天这些地基打稳,才是最重要的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询