存量自动化测试资产盘活:Pytest+Selenium重构回归体系实战

发布时间:2026/9/28 14:26:53
存量自动化测试资产盘活:Pytest+Selenium重构回归体系实战 接手 Test1802 这个项目的时候说实话心里是有点发怵的。仓库里躺着两千多条测试脚本但版本迭代时真正敢说“通过”的不到三成团队早就默认回归测试就是对照 Excel 清单手工点一遍页面。Test1802 这个编号其实是内部测试仓库的迭代流水号——第 18 轮重构里的第 02 个模块它承载的不是“写几个自动化脚本”而是把一套已经失信于团队的测试资产重新盘活。如果你正被历史遗留的测试代码折磨、刚接触自动化测试不知从哪下手、或者想给团队搭一套真正能跑的回归体系这篇复盘应该能给你一些参考。我不会只贴代码而是把“为什么这么设计”“当时踩了哪些坑”“哪些决策后来被证明是对的”都讲清楚毕竟测试脚本这东西最难的从来不是写出来而是让它稳定地跑下去。1. 项目整体设计与思路拆解1.1 项目背景与核心矛盾Test1802 的前身是一批由外包同学陆续堆出来的 Selenium 脚本前后几拨人维护风格完全不统一。有的人习惯把定位器写在用例里有的人封装了一层又一层但没人说得清某个函数被哪些地方调用。最致命的是三个问题用例之间存在顺序依赖——A 用例跑完留下一条数据B 用例才能查到测试数据全部硬编码在脚本里同一个手机号被十几个脚本反复注册等待逻辑基本是time.sleep(5)页面稍微慢一点就红灯一大片。我接手后做的第一件事不是重写而是把所有脚本完整跑一遍把失败原因分类统计。结果很能说明问题35% 的失败是定位器失效30% 是数据冲突20% 是时序问题真正业务逻辑出错的比例不到 10%。换句话说这套自动化不是在测业务而是在测脚本自身的运气。核心矛盾清楚了团队缺的不是自动化用例数量而是一套稳定、隔离、可维护的执行框架。想清楚这一点之后所有设计决策都有了判断依据。任何增加用例间耦合的做法都砍掉任何让定位器更脆弱的写法都禁止任何不能复现问题的失败都必须补日志和截图。Test1802 重构的定位不是“写得更好看”而是“让结果可信”。1.2 技术选型Pytest Selenium Allure 的取舍选型这件事网上对比文章一抓一大把但落到自己团队场景真正要权衡的就三件事学习成本、生态完整度、历史资产价值。我当时在四个方案里做了对比列个表看得更清楚方案语言学习曲线断言表达报告与插件适用场景Robot FrameworkPython低关键字驱动受限于关键字库一般RIDE 偏老业务人员也能参与的验收测试Java TestNGJava中高较繁琐一般和 Maven 集成尚可团队以 Java 为主的大型项目Python PytestPython低原生断言简洁直观Allure 集成成熟fixture 强大中小团队、快速迭代的 Web 回归PlaywrightPython/JS中原生断言也有 Allure 适配新项目、需要多浏览器兼容的端到端测试Test1802 里最终选了 Python Pytest Selenium Allure。选 Selenium 而不是 Playwright主要是存量资产问题——业务系统有大量针对旧浏览器的兼容性要求Selenium 对 WebDriver 生态的支持依然最稳团队里也有人写过 Selenium上手没有心理门槛。至于 Playwright我在另一个新项目里试过确实在等待策略和调试体验上强不少如果是从零起步的纯新项目我会认真考虑它。但 Test1802 的场景是“救活存量”不是“推倒重来”Selenium 加上 Pytest 这套组合完全可以解决问题。Pytest 的三个特性是这次重构的核心支撑fixture 用于构造和清理测试数据parametrize 用于数据驱动插件体系可以挂接失败重试、截图、报告。配合 Allure能直接生成带步骤、带截图、带日志的 HTML 报告开发同学看报告就能定位问题不需要追着测试问“到底哪里红了”。2. 核心细节解析与实操要点2.1 目录结构让新人也能快速定位旧脚本最大的问题是“一锅粥”所有用例平铺在一个目录里公共函数散落各处页面操作和断言混在一起。重构后的目录结构如下这也是我多次调整后觉得最顺手的一种分层方式test1802/ ├── pages/ # 页面对象层每个页面一个类 │ ├── login_page.py │ ├── order_page.py │ └── base_page.py # 公共操作封装等待、点击、输入 ├── cases/ # 测试用例层只写业务场景和断言 │ ├── test_login.py │ ├── test_order_flow.py │ └── conftest.py # 作用域内的 fixture ├── data/ # 测试数据yaml/json 文件 │ ├── login_data.yaml │ └── order_data.yaml ├── utils/ # 工具类 │ ├── driver.py # WebDriver 初始化 │ ├── screenshot.py # 截图与报告附件 │ └── logger.py # 日志封装 ├── reports/ # 测试报告输出目录 ├── requirements.txt └── pytest.ini这个分层的核心思想是让每一层只干一件事pages 层知道“页面长什么样、能做什么操作”cases 层只关心“业务场景是什么、期望结果是什么”data 层隔离所有可变数据。新人接手时第一眼就知道去哪里改定位器、去哪里加用例、去哪里调数据而不是整个仓库翻遍才找到一个find_element_by_id散落在哪里。我最坚持的一点是用例代码里不允许出现find_element。定位器必须封装在 pages 层用例只调用login_page.do_login(user1, pass123)这样的业务方法。刚开始有同事觉得多此一举但后来定位器批量更新时只改一个文件就能完成团队就再没人反对了。2.2 用例编写把“沙子”垒成“城墙”自动化测试圈的玩笑是“写用例容易维护用例难”。Test1802 里我定了三条铁律每条都是拿真金白银的教训换来的。第一条一个用例只验证一件事。有人写“登录并创建订单并查询订单并退出”一旦中间失败你根本不知道是登录挂了还是下单挂了。拆成独立用例之后失败定位从“打开报告看步骤”变成“看红了的那个用例名”排查成本直接下降一个数量级。第二条数据用参数化绝不硬编码。Pytest 的parametrize配合 yaml 数据文件可以做到“用例逻辑一套数据铺开多套”import pytest import yaml with open(data/login_data.yaml, encodingutf-8) as f: login_cases yaml.safe_load(f) pytest.mark.parametrize(case, login_cases, idslambda c: c[case_id]) def test_login(case, login_page): login_page.open() result login_page.do_login(case[username], case[password]) assert result case[expected], f登录结果不符: {case[case_id]}注意我用ids给每条参数起了一个可读的名字这样报告里一眼就能看出是哪组数据出了问题而不是显示成login_cases[0]、login_cases[1]这种天书。第三条测试数据必须隔离用例之间不许互相依赖。这条最难执行因为涉及业务系统的数据状态。我的做法是所有测试数据通过 fixture 在用例开始前创建、结束后清理用户名统一加时间戳前缀。比如注册类用例pytest.fixture def new_user(db_cleaner): username fauto_{int(time.time())}_{random.randint(100, 999)} password Test123456 yield {username: username, password: password} db_cleaner.delete_test_user(username)这样同一套用例跑十遍也不会因为“手机号已注册”而失败。有人觉得清理数据麻烦但如果你试过一个月后重跑回归发现三成用例报“数据已存在”就会明白这种麻烦完全值得。2.3 元素定位与等待最容易被忽视的细节测试脚本 80% 的偶发失败都出在定位和时序上由业务 Bug 引起失败的实际占比很小。这一节值得每个刚入门的测试开发认真读。先说等待。time.sleep(5)这种写法在 Test1802 里是被严格禁止的——它不是太慢的问题而是不确定性页面快的时候浪费 4 秒页面慢的时候照样超时。正确的姿势是显性等待等元素达到某个状态再继续from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By def wait_clickable(driver, locator, timeout10): return WebDriverWait(driver, timeout).until( EC.element_to_be_clickable(locator) )这套封装的好处是“等到能点为止”而不是“睡到预计能点为止”。我在base_page.py里统一放了wait_visible、wait_clickable、wait_text_present几个方法全项目所有用例都走这一套几乎没再遇到“偶尔点不到按钮”的玄学问题。再说定位器。尽量少用脆弱的 XPath 绝对路径比如/html/body/div[2]/div[3]/form/input[1]前端布局一调就全挂。优先用id、name或者前端约定好的>cd test1802 python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txtrequirements.txt内容如下版本号我都锁定了避免半年后依赖升级导致行为不一致selenium4.18.1 pytest8.2.0 pytest-rerunfailures13.0 pytest-base-url2.1.0 allure-pytest2.13.5 PyYAML6.0.1 webdriver-manager4.0.1这里要特别推荐webdriver-manager——它会在首次运行时自动下载匹配浏览器版本的 WebDriver省掉手动下载、配置路径、版本不匹配这一堆烦心事。pytest-rerunfailures则是给偶发失败留了缓冲下面会细说。第二步在pytest.ini里做好基础配置[pytest] testpaths cases addopts -v --tbshort --reruns2 --reruns-delay2 --alluredirreports/allure-results markers smoke: 冒烟用例发布前必跑 p0: 核心流程用例--reruns2表示失败自动重跑 2 次中间间隔 2 秒。这样网络抖一下、页面偶发加载慢导致的失败会被自动消化掉不会在报告里留下一堆吓人的红点。但要控制重跑次数重跑太多会把真正的 Bug 盖住。3.2 第一个真正能跑的回归用例写一个完整可跑的示例从打开浏览器登录到退出展示分层后的代码长什么样。先看conftest.py负责驱动初始化和页面对象注入import pytest from selenium import webdriver from utils.driver import create_driver from pages.login_page import LoginPage pytest.fixture(scopesession) def driver(): driver create_driver() # 内部封装 webdriver_manager yield driver driver.quit() pytest.fixture def login_page(driver): return LoginPage(driver)再看pages/login_page.py页面操作都在这from .base_page import BasePage from selenium.webdriver.common.by import By class LoginPage(BasePage): username_loc (By.ID, username) password_loc (By.ID, password) submit_loc (By.ID, login-btn) def open(self): self.driver.get(self.base_url /login) def do_login(self, username, password): self.wait_clickable(self.username_loc).send_keys(username) self.driver.find_element(*self.password_loc).send_keys(password) self.driver.find_element(*self.submit_loc).click() return self.driver.current_url最后是cases/test_login.py只写业务和断言def test_login_success(login_page): login_page.open() url_after login_page.do_login(tester01, Passw0rd!) assert url_after.endswith(/dashboard), 登录后未跳转到主页 def test_login_wrong_password(login_page): login_page.open() error login_page.get_error_message() assert 用户名或密码错误 in error看到没有用例里完全看不到By.ID这种细节读起来就像在用自然语言描述业务。这是整个 Test1802 重构后我最满意的状态测试代码在向“可读的业务说明书”靠近而不是一堆浏览器操作 API 的堆砌。3.3 接入 Allure 报告与失败自动截图光有测试结果还不够要让人愿意看报告报告就得“好看”且“有用”。Allure 是我用过的报告工具里最值得推荐的它按“步骤 附件 层级标签”组织展示缺陷定位效率比之前的 HTML 表格高太多。第一步安装 Allure 命令行工具。macOS 可以直接brew install allureWindows 用户需要去官网下载 zip 包把bin目录加到环境变量 PATH。然后运行用例时指定结果目录前面pytest.ini已经配好了pytest --alluredirreports/allure-results第二步在用例里添加步骤描述。Allure 会把每个步骤展示成时间线步骤里还能带附件参数import allure allure.step(登录系统) def action_login(driver, username, password): ... allure.feature(登录模块) allure.story(正确密码可登录) def test_login_success(...): ...这样报告里就不是一根直上直下的进度条而是清晰的“打开页面 → 输入用户名 → 输入密码 → 点击登录 → 校验跳转”分步记录。哪一步失败一目了然。第三步失败自动截图并挂到报告上。这是 Test1802 里最实用的一段代码写在conftest.py中import allure import pytest pytest.hookimpl(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: allure.attach( driver.get_screenshot_as_png(), name失败截图, attachment_typeallure.attachment_type.PNG, )有了它每次红灯都自动留下一张现场截图不需要手动在except里写截图代码。每次失败排查时先看图、再对步骤、最后才翻日志定位效率提升极快。3.4 让回归任务自己跑起来定时执行与结果通知脚本能跑出报告还不够——如果每次都要人手动敲命令用不了多久自动化又会回到角落吃灰。Test1802 里我用两条腿走路定时任务负责夜间跑全量回归CI 触发负责每次提交后跑冒烟级用例。定时执行这块简单环境下用系统自带的定时器就行。Linux 加一条 crontab0 22 * * 1-5 cd /opt/test1802 ./venv/bin/python -m pytest -m p0 or smoke --alluredirreports/allure-results跑完之后把 Allure 报告转成静态站点发给团队allure generate reports/allure-results -o reports/allure-report --clean结果通知我用的是企业微信机器人通过 webhook 推一条消息带“通过数/失败数/报告链接”。核心代码如下import requests def send_wecom_message(summary: dict, report_url: str): text ( fTest1802 回归结果 f通过 {summary[passed]}失败 {summary[failed]} f跳过 {summary[skipped]}\n报告{report_url} ) requests.post(WECOM_WEBHOOK_URL, json{msgtype: text, text: {content: text}})消息里不刷屏每天早上团队群一句话比任何测试维护的月度汇报都有说服力。这套链路跑通之后我才真正体会到一个道理自动化测试的终点不是用例写完而是结果有人看、失败有人管。4. 常见问题与排查技巧实录4.1 用例“一会儿亮一会儿红”的常见原因速查表执行一段时间后我把 Test1802 遇到的偶发失败做了归类整理成下面这张速查表。建议你遇到问题先对号入座而不是一头扎进代码里逐行排查现象根本原因解决方式点击按钮偶尔没反应元素在 DOM 里但被遮罩层盖住等待条件改为element_to_be_clickable必要时先点击遮罩关闭页面元素找不到重跑又能过页面接口响应慢未等渲染完成用显性等待等目标元素出现禁止全局固定 sleep用例 A 跑完用例 B 才失败用例间共享数据前一个用例没清理干净每个用例独立创建自己的测试数据fixture 后置清理登录偶尔失败报错信息变了测试账号被其他环境顶下线改为每轮生成临时账号或使用独立的测试环境本地能跑CI 环境一直挂浏览器版本和 WebDriver 版本不匹配用 webdriver-manager 自动匹配CI 里禁用自动更新浏览器报告中内容正常但是全部失败基地址配置错误driver.get打开了错误环境用 pytest-base-url 统一管理环境地址不要在用例里拼字符串这张表的价值在于先看现象和根因的对应关系很多时候一分钟就能排除 80% 的干扰项。4.2 提升脚本稳定性的三个关键习惯这里分享的是从 Test1802 执行上百轮之后沉淀下来的习惯也是我在其他项目里反复验证过仍然管用的经验。习惯一等待策略统一封装拒绝“就地造轮子”。不要在用例里随手写WebDriverWait(driver, 5).until(...)而是全部封装到BasePage里。好处不只是代码复用——集中管理之后如果某个环境页面加载很慢你只需要改封装里的默认超时时间全项目就都生效了。我在 Test1802 里把默认超时设为 10 秒并在pytest.ini里加了--reruns2双重保险偶发失败率降到了 1% 以下。习惯二用例独立是底线不是奢望。我见过太多团队为了“跑起来顺序稳定”搞出用例编号然后用一套复杂脚本按顺序执行。这个方向是完全错误的。正确的做法是放弃“一批用例要有顺序”的想法把每个用例当作独立的小工厂创建数据、执行业务、断言结果、清理数据。虽然初期改造成本高但一旦做到你可以在任意时刻只跑某一用例、并发跑多套环境、断点续跑这些都是顺序依赖下根本做不到的。习惯三失败信息要做到“看图说话”。人不是机器不可能记住每个页面的正常状态。所以失败时必须留下足够现场截图、当前 URL、关键元素是否存在、日志中的错误堆栈。上面 Allure 截图是一个例子更进一步的可以在失败时把页面 DOM 关键节点或接口响应抓下来。总之让失败现场“自解释”这是提升排查效率最有效的手段。4.3 团队协作中的几个现实问题Test1802 能落地不只是技术问题还有一堆和人有关的挑战。开发同学跑不动测试怎么办很多测试框架对命令行操作要求偏高开发同学拿到仓库后第一句话往往是“怎么跑”。我在项目根目录放了一个run_tests.sh一行命令完成环境检查和用例执行#!/bin/bash if [ ! -d venv ]; then python -m venv venv source venv/bin/activate pip install -r requirements.txt fi source venv/bin/activate python -m pytest -m smoke --alluredirreports/allure-results allure serve reports/allure-results这样开发收到需求后只需执行./run_tests.sh浏览器会自动打开报告页面。降低启动门槛比任何文档都有效。测试代码谁来维护和写业务代码一样测试代码也必须走 Code Review。每个 PR 至少由另一位测试或开发确认改动是否合理重点看定位器是否稳定、等待策略是否规范、断言是否真的覆盖了业务预期。三个月下来最明显的变化是“随便改两行就不管了”的情况消失测试代码的回头率从接近 50% 降到了不足 10%。用例太多跑不完怎么办全量回归两小时确实长了所以我在设计之初就用 marker 给用例分了级别smoke和p0是每次提交必跑的小集合约 15 分钟其余凌晨全量回归。分层跑既缩短了反馈时间又保住了全量覆盖。这个设计后来被团队推广到其他项目算是 Test1802 留下的一个额外礼物。这套体系跑顺之后我还做了一次小的扩展给报告接了趋势图每次执行的结果都会追加到历史库两周下来就能直观看到哪个模块缺陷密度最高。后面如果有人想在这个方向继续深挖数据驱动、基于风险的用例排序、甚至接入流量回放都是值得尝试的路。不过这些东西得结合自己团队的业务特点慢慢试别人的方案再漂亮不适合自家流程也是白搭。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询