ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Pytest+Selenium4的POM三层分离UI自动化框架设计

基于Pytest+Selenium4的POM三层分离UI自动化框架设计 简介这是一套面向Web自动化测试工程师与Python初/中级学习者的POM模式实战框架源码专为解决传统UI测试脚本耦合度高、维护成本大、环境切换繁琐等痛点而设计。资源共65个文件压缩包大小2.28MB涵盖17个核心Python模块含base_page、page对象、test用例及工具类、28个JSON配置文件支撑多环境数据驱动与参数化、4个JavaScript辅助脚本、2个INI配置、2个CSS/2个HTML报告模板、1个Dockerfile支持容器化部署及完整日志与Allure报告生成体系。已有531人学习下载结构清晰data目录管理测试数据page目录实现页面元素与操作封装report与tmp目录分离执行产物base与common提供可复用基础能力。读者可直接运行、调试并扩展该框架掌握Parametrize数据驱动、JSON动态配置、Log分级记录、Allure可视化报告及POM三层解耦的完整工程实践。 我见过太多 WebUI 自动化测试项目死因不是写不出来而是写完之后没人敢维护。刚搭起来的时候大家都觉得简单等用例涨到一两百条页面元素改一次几十个文件跟着崩最后领导问“为什么测试又红了”你连答都答不上来。我在这条路上踩过一整轮坑之后最终沉淀出一套基于 Python Pytest Selenium4 的 POM 模式三层分离框架基础层管 driver、配置和公共能力页面对象层管元素定位和页面操作用例层只管业务场景与结果断言。如果你正被 UI 自动化改版维护折腾得头大或者想从一开始就搭一套能踏实用的架子这篇文章应该能帮你少走不少弯路。文章会从架构划分讲到目录结构再讲到每一层的关键代码最后是失败截图、重试和报告这些让框架真正能上 CI 的细节。中间穿插一些我在实际项目中踩过的坑尤其是那些你翻官方文档不一定看得到的经验。1. 为什么是三层而不是两层第一版框架的教训和边界划分1.1 第一版框架是怎么从“能用”变成“不敢用”的我第一次写 UI 自动化框架时图省事把所有逻辑直接放在用例文件里。每个测试函数开头都是driver webdriver.Chrome()中间是driver.find_element(By.ID, username)这样的操作断言也散落各处。当时跑几十条用例确实没问题但等到用例过百噩梦就来了。最典型的场景是登录页的“用户名输入框”从idusername改成了iduser_name我以为只改一个地方就行结果全局一搜十个用例文件里有二十多处引用了旧定位。更难受的是等待策略有人写time.sleep(2)有人依赖隐式等待一个小改动就要把整个项目翻一遍。后来我意识到问题的根源不是代码写得不够好而是所有职责都混在一起没有边界。于是我做了一次重构把公共操作抽成了BasePage又把每个页面的元素定位和操作抽成了页面类。从形式上看这已经是 POM 模式了但跑了一段时间后还是乱。原因是我把 driver 初始化、配置读取、日志、截图这些基础设施也塞进了页面类里页面对象既要管业务又要管浏览器启动负担非常重。这成了典型的“两层半”结构。1.2 三层各管什么依赖方向是怎么定的后来的重构方案就是今天文章的主角基础层、页面对象层、用例层。三层之间的依赖方向严格自上而下用例层可以调用页面对象层页面对象层可以调用基础层但反过来不行同层之间也尽量避免互相调用。层级放什么内容坚决不放什么基础层driver 创建与销毁、配置读取、日志、截图、公共等待、BasePage 封装不允许出现“登录”“下单”等业务关键词页面对象层页面元素定位、页面级操作、页面状态判断不允许写测试断言不允许组织用例流程用例层业务场景编排、测试数据准备、断言、用例标记不允许出现 By、click、send_keys 这类直接操作这一层划分的核心目的是把变化隔离起来。页面元素变了只改页面对象层浏览器升级或环境切换只改基础层业务规则变了只动用例层。这样一来任何一层出问题影响范围都是可控的。我还遇到过有人把断言写在页面对象层里比如login_page.assert_login_success()。表面看很方便但一旦同一个页面在不同场景下需要不同断言这个页面类就会越写越肿。我的建议是页面对象层只返回“状态”比如is_login_success()把“判断对不对”留给用例层去做。1.3 为什么不要轻易再加第四层有人会问那是不是还要再抽一个 service 层、数据层我的经验是多数 Web UI 自动化项目不需要。UI 自动化的本质是业务回归不是复杂分布式系统。分层的目的不是展示抽象能力而是隔离变化源。三层已经能覆盖绝大多数场景多一层就意味着多一份理解成本团队成员写起代码来也会纠结“这个函数该放哪层”。如果你真的遇到需要多层的情况比如要同时支持 UI 断言和接口造数我建议单独抽一个utils模块或api_client但不要把它当成一个完整层级来参与调用链。保持主线简单比什么都重要。2. 目录结构、依赖清单和配置先把工程骨架立起来2.1 这套框架的目录树长什么样结构会直接决定团队成员的协作方式。下面是我最终落地的目录树你可以直接照着建webuitest/ ├── conf/ │ ├── config.yaml │ └── pytest.ini ├── core/ │ ├── config_loader.py │ ├── driver_factory.py │ ├── base_page.py │ ├── logger.py │ └── screenshot.py ├── page/ │ ├── login_page.py │ ├── home_page.py │ ├── cart_page.py │ ├── checkout_page.py │ └── biz/ │ └── order_flow.py ├── testcase/ │ ├── conftest.py │ ├── test_login.py │ └── test_order.py ├── utils/ │ ├── data_factory.py │ └── allure_handler.py ├── report/ └── requirements.txt这个名字是我重构过两版之后定下来的。core是基础层只放和浏览器、框架机制有关的内容page是页面对象层一个页面一个模块testcase是用例层也是 pytest 的执行入口。biz子目录专门放跨页面的业务流程封装这个后面会细说。2.2 requirements.txt 版本选择依赖这块我建议锁定大版本不要每次装最新的。以下是我目前比较放心的组合selenium4.21.0 pytest8.2.0 pytest-rerunfailures14.0 pytest-xdist3.6 allure-pytest2.13.5 PyYAML6.0Python 版本建议至少 3.9有条件直接上 3.11 或 3.12。Selenium 4 对 Python 的版本要求不算高但新版 pytest 和 allure 的组合在旧版本上偶尔会有兼容问题。团队成员多的话建议在项目里放一个.python-version配合 pyenv 或 venv 统一环境能省掉很多“我本地能跑你本地不能跑”的争吵。2.3 config.yaml 和 pytest.ini 的初始化配置文件用 YAML 比 JSON 舒服因为可以写注释。我习惯把浏览器、等待时间、基础地址、测试账号这些都放进去browser: chrome headless: true implicit_wait: 2 explicit_wait: 10 base_url: https://demo.example.com users: admin: username: admin password: admin123 buyer: username: buyer01 password: test123456这里重点说一下headless。Selenium4 新版本推荐用--headlessnew它不是简单的无头模式而是更接近真实 Chrome 的渲染行为遇到某些 flex 布局和弹窗时比旧无头模式更能还原真机表现。我建议本地调试时打开界面CI 上跑无头这个开关放在 YAML 里最合适。pytest.ini 负责的是 pytest 的发现规则和运行参数[pytest] testpaths testcase markers smoke: 冒烟测试 ui: UI 测试 addopts -s --alluredirreport/allure-resultsaddopts这里我做得比较克制只加了-s和 allure 输出目录。并行、重试这些参数我更喜欢在执行命令里临时指定因为不同团队、不同 CI 环境对稳定性要求不一样写死在配置文件里反而不灵活。3. Selenium4 的基础层封装driver 工厂、配置读取、BasePage 等待3.1 Selenium4 带来的三个使用习惯变化很多教程还停留在 Selenium3 时代的写法打开源码发现一堆find_element_by_id的调用这在 Selenium4 里已经被标记废弃了。使用 Selenium4有三个习惯必须改过来第一个是定位器写法。现在所有定位方式都通过By统一传入比如driver.find_element(By.ID, username)而不是driver.find_element_by_id(username)。这个改变看起来只是语法层面的但它让定位器可以像元组一样被定义和传递在 POM 模式里特别好用。第二个是 Selenium Manager。从 4.6 版本开始官方默认内置了驱动管理能力你不需要再手动下载 chromedriver也不需要配置路径。团队新成员拉下代码就能跑这是体验上最大的提升。第三个是相对定位器。Selenium4 里可以用locate_with来按相对位置找元素比如找一个按钮上方 20px 的 input。这个功能在测试复杂表单时很实用虽然常规场景用不到但知道有它遇到难写的定位时能多一个选择。3.2 DriverFactory 和 ConfigLoader基础层的第一行代码通常是 driver 工厂。它解决的问题是把“用什么浏览器、带什么参数、要不要无头”这些选择集中到一个地方而不是散落在每个用例里。from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.firefox.options import Options as FirefoxOptions class DriverFactory: staticmethod def create_driver(browserchrome, headlessTrue): if browser chrome: options Options() options.add_argument(--window-size1920,1080) options.add_argument(--langzh-CN) options.add_argument(--ignore-certificate-errors) if headless: options.add_argument(--headlessnew) return webdriver.Chrome(optionsoptions) if browser firefox: options FirefoxOptions() options.add_argument(-headless) return webdriver.Firefox(optionsoptions) if browser edge: return webdriver.Edge() raise ValueError(f暂不支持的浏览器: {browser})很多框架会在这里加入远程浏览器、云测试平台的支持我建议不要一上来就加先保证本地能跑通。把参数写进 YAML 之后基础层已经能应对大部分日常需求了。配置文件加载也属于基础设施。我用的是很简单的封装import yaml class ConfigLoader: staticmethod def load(path): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f)逻辑很简单但它的价值在于把 YAML 读取逻辑收口后面你想改成环境变量覆盖本文还有配套的精品资源点击获取
返回列表