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

Selenium Web自动化测试实战:从环境搭建到CI持续集成

Selenium Web自动化测试实战:从环境搭建到CI持续集成 ★ FEATURED ARTICLE
做过Web项目的人都懂功能测试里最枯燥、最想摔键盘的环节不是写用例而是手工回归。一个流程测十遍点按钮、填表单、等页面跳转手速再快也是在替机器做重复劳动。Selenium自动化测试框架就是为了把这一摊子破事接管过来——用脚本驱动真实浏览器模拟用户操作自动校验结果把测试从“体力活”变成“代码活”。这篇东西我不打算写成一板一眼的官方文档就按我实际搭建和落地这套框架的经验把选型思路、核心API、等待策略、工程化组织、踩坑实录一次讲透给正在选型或者刚入坑的朋友一份能直接参考的说明书。这套工具能帮你解决什么问题测试用例重复执行的次数降下来、回归周期从小时级压到分钟级、把可验证的断言交给机器去盯、把报告沉淀成团队资产。适合谁看刚接触Web自动化但被各种教程弄得一头雾水的测试新人项目里准备引入UI自动化但不知道怎么落地的测试负责人以及想把手动用例批量转成自动化用例的开发同学。下面我就按一个真实项目的演进路径来拆。1. 为什么是Selenium选型逻辑与核心原理1.1 它解决的核心痛点手工回归测试最大的问题不是慢而是不可复现。你上周测得好好的功能这周开发改了一行样式可能连按钮都点不到了你还得重新走一遍流程去确认。自动化要解决的就是这个“可重复验证”的需求同一套操作序列任何时候跑结果都应该是稳定一致的。Selenium的做法是用WebDriver协议跟浏览器通信让脚本指令真正驱动一个浏览器实例。这不是模拟HTTP请求那种假交互而是真真切切打开一个Chrome或Firefox窗口渲染页面、触发事件、执行JavaScript跟真实用户的操作路径几乎一致。正因为走的是真实浏览器环境那些纯接口测试发现不了的前端问题——比如元素被遮挡、异步加载卡住、跨域请求失败——它都能暴露出来。我见过不少团队在这个环节纠结为什么不用接口测试为什么不用无头浏览器模拟我的看法是它们是不同层级的东西接口测试管数据正确性UI自动化管用户可感知的行为正确性两者是互补关系不能互相替代。Selenium管的是后者而且管得非常到位。1.2 与其他方案的对比市面上的方案其实不少Cypress、Playwright都是后起之秀各有所长。但我个人在不同项目里对比过之后Selenium在一件事上的优势是别人比不了的生态沉淀时间足够长踩坑记录足够多。对比维度SeleniumCypressPlaywright浏览器支持Chrome、Firefox、Edge、Safari全覆盖主打ChromiumFirefox支持有限三巨头都支持但部分能力受限语言绑定Java、Python、C#、Ruby、JS都有仅JavaScript/TypeScriptJavaScript/TypeScript为主社区资料量极多任何疑难杂症几乎都能搜到答案增长很快但历史问题沉淀不如前者较新部分场景要自己摸调试体验依赖日志和截图略传统时间回溯面板很好用自带调试面板也方便成熟度老牌稳定企业级落地案例多适合快速见效的前端团队现代化API更友好如果你的团队是全栈JavaScript、项目偏前端驱动Playwright确实体验很好。但如果你的团队是传统测试团队、主力语言是Python或者JavaSelenium的学习成本和团队兼容性反而是最低的。我在某公司落地UI自动化时团队里还有不少只会手工测试的同事Python加Selenium的入门门槛明显比前端方案更友好这也是我最终继续押注它的原因。1.3 WebDriver的工作原理简析搞清楚Selenium怎么工作排查问题就能少走一半弯路。WebDriver的本质是一个远程控制协议你的测试脚本作为客户端通过HTTP请求把命令发给浏览器里的驱动进程驱动再去调用浏览器原生自动化接口执行操作。拆开看就是这么几步测试脚本创建WebDriver实例指定浏览器类型和驱动路径驱动启动一个浏览器进程并监听脚本发来的命令脚本调用find_element、click这些API时WebDriver将其转换成标准命令发给浏览器浏览器执行完把结果返回给驱动驱动再回传给脚本这个链路决定了两个天然特性一是脚本和浏览器是分离的可以跑在远程机器上做分布式执行二是所有操作都有网络通信开销这也是为什么自动化脚本跑起来比人手工操作还要慢半拍。理解了这一点你在设计用例的时候就该知道不要指望它像录屏一样快而是要在稳定性和执行时间之间取平衡。基于这个原理实际落地时脚本运行环境出问题十有八九出在驱动和浏览器版本不匹配上。这块细节我会在环境搭建部分展开讲。2. 环境搭建与核心配置最小可用方案2.1 Python环境与依赖安装我用的是Python 3.10以上版本配合最新版Selenium 4.x。为什么选Python而不是Java因为写起来快断言语义清晰团队容易上手。安装命令非常简单pip install selenium pip install webdriver-manager第三行的webdriver-manager是个救命的库。早年用Selenium最烦的就是手动下载对应版本的chromedriver放到PATH里浏览器一升级驱动就失效整个测试套件直接瘫痪。用了这个库之后它会自动探测你本机浏览器版本自动下载对应驱动自动注册路径实测下来基本上不需要再管驱动的事了。2.2 驱动管理与浏览器版本匹配虽然webdriver-manager能自动处理大部分情况但它也不是万能的。我遇到过几次极端情况公司内网环境无法访问外部下载源或者浏览器版本太新、驱动还没更新出来这时候就得手动介入了。手动方式也简单先看一下你本机的Chrome版本菜单里的“关于Chrome”能直接看到。然后去ChromeDriver镜像站下载对应版本的驱动解压后放在一个固定目录用下面的代码指定路径from selenium import webdriver from selenium.webdriver.chrome.service import Service # 手动指定driver路径 service Service(executable_path/path/to/chromedriver) driver webdriver.Chrome(serviceservice)有一个版本对照的规律可以记一下驱动主版本号跟浏览器主版本号必须一致比如Chrome是114那就得用114.x的ChromeDriver。如果你用的是webdriver-manager它自己处理这个映射关系不需要你操心但了解这个机制有助于你排查奇怪的报错。2.3 浏览器启动参数配置日常调试时我习惯让浏览器窗口正常显示这样能直观看到脚本执行的每一步。但跑CI或者批量执行回归时显然不能每次都弹一堆窗口出来这时候就要配置无头模式。from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--headlessnew) # 新版无头模式 options.add_argument(--window-size1920,1080) options.add_argument(--disable-gpu) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) driver webdriver.Chrome(optionsoptions)这几个参数有讲究--headlessnew新版Chrome的无头模式比旧版更接近有头渲染跑脚本更稳--window-size无头模式默认窗口尺寸是800x600很多弹窗、下拉框在这个尺寸下会异常必须显式指定大尺寸--disable-dev-shm-usage容器环境里不设置这个很容易因为共享内存不够导致浏览器进程崩溃CI上跑一定加上这里踩过的坑值得一提旧版--headless模式下有些JavaScript动画行为跟有头模式有差异导致某些元素点击要等更久甚至定位不到。升级到新版无头模式之后这个问题基本消失。所以如果你们还在用很老的方式配置无头模式建议升一下参数写法。2.4 浏览器安装与版本选择建议有的团队成员本机没装Chrome只有Edge那也没关系Edge本身就是Chromium内核Selenium支持得很好。只需要把驱动换成EdgeDriver代码几乎不用改。如果团队统一用Chrome我建议锁定一个大版本区间不要追新。因为平时跑自动化脚本的机器保持浏览器版本稳定比什么都重要版本一换整个回归脚本就可能开始抽风。我还见过一个方案准备一台专门的测试执行机只用来跑自动化上面的浏览器版本由测试团队自己控制不跟随业务电脑的自动更新走。效果很好强烈建议有条件的团队这么搞。3. 核心API实操从定位元素到模拟操作3.1 元素定位八种方式的优先级Selenium定位元素的方式有八种ID、Name、ClassName、TagName、LinkText、PartialLinkText、XPath、CSS Selector。实操中我的选择优先级很明确优先用ID页面上ID应该是唯一的也是最稳的其次用Name表单元素里常见再次是CSS Selector灵活度高语法简洁XPath是兜底方案但不要用绝对路径一开始接触自动化测试的人最容易犯的错就是从浏览器里直接复制一个绝对的XPath出来长这样/html/body/div[2]/div/div[1]/div[2]/form/div[1]/input这种路径开发只要在页面中间加一个div你的脚本就报废了。我见过一个项目里几十条用例全是这种定位方式改版的时候一个晚上的时间全花在改定位上极其痛苦。我推荐的做法是优先用稳定的语义化属性。比如这种写法# 推荐用id定位 driver.find_element(By.ID, username) # 推荐用CSS定位 driver.find_element(By.CSS_SELECTOR, [namelogin-btn]) # 谨慎用XPath要写相对路径 driver.find_element(By.XPATH, //button[contains(text(),登录)])定位的核心原则不是追求某个方式炫酷而是追求稳定性。所谓稳定就是你写完这个定位下个月还能用下次版本迭代还不坏。所以我在团队里定了一条规矩定位信息优先找开发要># 输入文本 input_element driver.find_element(By.ID, username) input_element.clear() # 先清空防止之前有残留内容 input_element.send_keys(test_user) # 点击 button driver.find_element(By.ID, login-btn) button.click() # 获取文本做断言 toast driver.find_element(By.CLASS_NAME, toast-message) assert toast.text 登录成功这里有个常被忽略的细节clear()这个动作看起来多此一举但真实场景下很多输入框会在你点击的时候自动填充默认值不清空直接塞文本就可能变成“默认值你输入的值”。表单类用例我都是标配先clear再send_keys。点击的坑也很多。有时候元素明明在页面上click()却报ElementClickInterceptedException说被其他元素挡住了。常见原因是有个透明的遮罩层或者广告弹窗盖在按钮上面。我处理的办法是点击前先判断元素是否可见、可点击不行就先关掉遮罩层再操作。代码如下from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By # 等待元素可点击再操作 login_btn WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, login-btn)) ) login_btn.click()这种写法每次点击前都会等到元素真正可用才动手稳定性比直接定位然后强点高出一大截。3.3 特殊元素处理下拉框、单选、复选框、弹窗常见页面控件里下拉框、单选组、弹窗这三类处理的坑最多。下拉框原生HTML的select标签可以直接用Select类from selenium.webdriver.support.ui import Select select_element Select(driver.find_element(By.ID, city)) select_element.select_by_visible_text(北京) # 按显示文本选 select_element.select_by_value(bj) # 按value属性选如果是那种用div模拟的自定义下拉框原生Select类就没用了只能点击展开再点具体选项。这时候定位选项就考验功力了我一般用CSS配合文本过滤来定位。弹窗分两种浏览器原生弹窗alert和页面自定义弹窗。原生弹窗用switch_to.alert处理alert driver.switch_to.alert alert.accept() # 确定 # alert.dismiss() # 取消页面自定义弹窗就回到元素定位的范畴了但要注意弹窗内容可能是异步加载出来的直接定位会找不到元素所以要先等待弹窗出现再继续操作。单选和复选框相对简单直接点击对应的label或者input就行。唯一的坑是部分组件库的checkbox用CSS伪元素绘制勾选状态直接点击input可能无效要点击外层的label。遇到这种我调试的心得是先手动操作一遍在浏览器开发者工具里看一下事件绑定在哪一层再决定脚本点谁。3.4 iframe切换与窗口句柄管理这两个是自动化测试里比较容易蒙圈的地方。iframe的本质是页面里嵌了另一个独立文档你在主文档里怎么都定位不到iframe内部的元素。解决思路是先切入iframe上下文再定位用完再切回来# 切入iframe可用索引、id或WebElement driver.switch_to.frame(main_iframe) # 在iframe内操作 driver.find_element(By.ID, name).send_keys(abc) # 切回主文档 driver.switch_to.default_content()iframe还可能是嵌套的两层甚至三层。这时候要一层层切进去用完一层层切回来顺序不能乱。处理完记得回到最上层不然下一个用例会莫名找不到元素。窗口句柄管理是另一类问题。点击一个链接打开新标签页脚本还停留在旧页面这时候就要切换driver.switch_to.new_window(tab) # 或直接获取当前所有句柄 # 拿到当前所有窗口句柄 handles driver.window_handles driver.switch_to.window(handles[-1]) # 切到最新打开的窗口这里记录一个小技巧切换窗口之前先把当前窗口句柄存下来处理完新窗口还能切回来。不然开着几个窗口靠盲猜句柄顺序很容易切错。3.5 文件上传与下载文件上传是很多自动化项目里绕不开的环节。分两种情况input标签实现的直接send_keys传文件路径就行upload_input driver.find_element(By.CSS_SELECTOR, input[typefile]) upload_input.send_keys(/path/to/local/file.xlsx)但很多系统的上传是用了隐藏input或者第三方插件那就只能引出系统级对话窗处理这需要借助AutoIt或者pyautogui这类的工具。这种方案很不稳定而且依赖操作系统环境我一般不建议在核心回归用例里覆盖这种场景放冒烟测试里抽查就行了。文件下载相对简单设置浏览器下载目录后用文件系统的检查来做断言。比如触发下载后定时检查目标目录里有没有新文件出现有的话说明下载成功。4. 等待机制与稳定性优化把脚本写稳的必修课4.1 三种等待方式强制、隐式、显式大部分自动化脚本跑挂不是定位没写对而是页面还在加载你就去点。Selenium里等页面有三种策略我用一个直观类比来讲强制等待time.sleep相当于无脑数秒固定等3秒再操作。不管页面加载快慢你都得等够时间隐式等待implicitly_wait相当于给浏览器设一个全局超时比如10秒。每找一次元素最多花10秒去找找不到才报错显式等待WebDriverWait相当于带条件地等等到某个条件成立才继续等不到就超时抛异常代码示例对比import time # 强制等待简单粗暴但浪费时间和稳定性 time.sleep(3) driver.find_element(By.ID, submit).click() # 隐式等待全局生效 driver.implicitly_wait(10) driver.find_element(By.ID, submit).click() # 显式等待精准等待 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, submit)) ) element.click()我的建议是尽量用显式等待别碰强制等待。强制等待写多了整套脚本的执行时间会成倍拉长而且等待时间不够还会随机失败属于又慢又不稳的方案。4.2 显式等待的常用条件与实战写法显式等待的威力在于它有丰富的expected_conditions可以用。我把最常用的几个列一下from selenium.webdriver.support import expected_conditions as EC # 元素出现在DOM树中不一定可见 WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, a))) # 元素可见且可交互 WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, a))) # 元素可点击 WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, a))) # 元素文本包含指定内容常用于加载状态消失 WebDriverWait(driver, 10).until(EC.text_to_be_present_in_element((By.ID, a), 完成)) # 某元素消失常用于loading条消失 WebDriverWait(driver, 10).until(EC.invisibility_of_element_located((By.ID, loading))) # 页面标题包含某些内容常用于页面跳转 WebDriverWait(driver, 10).until(EC.title_contains(首页))实践中我搭过一套通用等待封装减少重复代码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 wait_clickable(self, locator): return self.wait.until(EC.element_to_be_clickable(locator)) def wait_visible(self, locator): return self.wait.until(EC.visibility_of_element_located(locator))这样每个页面类直接继承BasePage调用等待条件写起来干净很多。这是我在项目中实际落地的方式不复杂但对维护性的提升非常明显。4.3 隐式等待与显式等待的配合禁忌有一个知识点我见过太多人搞混隐式等待和显式等待不建议混用。原因是两者的实现机制完全不同。隐式等待是每次都去轮询查找显式等待是自带轮询逻辑混用的时候可能出现等待时间翻倍或者某些情况下提前返回的问题导致行为不可预期。我实际项目的配置是全局只设一个合理的隐式等待时间做兜底具体用例里关键操作全部用显式等待覆盖。不做无脑混搭。这个其实有点争议但我实测多年下来这种组合最稳定排查问题也最清晰。4.4 页面加载策略别忽略这个参数Selenium启动浏览器时有个页面加载策略可以配置默认是normal也就是等页面全部资源加载完才继续执行。对于富媒体页面这很浪费时间。options.add_argument(--disable-javascript) # 上面提过更推荐的方式是通过page_load_strategy设置options.page_load_strategy eagereager是等DOM树加载完就继续不管图片和异步资源。设置成这个之后跑那些图片特别多的页面整个用例执行时间能缩短不少。代价是某些依赖图片或者异步数据的元素可能需要额外的显式等待但从总体性能来看收益远大于成本。5. 用例工程化从单条脚本到可维护的测试套件5.1 用页面对象模型POM组织代码脚本写多了必然面临维护地狱。一个简单的登录操作在十条用例里各写一遍改一个登录按钮的定位你就要改十个地方。页面对象模型Page Object Model就是来解决这个问题的。核心思想很简单把页面抽象成一个类每个页面的元素定位和操作逻辑封装成一个方法测试用例只跟这些方法打交道不再直接碰定位。以登录页为例class LoginPage(BasePage): # 页面元素定位信息集中管理 USERNAME (By.ID, username) PASSWORD (By.ID, password) LOGIN_BTN (By.ID, login-btn) ERROR_MSG (By.CLASS_NAME, error) def input_username(self, text): self.wait_clickable(self.USERNAME).clear() self.wait_clickable(self.USERNAME).send_keys(text) def input_password(self, text): element self.wait_clickable(self.PASSWORD) element.clear() element.send_keys(text) def click_login(self): self.wait_clickable(self.LOGIN_BTN).click() def get_error_message(self): return self.wait_visible(self.ERROR_MSG).text def login(self, username, password): self.input_username(username) self.input_password(password) self.click_login()对应的测试用例就非常清爽def test_login_success(driver): login_page LoginPage(driver) login_page.login(test_user, pass123) assert 登录成功 in login_page.get_tip_message() def test_login_failed_wrong_password(driver): login_page LoginPage(driver) login_page.login(test_user, wrong_pass) assert 用户名或密码错误 in login_page.get_error_message()这样改代码的时候只动页面类用例本身不用动。这是整个工程化里性价比最高的一步我强烈建议团队无论多小都从POM开始。5.2 pytest组织用例与前置后置Python生态下pytest是Selenium最搭的测试框架。它的fixture机制很适合做浏览器的启动和关闭管理。import pytest from selenium import webdriver from selenium.webdriver.chrome.options import Options pytest.fixture def driver(): options Options() options.add_argument(--headlessnew) options.add_argument(--window-size1920,1080) driver webdriver.Chrome(optionsoptions) driver.implicitly_wait(5) yield driver # 用完之后自动回收 driver.quit()这个fixture的作用是在每个用例开始前启动一个干净浏览器用例跑完自动关掉用例之间互不干扰。这是我最推荐的隔离方式比共用浏览器跑完所有用例要稳得多。5.3 数据驱动与配置分离业务逻辑跟测试数据混在一起是很多测试脚本后期维护痛的要死的根源。我的做法是从一开始就分离。配置放配置文件[app] base_url https://example.com username test_user password pass123 [browser] headless True timeout 10用例数据用参数化import pytest pytest.mark.parametrize(username,password,expected, [ (user1, pass1, 登录成功), (user2, wrong, 用户名或密码错误), ]) def test_login_scenarios(driver, username, password, expected): login_page LoginPage(driver) login_page.login(username, password) assert expected in login_page.get_tip_message()这样新增一条测试数据不用改任何代码只在参数列表里加一行。十几条数据就能把一组输入组合全覆盖掉写测试的效率也高很多。5.4 失败截图与日志出了问题别靠猜自动化脚本跑失败的时候如果只给一行报错日志你可能要花半小时去猜当时页面发生了什么。我的标配做法是用例失败自动截图同时把当前页面的标题和URL打进日志。pytest.hookimpl(tryfirstTrue, 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: timestamp time.strftime(%Y%m%d_%H%M%S) driver.save_screenshot(fscreenshots/failure_{timestamp}.png)配合企业微信或者邮件通知的脚本跑挂了直接能看到截图。这个功能帮我省了大量排查时间属于投入产出比极高的工程改进。6. 持续集成与远程执行把自动化跑成常态化6.1 无头模式在CI流水线里的最佳实践本地能跑通只是第一步真正稳定地让自动化测试在CI流水线里天天跑才是团队的另外一个门槛。CI环境通常没有显示器无头模式就是必然选择。关键是要注意CI环境和本地环境的差异。CI里的容器通常资源有限跑浏览器需要额外关注以下几点加--no-sandbox否则在容器内跑会因权限报错加--disable-dev-shm-usage否则共享内存不足会崩溃设置足够大的窗口尺寸否则页面布局会异变我在某团队的CI流水线上曾经遇到一个诡异问题在本地全部通过一到CI就大面积失败。排查到最后发现是CI容器默认的/dev/shm只有64MB浏览器进程分分钟占满导致渲染卡死。加了这个参数之后问题直接消失。这种问题不看日志根本猜不到所以跑CI一定要留着完整日志别为了省空间把调试信息全关了。6.2 定时执行与自动触发策略现在很多团队是白天开发晚上回归或者每次提交都跑一轮冒烟。定时执行一般用CI的定时任务每天凌晨跑全套回归早上上班直接看邮件报告。提交触发一般只跑冒烟集合和受影响模块的相关用例全量回归放深夜。我个人的策略是分三层提交触发冒烟用例集10到15条核心路径几分钟跑完快速反馈每晚定时全量回归几百条用例天亮前跑完发版前全量回归加针对新功能的重点用例这三层的区分很重要因为如果你每次提交都跑全量回归用例一多就根本跑不动CI排队排到天荒地老。6.3 分布式执行与并行策略用例多了之后单机串行跑确实慢几百条用例动辄一两个小时。Selenium支持Selenium Grid可以把用例分发到多台机器上并行执行。但Grid的部署和维护都有一定成本更轻量的是用pytest-xdist做多进程并行。pytest -n 4 --distloadscope-n 4表示用4个进程并行跑loadscope按模块分片同一个模块的用例不会被分到不同进程导致相互干扰。实测下来四进程并行能让执行时间缩到原来的三分之一以内。不过并行也有代价每个进程都是独立的浏览器内存占用翻倍用例之间如果共享了某些全局状态并行可能出错。所以我的建议是先保证用例如串行能稳定通过再考虑并行加速。不要为了性能破坏稳定性。6.4 测试报告与通知体系跑完了得有报告报告得好读。pytest本身带一个简单的文本输出但真正常用的是pytest-html这样的插件能生成一个HTML报告展示哪些用例通过、哪些失败、失败时的日志和截图。pip install pytest-html pytest --htmlreport.html --self-contained-html报告发出来之后我还会配一个失败用例的通知机制跑完当天如果有失败在群里发一条消息附上失败用例的名字和截图链接。这样团队不用每天主动去看报告有问题才会被通知到。让自动化测试成为一种“平时不打扰、出事必响应”的基础设施这就是工程化的收益。7. 常见问题与排查技巧实录7.1 元素定位失败的典型场景自动化测试里遇到最多的一定是NoSuchElementException。原因千奇百怪但常见的有这么几类现象可能原因排查方法定位不到元素页面还没加载完就去找加显式等待等元素出现定位到但不可见元素在隐藏标签页或折叠区域里检查是否要先展开或切换标签页定位到但点击无效被遮罩层或弹窗挡住检查页面是否有浮层先关闭再操作偶尔能定位偶尔不行动态ID在每次刷新后变化改用相对XPath或语义属性定位找不到iframe内元素没有先切入iframe上下文检查元素是否在iframe里先switch再定位我的排查习惯是失败之后先截图再看DOM结构。截图能看出页面当时长啥样DOM结构能确认元素为什么定位不到。之前提过的失败自动截图这时候就派上大用场了。7.2 元素不唯一与随机定位问题页面上出现多个相同ID或相同class的元素定位时就会拿到错误的那个。比如一个页面上有多个“确定”按钮你要点的可能是弹窗里的那个不是页面底部那个。解决办法是缩小定位范围# 先定位到包含目标元素的容器 modal driver.find_element(By.CLASS_NAME, modal) # 再在容器内查找目标按钮 confirm_btn modal.find_element(By.XPATH, .//button[contains(text(),确定)])这里那个开头的.很关键表示从当前元素往下找不是从整个文档找。这个用法也是我经常在代码审查时给人强调的细节。7.3 页面跳转与异步加载的等待策略SPA应用是自动化测试里比较头疼的场景。页面看起来跳转了但URL没变内容全靠在DOM里局部替换。这时候用title_contains或者切换窗口这些老办法可能就不太灵了。我的处理思路是不要等页面跳转而是等目标内容出现。不管是跳转还是局部刷新最终你要操作的还是那个具体的元素所以用显式等待等元素出现是所有场景里最通用的等待策略。WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.XPATH, //div[classcontent])) )这个思路的底层逻辑是你要关心的是业务结果不是技术实现方式。页面是路由跳转还是局部刷新对用户来说都一样对你的自动化脚本来说也应该一样。7.4 浏览器崩溃与资源耗尽问题跑大批量用例时偶尔会遇到浏览器进程卡死、无响应、或者干脆崩溃。这种情况多半是长时间的用例执行中积累了大量资源。解决方案有几个方向每个用例结束都driver.quit()不跑完一套再关适当拆分执行批次比如300条用例分5批跑用无头模式减少内存占用加--disable-gpu减少渲染进程消耗还有一个容易忽视的问题用例执行中不断打开新标签页却不关闭标签页数量越积越多最终把浏览器拖垮。所以也要养成用完就关的习惯# 打开新窗口处理完后关闭并切回原窗口 driver.close() driver.switch_to.window(original_window)7.5 慢速定位与执行时间优化用例太慢一般不是Selenium本身慢而是等待策略不合理。最常见的是到处time.sleep(5)或者每个页面都强制等待3秒。优化思路有两个方向。一是把强制等待改成显式等待等到了就继续不等满二是把页面加载策略改成eager少等那些图片和副资源的加载。我见过一个项目只做了这两项优化整个回归套件的执行时间从90分钟降到了40分钟效果立竿见影。另外脚本层面的数据组织也能优化比如从配置文件里读测试数据比每次启动都登录拿一个token再调用接口要快得多。但这要结合具体业务来权衡不能为了快牺牲了用例的真实性。8. 从Selenium到测试平台未来可以这样扩展8.1 关键字驱动与可视化用例编排有了POM的基础之后下一步常见的演进方向是关键字驱动。简单说就是把操作抽象成可配置的关键字比如open_page、input_text、click_button、assert_text测试人员不用写代码通过配置表格就能拼出测试场景。这个方向适合团队成员编程基础薄弱、但业务理解深的团队。我在某项目里见过测试工程师做成套件复用度高的场景交给代码层业务组合场景放到配置层效果非常不错。但要提醒一句不要把框架抽象得太复杂不然维护框架本身的成本超过了用例编写的成本就本末倒置了。8.2 与接口测试的融合策略UI自动化和接口测试不是对立关系而是互补关系。接口测试快、稳、适合做底层校验UI自动化慢、贴近真实用户适合做主流程验证。我落地过的一个方案是基础数据准备全部走接口比如用接口创建好测试账号用接口把订单变成指定状态然后UI层只管核心操作和断言。这样UI用例的执行时间大幅缩短稳定性也明显提升因为不要再用UI去一点点造数了。具体例子要测“已发货订单的取消流程”通过接口直接创建订单并改成已发货状态比在UI里走完创建订单、下单、支付、发货这一整条链路快得多。实测下来这个融合方案让我负责的回归套件执行时间缩短了差不多一半。8.3 从自动化走向质量平台做到最后其实你会发现UI自动化只是质量保障体系里的一环。完整的体系还应该包括需求阶段的可测试性评审、开发阶段的静态检查与单元测试、集成阶段的接口自动化、上线前的UI回归和发布后的线上监控。Selenium在体系中的定位是“业务主流程的最后一道护栏”。它不负责发现所有问题但负责兜住那些接口测不到、开发没注意、只有真实用户操作才能触发的问题。把自动化跑成常态化之后版本的发布节奏会变快、出线上问题的概率也会下降这比刷多少个执行通过率都有说服力。最后的一点个人体会Selenium这套框架说难真的不难API翻来覆去就那么些东西说简单也不简单真正让它稳定的往往不是代码怎么写而是对业务场景的理解和对不稳定因素的预判。这几年做自动化测试落地我最深的体会是脚本跑通只是第一步让脚本稳定地跑上一个月、三个月、半年才是真正考验功力的地方。如果你正准备开始做Web自动化测试我的建议非常简单别贪多求全从一个核心流程做起选用例框架加POM把失败截图做好然后让它在CI里跑起来。先把这条最简路径走通再逐步扩展覆盖面和并行能力。整个过程里你一定会踩到各种各样让我前面列举还多的坑但没关系解决一个就沉淀一个这套框架会跟着你一起越来越成熟。
阅读完成 · 觉得有帮助?
咨询建站