首页 / 资讯中心 / 文章详情

Selenium自动化测试实战:从环境搭建到POM工程化全指南

Selenium自动化测试实战:从环境搭建到POM工程化全指南 ★ FEATURED ARTICLE
如果你在测试岗待过一段时间大概率会遇到这样一个画面产品迭代快到月底回归测试却要手动点几百个按钮点得人眼冒金星。所以我一直觉得Selenium是测试领域里最值得投入的第一个自动化工具——上手快、资料多、就算出问题也大多有迹可循。今天这篇不聊高大上的测试理论只聊我从装环境到把脚本跑成一个能持续回归的小工程期间实打实走过的路和踩过的坑。不管你是功能测试想转自动化、开发想兼职做产品验证还是刚学Python想找点能落地的练习项目这篇都可以当一份“从零到能干活”的参考。Selenium在Python生态里的定位很明确它把浏览器操作封装成了一套API让你用代码模拟真实用户去打开页面、点击、输入、断言结果。能做到这一步很多重复劳动就可以交给脚本去跑了。1. Selenium到底是什么一个“会操作浏览器”的机器人很多人第一次接触Selenium会把它和爬虫工具混为一谈。这其实是最大的误解。Selenium的核心价值不是“抓数据”而是“模拟行为”。它背后通过WebDriver协议和浏览器通信把我们的代码指令翻译成浏览器原生操作。你可以理解为有个机器人坐在电脑前按照你的剧本去点按钮、填表单、翻页面然后把执行结果反馈回来。这种设计天然适合Web测试因为测试的本质就是验证用户操作路径是否正常。在实际项目中我用Selenium主要解决三类问题回归测试每次发版前把核心业务流程登录、下单、支付、查询自动化跑一遍省去重复劳动力。跨浏览器验证同样的用例在Chrome、Firefox、Edge上各跑一遍确认前端兼容性。动态数据校验比如报表系统里不同条件下数字是否更新、图表是否渲染完毕这些用接口测试不好覆盖必须靠界面断言。不过也要清楚它的边界。如果目标是短时间内抓取几十万条数据Selenium并不是最佳选择它的启动成本和内存开销远高于直接发HTTP请求。性能测试、桌面客户端测试也不是Selenium的强项。我的经验是别指望一个工具包打天下Selenium负责“用户视角的功能验证”这一块就够了选型选对了后面的精力才不会白费。2. 从Python环境到WebDriver环境搭好后才能谈自动化环境搭建这一步看着简单但其实坑最多。很多新手脚本写得没问题最后卡在环境上一运行就报错根本进不了业务逻辑。2.1 Python安装与PATH配置命令行不认识python怎么破我见过不少同学在官网下载了Python安装完后在命令行敲python --version结果提示“不是内部或外部命令”。这十有八九是安装时没有勾选Add Python to PATH。安装Python时有个勾选项记得选上。如果已经装完了就需要手动把Python的安装目录和Scripts子目录加到系统环境变量里。# 验证Python是否可用 python --version pip --version至于版本选择3.8以上都可以Selenium 4.x对3.7到3.12都支持得比较好。有一点我建议不要盲目追最新如果你的公司项目里有老框架依赖Python 3.9或3.10往往是最稳妥的。另外国内网络环境下直接用pip install selenium有时候会很慢这时候可以切换镜像源。注意这不算什么高阶操作但确实能帮你少等几分钟。pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple装完以后再用pip show selenium确认版本别一上来就写代码。2.2 Selenium库与WebDriver的版本匹配艺术Selenium本身只是个客户端库真正指挥浏览器干活的是WebDriver。Chrome有chromedriverFirefox有geckodriverEdge有edgedriver。版本匹配是整个环境搭建里最容易载跟头的地方——driver版本和浏览器主版本不一致启动就会报session not created。我在实际项目中总结了一套最小化踩坑的安装路径打开浏览器在地址栏输入chrome://version查看主版本号。去对应的driver下载地址选择相同主版本的最新子版本。把driver解压后放到Python的Scripts目录或者专门建一个drivers目录并加入PATH。从Selenium 4.6开始官方推出了Selenium Manager可以自动检测并下载匹配的driver。但国内网络环境下自动下载未必每次都顺利所以我仍然建议手动放置driver。手动放置一次花费五分钟但能保证后面一个月省心。2.3 用一个最小脚本验证整条链路环境到底通没通跑一个最小脚本就知道。我管这个叫“Hello World of Automation”from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() driver webdriver.Chrome(optionsoptions) try: driver.get(https://www.baidu.com) print(driver.title) finally: driver.quit()如果这一跑能打印出页面标题说明Python、selenium、driver、浏览器这一整条链路已经通了。很多新手就是卡在这一步之前所以强烈建议先把这段跑通再往后学。出了问题优先检查driver版本和浏览器版本这一步对了就成功一大半。3. 第一套能跑的自动化脚本骨架代码与等待策略环境通了以后就要开始写真正能完成业务验证的脚本了。这里最核心的不是语法而是对“页面加载时序”的理解。一个脚本能不能稳定跑大半取决于你会不会等待。3.1 启动浏览器、操作元素、断言的骨架代码一套完整的用例骨架基本是这个样子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() try: # 打开页面 driver.get(https://example.com/login) # 定位用户名输入框并输入 username WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, username)) ) username.send_keys(test_user) # 定位密码框并输入 driver.find_element(By.ID, password).send_keys(123456) # 点击登录按钮 driver.find_element(By.CSS_SELECTOR, button.btn-login).click() # 断言登录后页面包含欢迎语 WebDriverWait(driver, 10).until( EC.text_to_be_present_in_element((By.TAG_NAME, body), 欢迎回来) ) print(登录用例通过) finally: driver.quit()这段代码看起来简单但它涵盖了自动化的五个基本动作打开页面、定位元素、输入文本、点击、断言结果。把这五个动作练熟了大多数业务场景都能拆解成这五步的组合。3.2 为什么我强烈建议你不要用固定sleep很多从入门教程走过来的同学会在代码里写time.sleep(3)。它的逻辑很简单等三秒再继续执行。但问题在于三秒对慢页面不够对快页面又太多最终导致脚本要么不稳定要么整个回归跑得非常慢。我的实践经验是分三层来处理等待等待方式使用场景注意事项隐式等待driver.implicitly_wait(5)全局兜底给所有元素查找一个最大等待时间不要和显式等待混用得过于随意了解即可我通常只在开局设置一次显式等待WebDriverWait针对某个特定元素满足条件时使用最推荐能精确表达“等到元素可点击/可见/存在”固定sleep几乎不使用只有加载动画无法用条件判断时才临时用一下显式等待为什么好用因为它把“等待”和“条件”绑定在了一起。比如登录按钮可能一开始是灰色的element_to_be_clickable可以等到按钮变成可点击状态再动手btn WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, button.btn-login)) ) btn.click()这样脚本既不会过早操作导致找不到元素也不会傻等浪费时间。可以说告别sleep是脚本稳定性迈上的第一级台阶。3.3 定位元素的实战选择ID、CSS还是XPathSelenium定位元素的方式非常多ID、Name、Class、CSS Selector、XPath、链接文本都有。我的优先级排序是这样的有id就用id。它最快也最稳页面结构变动时一般不会影响。没有id优先选CSS Selector。CSS语法简洁对class、属性组合支持很好。最后才用XPath。虽然XPath很强大能处理复杂路径和文本匹配但写不好就会变成又长又脆的“绝对路径”前端一改就挂。举几个常用CSS选择器的例子# 根据class定位 driver.find_element(By.CSS_SELECTOR, .username-input) # 根据属性定位 driver.find_element(By.CSS_SELECTOR, input[nameemail]) # 层级结合 driver.find_element(By.CSS_SELECTOR, div.login-form input[typepassword])XPath里我最常用的是相对路径加文本匹配比如找一个“退出登录”按钮driver.find_element(By.XPATH, //button[contains(text(), 退出登录)])这样的定位方式抗页面结构变动能力强。要记住一个原则选择器写出来是为了在页面改版时少改代码而不是为了炫技。4. 页面元素的真实世界下拉框、弹窗、iframe、文件上传这样处理脚本跑起来以后你很快就会遇到真实页面的各种“不规则元素”。这些元素在教程里很少提到却是实际工作中最耗时间的地方。4.1 处理select下拉框与日期控件原生下拉框在HTML里是select标签它不能用普通的click点击选项因为选项在展开前是不可见的。Selenium提供了专门的Select类from selenium.webdriver.support.ui import Select select_elem Select(driver.find_element(By.ID, province)) select_elem.select_by_visible_text(广东省) select_elem.select_by_value(gd) select_elem.select_by_index(1)这里select_by_visible_text最符合真实用户操作逻辑我90%的情况会用这个。至于日期控件分两种一种是input框可以直接send_keys输入日期但格式必须匹配另一种是真正的日历弹层这种需要先点击触发再定位对应日期的单元格。我的建议是优先尝试send_keys如果开发没有拦截那我们就能省掉很多麻烦。4.2 iframe层层嵌套时的切换逻辑iframe是新手最容易卡死的关卡。你会发现明明页面里有这个元素但find_element就是找不到。原因很简单元素在iframe里当前上下文根本看不见它。处理办法是先用switch_to.frame切进iframe再去定位。# 切到iframe driver.switch_to.frame(driver.find_element(By.ID, main_frame)) # 操作iframe里的元素 driver.find_element(By.ID, content_input).send_keys(hello) # 切回主文档 driver.switch_to.default_content()如果是多层嵌套iframe需要一层一层切进去操作完之后再一层一层退出来。很多测试同学在这里一卡就是一下午。我的建议是遇到找不到元素的问题先打开浏览器开发者工具确认元素是不是在iframe里这个检查比瞎调选择器快得多。4.3 文件上传、滚动加载与新窗口切换文件上传也是高频场景。如果页面上是input[typefile]元素直接send_keys(文件路径)就行它不会真的弹出文件选择窗口upload driver.find_element(By.CSS_SELECTOR, input[typefile]) upload.send_keys(C:/test_files/contract.pdf)注意路径分隔符在Windows下最好用正斜杠或双反斜杠避免转义问题。滚动加载在瀑布流页面里很常见。有时候“加载更多”按钮并不存在页面靠滚动到底部触发请求。这时可以用JavaScript帮忙滚动driver.execute_script(window.scrollTo(0, document.body.scrollHeight);)新窗口的处理核心是句柄切换。点击一个在新标签页打开的链接后driver.window_handles会多出一个句柄先切换到最新句柄再继续操作handles driver.window_handles driver.switch_to.window(handles[-1])这四个场景——下拉框、iframe、文件上传、新窗口——我在每周的回归脚本里都会碰到。学会它们之前脚本经常断学会之后脚本一次性跑完的概率大大提升。5. 从脚本到测试工程pytest、POM与失败告警单个脚本能跑通只是第一步。如果你希望自动化测试能长期维护、能在每次发版时稳定运行就必须从“脚本思维”进化到“测试工程思维”。这一节是我认为全篇最有价值的部分。5.1 为什么要用pytest把脚本组织成用例裸Python脚本做自动化有两个致命问题断言失败时没有清晰的报告多个脚本之间也没办法统一管理。pytest可以解决这两点。它把每个test_开头的函数当成一条测试用例失败时自动捕获异常和堆栈还支持fixture做前后置整理。import pytest from selenium import webdriver pytest.fixture def browser(): driver webdriver.Chrome() yield driver driver.quit() def test_login_success(browser): browser.get(https://example.com/login) browser.find_element(By.ID, username).send_keys(test_user) browser.find_element(By.ID, password).send_keys(123456) browser.find_element(By.CSS_SELECTOR, button.btn-login).click() assert 欢迎回来 in browser.page_source我习惯给每个用例加上pytest.mark标签比如pytest.mark.smoke标记冒烟用例pytest.mark.regression标记全量回归用例这样跑测试的时候就可以自由组合pytest -m smoke --tbshort -q有了pytest测试用例就变成了工程文件可以和CI系统比如Jenkins、GitLab CI集成每天定时跑。这一步跑通之后自动化才算真正从“写给自己看”变成了“团队可依赖的工具”。5.2 POM页面对象模型让代码经得起页面改版页面改版是自动化测试的天敌。昨天还在的按钮今天就被挪走了改一个元素选择器可能要改十处脚本。POM页面对象模型解决的就是这个问题。POM的核心思想很简单把页面元素和操作封装到独立的类里测试用例只负责业务步骤。class LoginPage: def __init__(self, driver): self.driver driver def login(self, username, password): self.driver.find_element(By.ID, username).clear() self.driver.find_element(By.ID, username).send_keys(username) self.driver.find_element(By.ID, password).clear() self.driver.find_element(By.ID, password).send_keys(password) self.driver.find_element(By.CSS_SELECTOR, button.btn-login).click()测试用例里就变得非常简洁login_page LoginPage(browser) login_page.login(test_user, 123456)这样一来如果开发改了登录页的某个输入框ID我只需要改LoginPage这一个类不用改所有测试用例。对于几百条用例的回归项目这个工作量差距是数量级的。我在实际项目中通常分三层base_page.py放公共操作打开页面、等待、截图pages/目录放各页面的封装testcases/目录放具体用例。维护起来思路很清楚新同事接手也不会迷路。5.3 失败截图与测试报告自动化测试的“证据链”自动化脚本跑挂了如果只留下一行assert False没人知道页面当时长什么样。所以我在每一轮回归里都会加上失败截图和时间戳。用pytest写一个失败钩子import time from pathlib import Path def pytest_runtest_makereport(item, call): if call.when call and call.excinfo is not None: driver item.funcargs.get(browser) if driver: timestamp time.strftime(%Y%m%d_%H%M%S) shot_dir Path(screenshots) shot_dir.mkdir(exist_okTrue) driver.save_screenshot(shot_dir / f{item.name}_{timestamp}.png)我还会用pytest-html插件生成可读的HTML报告这样在Jenkins上点开就能看到每一条跑过的用例、耗时和失败原因。可以说截图和报告是自动化测试团队的“证据链”没有它们脚本跑完只是完成了50%剩下50%是让结果能被人看懂。6. 我在实战中踩过的Selenium坑与排查思路任何长期跑自动化的人都会遇到比教程丰富得多的怪问题。这里整理几个我印象深刻的案例不是直接给答案而是把完整的排查链路写出来因为排查思路才是可以复用的经验。6.1 no such element 的排查链路报错selenium.common.exceptions.NoSuchElementException是新手的第一道坎。我的排查顺序是看元素在不在iframe里。开发者工具选中元素看标签是否被包在iframe中。如果在先switch_to.frame。看元素是不是隐藏状态。比如CSS里display:none或者需要先操作某个父级元素才显示。用execute_script把鼠标悬停或先把前置条件做掉。看是不是等待时间不够。把find_element换成WebDriverWait等待元素出现而不是裸查找。看选择器是否过时。打开开发者工具在Elements面板里试一遍你的XPath或CSS如果找不到说明选择器本身有问题换一种定位方式。绝大多数no such element问题都逃不开这四步。我最常犯的错误是跳过第1步直接改选择器越改越乱最后发现元素就在iframe里。6.2 Chrome与WebDriver“会话崩溃”的根因session not created: This version of ChromeDriver only supports Chrome version XX——这条错误信息我在群里看到过无数次。原因简单粗暴浏览器自动更新了driver版本没跟上。Chrome在Windows上默认会自动升级可能你昨天还用得好好的今天就崩了。我的解决思路是两条线并行短期去driver下载页找到和当前Chrome主版本一致的driver版本解压覆盖。长期在CI环境里固定Chrome版本不要让它自动升级。测试环境稳定性比新功能重要得多升级浏览器应该走变更管理流程而不是自动发生。另外如果用的是Selenium Manager自动管理driver也要确保它能正常联网下载CI环境断网时反而会启动失败。这就是我一直保留手动放置driver习惯的原因——少一个外部依赖就少一种失控可能。6.3 无头模式的稳定性问题与我的取舍无头模式headless可以在没有界面的情况下跑测试CI服务器上非常常用。它的好处是节省资源、不占用桌面坏处是某些浏览器行为和有头模式不完全一样比如部分元素尺寸计算、视频渲染、下载弹窗等表现有差异。我踩过最典型的一个坑某个页面的图表在无头模式下宽度为0导致断言失败但肉眼打开却完全正常。排查半天发现是无头模式窗口默认尺寸太小加上--window-size参数解决。所以配置无头模式时我固定会加这样一段options Options() options.add_argument(--headlessnew) options.add_argument(--window-size1920,1080) options.add_argument(--disable-gpu)我的取舍原则是比如了确保某条用例本身就是验证视觉布局我会在有头模式跑常规业务流可以用无头模式跑。两类脚本区分开谁也不影响谁。不要一上来就把所有用例都切到无头模式稳定跑通了再切换也不迟。还有一个建议是关于用例之间互相影响的问题。有些测试脚本依赖登录状态如果一个用例改了密码后面的用例就全挂了。我后来学乖了用例之间要做到相对独立公共数据尽量放fixture里初始化不要靠“上一条用例执行完的状态”去跑下一条。这点在用例数量超过50条以后尤其重要。回头看看从最初连WebDriver版本都搞不清到现在能搭起一套跑了几千次的回归体系我最大的体会不是某个API用得有多熟而是“排错思路比正确写法更重要”。你迟早会遇到教程里没有的问题但只要按着“先看iframe、再看等待、再看选择器、再看driver版本”的顺序一步步查多数坑都有迹可循。如果你刚开始学Selenium先别急着追求炫酷的技巧老老实实把最小脚本跑通再把等待和元素定位练熟最后加一层pytest和POM——这套路径走下来你手里的就不再是几段零散脚本而是一个真正能为团队省时间的自动化工程。
阅读完成 · 觉得有帮助?
咨询建站