尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Selenium自动化测试实战:从WebDriver环境搭建到元素定位与PO框架

Selenium自动化测试实战:从WebDriver环境搭建到元素定位与PO框架 简介本资源是一套面向自动化测试工程师、QA初学者及高校软件测试课程学习者的Selenium Web自动化实战框架合集系统解决Web UI测试中框架搭建、稳定性保障与工程化落地等核心问题。压缩包共含多个模块化文件以Python脚本为主含WebDriver配置、Page Object类、测试用例、数据驱动模板辅以README说明文档与配置示例整体大小77.45MB结构清晰、即拿即用。已有28人下载学习适合希望从零构建可维护测试框架的实践者。读者可直接获得完整可运行的跨浏览器测试脚手架支持Chrome/Firefox、覆盖ID/XPath/CSS等8种定位策略的实操示例、显式等待与隐式等待的对比实现、基于Excel的数据驱动测试模板以及遵循Page Object设计模式的典型页面封装案例如登录页、搜索页显著降低后续维护成本并提升测试健壮性。基于Selenium的自动化Web测试框架与实战案例集合1. 环境搭建踩坑实录Selenium WebDriver的安装配置并不只是pip install做Selenium自动化测试很多人第一步就栽在环境上。你以为敲一句pip install selenium就万事大吉了结果跑第一个脚本时浏览器一个黑窗闪过报错信息直接教做人。这一节我把环境搭建阶段最容易踩的坑、最合理的配置方式全部拆开讲清楚。1.1 版本匹配是最大命门Python、Selenium、浏览器、Driver四者关系先说不开玩笑的事实Selenium本身只是个协议客户端它不负责启动浏览器真正去驱动浏览器干活的是各浏览器厂商提供的WebDriverChrome叫ChromeDriverEdge叫MSEDGEDriverFirefox叫GeckoDriver。四者之间的版本必须匹配任何一个版本错位脚本都会以各种诡异姿势挂掉。这里有个惨痛教训某次我用Chrome 115版本的系统手头ChromeDriver还是114的跑脚本时报的是SessionNotCreatedException提示This version of ChromeDriver only supports Chrome version 114。这种报错还算友好更坑的是有些版本错位根本不会提示版本问题而是随机出现元素找不到、点击无效等灵异现象排查半天才发现是Driver版本不匹配。版本匹配的核心逻辑Selenium 4.x版本对WebDriver的版本限制非常宽松只要Driver能匹配浏览器即可ChromeDriver的大版本号必须与Chrome浏览器主版本号一致比如Chrome 120就用ChromeDriver 120.xFirefox的GeckoDriver相对宽容一些但依然建议用最新版Edge的Driver版本必须与Edge浏览器严格对应而且Edge的自动更新策略很激进今天配好明天就可能失效实操时我建议直接把Chrome的自动更新关闭然后锁死浏览器版本和Driver版本。Chrome关闭自动更新的方法有组策略法、服务停用法最省事的是直接用企业版Chrome更新节奏可控。1.2 Driver的获取路径与常见网络问题ChromeDriver的官方下载地址是Chrome for Testing的站点各种版本都能找到。但国内直连下载经常被卡死我自己常用的替代方案有两个一是通过https://registry.npmmirror.com/-/binary/chromedriver/这个镜像站拉二是用Selenium Manager自动管理。Selenium 4.6版本起Selenium内置了Selenium Manager当你启动脚本时如果检测不到Driver它会自动从网上拉取匹配当前浏览器版本的Driver。这个功能很大程度上解放了双手但在内网或者网络受限的环境里它反而会成为超时重灾区。所以我的建议是本地开发用Selenium Manager没问题但正式测试环境或CI环境里还是手动下载Driver并指定路径更可控。# 手动指定Driver路径的写法 from selenium import webdriver from selenium.webdriver.chrome.service import Service service Service(executable_pathrD:\drivers\chromedriver.exe) driver webdriver.Chrome(serviceservice)对比一下Selenium Manager自动管理的写法# 这种写法下Selenium 4.6会自动寻找或下载匹配的Driver from selenium import webdriver driver webdriver.Chrome()两种写法都能跑通但在团队协作场景里我强烈建议显式指定路径或者把Driver路径抽到配置文件里否则每个成员都要维护一套自己的Driver环境不一致的问题会持续消耗团队精力。1.3 国产操作系统下的安装注意事项如果是在麒麟等国产操作系统上做测试安装Selenium时不要直接pip install建议先确认系统自带的Python版本。麒麟系统某些版本自带Python 3.6而Selenium 4.x对Python版本有要求至少要3.7以上。这种情况下可以先用python3 -m pip --version确认pip版本然后用虚拟环境隔离出Python 3.8再安装避免污染系统环境。另外麒麟这类系统上浏览器默认可能是FirefoxChromium的安装源不一定是默认启用的需要先用包管理器装好Chromium再对应下载ChromeDriver。这个细节很磨人但踩过一次之后就会长记性所有环境问题先确认浏览器本体能不能正常启动再谈Driver匹配。2. 元素定位策略从八种定位方式到XPath实战心法元素定位是Selenium自动化的核心技能。标题里专门提到元素定位策略说明这一块确实值得展开。Selenium WebDriver理论上提供八种定位方式id、name、class name、tag name、link text、partial link text、xpath、css selector。但实际写脚本时最常用的只有三种id、css selector、xpath。2.1 定位方式的优先级怎么排很多初学者总有选择困难症看到什么就用什么其实定位方式的优先级是有逻辑的优先级定位方式适用场景健壮性1idid属性值稳定的元素最好2css selector有稳定class或属性组合的元素很好3xpath无法用id/class唯一定位时好4name/link text表单控件、超链接等特定场景一般5tag name/class name极少用较差为什么id最好因为id在HTML规范里就是全页面唯一的定位效率最高而且前端开发者正常情况下不会频繁改id。但实际项目里很多前端框架会生成随机id比如React某些动态列表这时id就没法用了。css selector在性能上比xpath好语法更简洁但学习曲线稍微陡一点。真正难搞的场景比如嵌套层级深、动态属性多、元素之间靠位置关系定位最后还得靠xpath。2.2 XPath定位相对路径优先绝对路径少用XPath分绝对路径和相对路径绝对路径从/html/body/div[1]/div[2]/...一路写到底这种写法在页面结构调整时基本就是废了。真正实战中用得最多的是相对路径配合各种函数和轴。先看一个典型场景某个页面上有一个搜索框和搜索按钮页面结构长这样。div classsearch-wrapper input idsearch-input classsearch-input placeholder请输入关键词 / button classsearch-btn搜索/button /div定位搜索框最稳的方式是直接//input[idsearch-input]这没什么悬念。但如果是动态id呢比如idsearch-input-12345每次刷新数字都会变这时候就要用contains了//input[contains(id, search-input)]再看一个更复杂的在一个表格里要根据某一行某个单元格的文本去定位同一行的另一个单元格。比如用户列表里我要点张三那一行的编辑按钮。表格结构是经典的tr td//tr[contains(td, 张三)]//button[contains(text(), 编辑)]这一行代码的意思是先找到包含张三文本的tr再在这个tr范围内找名字含编辑的按钮。这种写法把范围缩小到了具体行比直接全局匹配编辑按钮健壮得多。xpath轴也是很有用的工具最常用的是following-sibling和preceding-sibling。比如定位表单里某个输入框之后的错误提示//input[idusername]/following-sibling::span[classerror-msg]我见过很多人写xpath全靠chrome开发者工具Copy XPath复制。这里泼一盆冷水devtools复制的xpath大概率是绝对路径能跑但极度脆弱稍微一个层级改动就挂掉。一定要自己动手写相对xpath如果对xpath语法不熟可以先在控制台用$x(//input[idsearch-input])验证验证通过再写进脚本。2.3 元素定位不到时的标准排查链路定位报错NoSuchElementException是自动化测试里出现频率最高的异常遇到先别急着改xpath按链路来排查确认元素是否在iframe里。这是新手最容易忽略的坑页面里嵌了iframe但脚本没切进去元素永远找不到。处理方法定位前先driver.switch_to.frame()用完再switch_to.default_content()切回来。确认元素是否在Shadow DOM里。现代前端框架有时会把组件封装进Shadow DOM普通定位方式摸不到。需要先拿到Shadow Host再通过driver.execute_script()深入。确认元素是否在窗口的可视区域之外。页面需要滚动才能看到的元素某些情况下直接定位没问题但点击操作会失败需要先scroll_into_view()。确认页面是否真的加载完成了。这个留到等待机制那一节详细讲。这套流程走一遍绝大多数定位问题都能找到根因。如果都排除完还找不到那才要考虑是不是xpath写错了。3. 等待机制隐式等待、显式等待和强制等待怎么选新手写Selenium脚本最容易犯的错一上来就是个time.sleep(5)页面没加载完就停5秒页面加载完了还在傻等。我见过一个跑50个用例的测试套件里面积累的sleep加起来有20多分钟全是无效时间。等待的真实目的不是等时间而是等条件成立。3.1 三种等待的本质区别强制等待time.sleep无条件阻塞指定时长。用于缓解偶发网络延迟可以但绝不能成为常规手段。它的缺陷很明显等待时间设短了偶发失败设长了浪费执行时间而且谁也预估不准最优等待时长属于所有方案里的下策。隐式等待implicitly_wait设置一个全局超时时间WebDriver在查找元素时如果元素没有立即出现会在超时时间范围内轮询等待。它用法简单一次设置全局生效driver.implicitly_wait(10)但隐式等待有硬伤它只能等元素出现不能等元素可点击、可见、消失等复杂状态而且一旦设置整个会话内所有find_element操作都受影响如果前后页面加载策略不同容易出现误判。显式等待WebDriverWait针对某个具体条件进行显式的等待灵活性最强、可读性最好、稳定性最高。这才是自动化测试里应该大规模使用的方式。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By element WebDriverWait(driver, 10, poll_frequency0.5).until( EC.element_to_be_clickable((By.ID, submit-btn)) )这段代码的意思是最多等10秒每0.5秒轮询一次只要submit-btn这个元素可点击就立刻返回10秒内条件始终不满足才抛TimeoutException。3.2 实战中高频使用的Expected Conditions清单expected_conditions模块封装了几乎所有主流等待场景我把实际项目里用得最多的列出来预期条件使用场景presence_of_element_located元素出现在DOM中不要求可见visibility_of_element_located元素可见element_to_be_clickable元素可见且可点击presence_of_all_elements_located多个元素都出现在DOM中text_to_be_present_in_element元素文本包含指定内容element_to_be_selected下拉框选项被选中alert_is_present弹窗出现url_contains / title_is页面跳转完成举个例子登录后跳转到首页如果直接定位首页元素在跳转瞬间有可能扑空。这时候等待条件应该绑定页面跳转这个动作而不是傻等WebDriverWait(driver, 10).until(EC.url_contains(/dashboard))再比如某个页面是Ajax异步刷新表格数据是局部加载的这时候等待条件要绑定数据出现WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.XPATH, //tr[data-loadedtrue])) )3.3 等待失效的实际场景怎么破显式等待也不是万能的实战里还有几个需要特别注意的场景第一页面局部刷新导致元素先消失再出现。比如点击刷新按钮后表格内容被清空再重新渲染。如果等待条件只是元素可见有可能在旧元素还没消失时就命中了导致后续操作作用在旧元素上。解决思路等待元素先消失再等新元素出现WebDriverWait(driver, 10).until( EC.invisibility_of_element_located((By.CSS_SELECTOR, .table-loading)) ) WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, .table-row)) )第二iframe内元素的等待。iframe里的元素在切换之前是摸不到的先切frame再等待这个顺序不能反。第三页面跳转后的等待需要配合window句柄切换。点击一个链接打开新标签页后代码如果不切句柄还在原页面上找元素永远找不到。等待机制这套组合拳打下来测试脚本的稳定性会从跑不跑得过看心情变成几乎都能一次过。不过也别神话显式等待我见过有人把每个元素都包一层WebDriverWait脚本长得没法看。正确的思路是对关键路径上的关键元素用显式等待辅助元素该用隐式等待兜底就用两者共存是可以的但优先保证显式等待的条件设计合理。4. 跨浏览器测试一套脚本跑通Chrome、Firefox、Edge的完整方案标题里专门有跨浏览器测试在真实项目里这意味着用户的浏览器分布是多元的或者团队内部开发用Chrome、测试环境用Firefox、产品演示还要用Edge。如果你的脚本只在一个浏览器上跑换个浏览器就形态百出那跨浏览器就是一个不折不扣的技术债。4.1 为什么跨浏览器测试容易翻车主要原因是浏览器内核差异和WebDriver实现差异。Chrome和Edge同属Chromium内核大部分情况下行为一致但Driver初始化参数、下载目录、无头模式设置这些细节都有差异。Firefox走的是Gecko引擎页面的渲染细节、事件处理机制都有区别某些CSS渲染在Firefox里就是和Chrome不一样导致元素的位置、尺寸、可见性都不同。还有一个容易忽略的问题同样是设置浏览器下载目录Chrome用prefs里的download.default_directoryFirefox用browser.download.dir。写框架时如果没有针对浏览器类型做参数隔离测试用例在Firefox里下载文件会跑到系统默认目录后续步骤再去找文件必然失败。4.2 Driver管理不应该写在测试用例里跨浏览器测试的第一步是把Driver的创建逻辑从测试用例里抽出来用工厂模式统一管理。下面是我在框架里惯用的写法from selenium import webdriver class DriverFactory: staticmethod def create_driver(browser_namechrome, headlessFalse): browser_name browser_name.lower() if browser_name chrome: options webdriver.ChromeOptions() if headless: options.add_argument(--headlessnew) return webdriver.Chrome(optionsoptions) elif browser_name edge: options webdriver.EdgeOptions() if headless: options.add_argument(--headless) return webdriver.Edge(optionsoptions) elif browser_name firefox: options webdriver.FirefoxOptions() if headless: options.add_argument(-headless) return webdriver.Firefox(optionsoptions) else: raise ValueError(f不支持的浏览器类型: {browser_name})测试用例里只需要传浏览器类型参数driver DriverFactory.create_driver(browser_namefirefox, headlessTrue)配合pytest的参数化一条用例就可以同时跑多个浏览器import pytest pytest.mark.parametrize(browser, [chrome, firefox, edge]) def test_login(browser): driver DriverFactory.create_driver(browser_namebrowser) # 用例逻辑4.3 无头模式、下载路径、用户目录这几个隐藏配置跨浏览器实践的常见痛点是浏览器配置文件差异。Chrome里配置下载路径和禁用GPUoptions.add_experimental_option(prefs, { download.default_directory: rD:\downloads, download.prompt_for_download: False, }) options.add_argument(--disable-gpu)Edge几乎兼容Chrome的写法毕竟同根源。Firefox则不同options.set_preference(browser.download.dir, rD:\downloads) options.set_preference(browser.download.folderList, 2)无头模式的坑儿也不少。Chrome的无头模式在Selenium 4里要用--headlessnew这个新模式的页面渲染行为和真实浏览器几乎一致老版本--headless会有概率出现元素定位失败。Firefox的无头模式参数是-headless。如果只跑无头模式不测有头模式等发布到生产环境才发现某些JS组件在无头模式下不渲染那就尴尬了。我还遇到过一种情况Chrome在Linux服务器上以root用户运行如果不加--no-sandbox参数浏览器直接拒绝启动。本地开发不会碰到但CI环境大概率碰到提前把参数加进工厂方法里能省很多排查时间。跨浏览器测试在Selenium Grid上还可以做到分布式并发执行把不同浏览器的Driver注册到Grid上用例跑起来会快很多。这属于进阶玩法初学阶段先把单机多浏览器跑通收益就已经很明显了。5. 数据驱动测试把数据和代码解耦才是框架化的第一步标题里提到数据驱动测试这是自动化测试从脚本走向框架的分水岭。之前写的那些用例数据和逻辑是耦合的比如登录用例里写死用户名和密码换一组数据就得复制整个用例。数据驱动的基本思想是把测试数据从测试代码中抽离出来用一条用例逻辑驱动多组数据执行。5.1 从参数化到数据文件三种数据驱动的层次我按使用复杂度把数据驱动分成三个层次大家可以对号入座第一层pytest.mark.parametrize参数化。适合数据量小、用例内嵌即可的场景。比如登录功能测试3组数据import pytest pytest.mark.parametrize(username,password,expected, [ (admin, 123456, 登录成功), (admin, wrong, 密码错误), (, 123456, 用户名不能为空), ]) def test_login(username, password, expected): # 执行登录操作 result login(username, password) assert result expected这种做法的好处是零成本、直观坏处是数据一变就要改代码数据量大了代码会膨胀。适合几条数据的冒烟测试不适合成百上千条数据的回归测试。第二层从外部数据文件读取。把测试数据放到JSON、YAML、Excel或CSV文件里代码从文件读取数据再喂给测试用例。这一层我已经在框架里反复用了。比如用JSON存储登录用例数据[ { username: admin, password: 123456, expected: 登录成功 }, { username: admin, password: wrong, expected: 密码错误 } ]测试代码这样写import json import pytest pytest.fixture(paramsjson.load(open(testdata/login_data.json, encodingutf-8))) def login_data(request): return request.param def test_login(login_data): result login(login_data[username], login_data[password]) assert result login_data[expected]第三层数据文件与用例文件分离加上数据预清洗和后置处理。这个层次多用于企业级框架数据文件里不仅有输入和断言还有用例ID、用例描述、是否启用的开关字段。执行器读取时会做前置校验、跳过标记为disabled的用例执行后自动回写执行结果方便做追溯。我个人的建议是别一上来就追求第三层。先把第一层用熟练在项目里跑起来之后感受到维护痛点再升级到第二层。直接上第三层很容易陷入数据文件设计地无尽头的困境。5.2 Excel数据驱动在真实项目中的实战细节很多人用Excel做数据驱动因为测试人员不一定会写代码但都会用Excel。用Python操作Excelopenpyxl库是比较顺手的。import openpyxl def read_excel_data(file_path, sheet_name): wb openpyxl.load_workbook(file_path, data_onlyTrue) ws wb[sheet_name] rows list(ws.iter_rows(values_onlyTrue)) headers rows[0] # 第一行是列头 data [] for row in rows[1:]: if row[0] is None: # 空行跳过 continue data.append(dict(zip(headers, row))) return data读取Excel时有两个极容易踩的坑第一个坑是公式和缓存值。Excel单元格里如果存的是公式openpyxl在默认情况下读出来的可能是公式字符串而不是计算结果要用data_onlyTrue读取缓存值。但data_onlyTrue要求Excel文件必须被某个程序打开并保存过否则缓存值不存在读出来是None。处理方式测试数据文件尽量纯手输不用公式。第二个坑是日期格式。Excel里的日期会被读成datetime对象直接塞进JSON或断言里会出问题。建议在读取时统一做格式化或者干脆在Excel里把日期列设置成文本格式减少不必要的转换。5.3 数据驱动的隔离性和可追溯性数据驱动还有一个容易被忽略的点用例与用例之间不能因为共享数据而互相影响。比如一组注册流程的数据里手机号是相同的第一条用例注册成功后第二条用例再用同一个手机号去注册必然报手机号已存在。这是非常典型的测试数据污染。解决方案通常有两种一是用动态数据比如时间戳后缀保证每次注册的手机号唯一二是数据清理测试结束后把注册的账号数据从数据库或接口层面删掉。我在框架里用的是前置准备后置清理的组合方式用pytest的fixture来做pytest.fixture def unique_user(): import time username ftest_{int(time.time())} yield username # 后置清理调用删除接口或数据库清理 delete_user(username)除了隔离性可追溯性也很重要。数据驱动用例一多跑挂了一条你要能立刻定位是哪个数据文件、哪一行、哪种前置条件出的问题。所以我建议在执行日志里打印数据的标识字段比如数据文件里的use_case_id这样在Allure报告或测试日志里能直接反查到这条数据的内容和来源。数据驱动不是银弹它最适合的场景是同样的操作流程大量不同的输入数据和预期结果。如果每个用例的流程差异很大硬套数据驱动只能让数据文件变成一锅粥。6. Page Object模式让自动化测试脚本从能跑走向可维护一个自动化测试项目最怕的不是用例失败而是前端一改版所有用例集体需要返工。Page Object模式简称PO就是解决这个问题的工程化手段。核心思想很简单把页面元素定位和相关操作封装进独立的页面类测试用例只关注业务逻辑不直接接触定位器。6.1 不用PO模式的脚本为什么后期会炸看一下不用PO时的典型写法def test_login(): driver.find_element(By.ID, username).send_keys(admin) driver.find_element(By.ID, password).send_keys(123456) driver.find_element(By.ID, login-btn).click() assert driver.find_element(By.CSS_SELECTOR, .welcome).text 欢迎回来看起来没什么问题项目里只有几个用例时也确实没问题。但一旦用例规模到几十个、上百个同样的driver.find_element(By.ID, username)可能在不同文件里出现了十几次。某天前端把username改成了user-name你要去所有文件里手动替换漏一个就是隐患。PO模式把每个页面的元素定位集中管理起来改版时只需要改Page类页面相关用例自动生效。这就是PO的核心价值把变化集中收敛把维护成本降下来。6.2 BasePage、Page类、TestCase三层结构的落地一个可落地的PO框架至少包含三个层级BasePage层封装所有页面共用的操作比如等待元素、点击、输入、滚动、截图。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 click(self, locator): self.wait.until(EC.element_to_be_clickable(locator)).click() def input_text(self, locator, text): element self.wait.until(EC.visibility_of_element_located(locator)) element.clear() element.send_keys(text) def get_text(self, locator): return self.wait.until(EC.visibility_of_element_located(locator)).textPage类层每个页面一个类类里面声明本页的元素定位器和操作业务方法。from selenium.webdriver.common.by import By from pages.base_page import BasePage class LoginPage(BasePage): # 元素定位器统一用元组集中管理 username_input (By.ID, username) password_input (By.ID, password) login_button (By.ID, login-btn) error_message (By.CSS_SELECTOR, .error-msg) def login(self, username, password): self.input_text(self.username_input, username) self.input_text(self.password_input, password) self.click(self.login_button) def get_error_message(self): return self.get_text(self.error_message)TestCase层业务用例只负责流程编排和断言完全不碰driver和定位器。from pages.login_page import LoginPage def test_login_with_valid_account(driver): login_page LoginPage(driver) login_page.login(admin, 123456) assert login_page.get_error_message() 三层分工明确之后前端的任何定位器变化都只波及Page类用例层几乎不用动。6.3 PO模式实践中的几个关键设计决策第一元素定位器集合放在Page类顶部用元组管理。我见过有人在Page类的方法里写裸的By.ID整个类没有定位器清单看起来是PO但实际上散落到处都是可维护性提升有限。统一放在顶部改版本时打开类扫一眼就能全部改完。第二PO类方法不要写断言。Page类职责是描述页面行为断言属于业务验证逻辑留在TestCase里。如果把断言写进Page类同一个页面在不同用例里的验证点可能不同Page类会变得越来越臃肿不利于复用。第三操作后返回新的Page对象是加分项。比如登录成功后返回首页的Page对象调用方可以继续链式操作def login_success(self, username, password): self.login(username, password) return HomePage(self.driver)这样TestCase写起来会有一种行云流水的流程感。当然新手阶段可以先不追求这种写法先把三层结构跑通。PO模式不是万能的但它确实是测试框架从脚本集合进化成项目资产的关键一步。哪怕项目规模不大我也建议至少要建一个BasePage因为一旦前期没有这一层后面想补就相当于重写。7. 综合实战一个搜索到下单全链路案例的框架落地到这里前面所有技术点需要串起来验证。我用一个典型的电商搜索商品→加入购物车→结算下单全链路来展示一套框架的完整落地过程涵盖Page Object、显式等待、数据驱动和失败截图等能力。7.1 页面对象设计把这个业务链路拆成三个页面对象SearchPage搜索页、ProductDetailPage商品详情页、CartPage购物车页。每个Page类继承BasePage元素定位器集中在类顶部。# pages/search_page.py from selenium.webdriver.common.by import By from pages.base_page import BasePage from pages.product_detail_page import ProductDetailPage class SearchPage(BasePage): search_input (By.ID, search-input) search_button (By.ID, search-btn) first_product (By.CSS_SELECTOR, .product-list .item:first-child .title) def search(self, keyword): self.input_text(self.search_input, keyword) self.click(self.search_button) def enter_first_product(self): self.click(self.first_product) return ProductDetailPage(self.driver)# pages/product_detail_page.py from selenium.webdriver.common.by import By from pages.base_page import BasePage from pages.cart_page import CartPage class ProductDetailPage(BasePage): add_to_cart_button (By.ID, add-cart-btn) go_to_cart_button (By.CSS_SELECTOR, .cart-icon) def add_to_cart(self): self.click(self.add_to_cart_button) def go_to_cart(self): self.click(self.go_to_cart_button) return CartPage(self.driver)# pages/cart_page.py from selenium.webdriver.common.by import By from pages.base_page import BasePage class CartPage(BasePage): checkout_button (By.ID, checkout-btn) order_submit_button (By.ID, submit-order) order_success_message (By.CSS_SELECTOR, .order-success) def checkout(self): self.click(self.checkout_button) self.click(self.order_submit_button) def is_order_success(self): return 下单成功 in self.get_text(self.order_success_message)7.2 数据驱动与用例层组织用外部JSON文件管理搜索下单的数据每条数据包含搜索关键词和商品名称断言[ {keyword: 机械键盘, expected_product: 青轴机械键盘}, {keyword: 降噪耳机, expected_product: 头戴式降噪耳机} ]测试用例通过pytest参数化循环执行import json import pytest from pages.search_page import SearchPage pytest.mark.parametrize(data, json.load(open(testdata/search_order_data.json, encodingutf-8))) def test_search_and_order(data, driver): search_page SearchPage(driver) search_page.search(data[keyword]) product_page search_page.enter_first_product() assert data[expected_product] in product_page.get_text( (By.CSS_SELECTOR, .product-title) ) product_page.add_to_cart() cart_page product_page.go_to_cart() cart_page.checkout() assert cart_page.is_order_success()7.3 失败截图与日志记录让用例失败可诊断用例跑挂了不可怕可怕的是挂了你不知道挂在哪一步。所以在框架里建议加一个pytest的失败截图机制在断言失败或异常抛出的瞬间把当前页面状态保存下来。import pytest from datetime import datetime 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 datetime.now().strftime(%Y%m%d_%H%M%S) driver.save_screenshot(fscreenshots/{item.name}_{timestamp}.png)配合在BasePage各操作里打印关键步骤日志失败后打开日志和截图基本能还原整个执行现场。这是自动化测试框架做到可信任的重要一环——不是只有跑成功的能力还要有失败时能快速定位的配套能力。7.4 从本地脚本到持续回归的流程沉淀框架在本地能跑通只是第一步真正产生价值还要让它能在服务器上定时跑。常见的做法是把测试代码推到代码仓库用持续集成工具定时拉取并执行pytest生成Allure报告通知到邮件或协作群。这个流程本身不复杂但有几个实践前提测试代码要和被测系统版本保持同步前端改版后测试用例要在同一个迭代周期内更新定时执行要有稳定的测试环境浏览器和Driver版本固定执行结果要有历史记录方便观察趋势而不是每次跑完就抛之脑后我在实际维护过程中体会很深的一点是自动化测试的价值不在于用例数量多少而在于它能不能持续稳定地帮你拦截回归问题。一套干净的PO框架加上数据驱动再加上完善的失败信息沉淀这套东西才能真正成为团队的护城河。我自己在维护中后期最大的收获反而是排查问题速度的提升——对着截图和日志很多时候一分钟就能锁定是前端改动还是脚本自身的问题。本文还有配套的精品资源点击获取
返回列表