OpenCart UI 自动化测试实战:POM 四层架构 + 失败自动截图 + 飞书通知

19 条用例 100% 通过 · POM 四层架构 · 失败自动截图 · 飞书定时通知


一、为什么写这篇文章?

你有没有遇到过这种情况:

  • UI 自动化用例本地跑得好好的,一到 CI 就随机失败
  • 开发问你"当时页面长什么样",你只能说"我没截图"
  • 写到 20 个用例后代码乱成一团,改一个登录按钮要改 10 个文件

这篇文章不是教你"怎么装 Playwright",而是带你完整走一遍 UI 自动化框架从设计到落地的全过程。POM 四层架构怎么做、失败自动截图怎么配、19 条用例怎么做到 100% 稳定------你想让面试官看到的工程化能力,都在这里。

源码已开源: GitHub 仓库地址


二、POM 四层架构

在 UI 自动化里,没有架构的脚本写到 20 个就崩了。我参考了 Page Object Model 的思想,把项目拆成四层:

text 复制代码
┌──────────────────────────────────────────────────────────┐
│ tests/              测试用例层                          │
│ 只放断言,不关心元素定位和底层实现                      │
├──────────────────────────────────────────────────────────┤
│ flows/              业务流程层                          │
│ 组合多个页面操作,封装完整场景(如结算流程)            │
├──────────────────────────────────────────────────────────┤
│ pages/              页面对象层                          │
│ 每个页面独立管理自己的元素定位和业务操作                │
├──────────────────────────────────────────────────────────┤
│ base_page.py        基类层                              │
│ 封装点击、输入、等待、滚动等公共方法                   │
└──────────────────────────────────────────────────────────┘

分层的好处:

  • 登录页元素变了 → 只改 login_page.py 一处
  • 购物车逻辑变了 → 只改 cart_page.py 一处
  • 新增结算流程 → 在 flows/ 里组合已有页面操作,用例层只需 3 行代码

三、BasePage:点击自带重试

UI 自动化最怕的就是页面抖动、动画遮挡导致点击失败。我在 BasePage 里给点击方法加了 2 次重试机制:

python 复制代码
def click(self, locator: Locator, timeout=12000, retry_count=2):
    for attempt in range(retry_count):
        try:
            self.wait_element_visible(locator, timeout)
            locator.click()
            return
        except Exception as err:
            logger.warning(f"点击失败,第{attempt+1}次重试: {err}")
            self.page.wait_for_timeout(1000)
    raise Exception("元素多次点击失败")

效果: 本地环境因页面动画导致的误报率从 15% 降到了接近 0。


四、失败自动截图

用例失败时,开发最常问的一句话是:"当时页面长什么样?"

为了省去这个沟通成本,我在 conftest.py 里配置了失败自动截图:

python 复制代码
@pytest.fixture(autouse=True)
def capture_screenshot(request, page):
    yield
    if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
        screenshot_bytes = page.screenshot(full_page=True)
        allure.attach(
            screenshot_bytes, 
            name="失败全屏截图", 
            attachment_type=allure.attachment_type.PNG
        )

效果: Allure 报告里失败用例旁边直接显示当时页面的全屏截图,开发一眼定位问题。


五、业务流程封装(CheckoutFlow)

结算流程涉及多个页面操作:登录 → 搜索 → 加购 → 填地址 → 选配送 → 选支付 → 提交订单。

我把这些操作封装到 CheckoutFlow 类里:

python 复制代码
class CheckoutFlow:
    def full_checkout_process(self, email, password, product_name):
        # 1. 清除历史 cookie
        self.page.context.clear_cookies()

        # 2. 登录
        self.login_page.navigate()
        self.login_page.login(email, password)
        self.login_page.verify_login_success()

        # 3. 清空购物车历史商品
        self.cart_page.clear_cart()

        # 4. 搜索并添加商品
        self.cart_page.search_and_add_product(product_name)

        # 5. 结算
        self.cart_page.go_to_checkout()
        self.checkout_page.fill_address(...)
        self.checkout_page.select_shipping_method()
        self.checkout_page.select_payment_method()
        self.checkout_page.confirm_order()
        self.checkout_page.verify_order_success()

效果: 测试用例只需要 3 行代码就能跑完整个结算流程,新增场景只需在 flows/ 里组合现有方法。

还有一个细节:每次执行前清空 Cookie 和购物车,确保用例之间互不干扰,这也是项目稳定运行 19 条全绿的关键。


六、19 条用例 100% 通过(测试覆盖)

模块 用例数 通过 通过率
登录 7 7 100%
注册 6 6 100%
购物车 5 5 100%
结算 1 1 100%
合计 19 19 100%

核心场景覆盖:

  • ✅ 正向/异常登录(错误密码、不存在邮箱、SQL 注入防护)
  • ✅ 空字段校验(邮箱为空、密码为空)
  • ✅ 正向/异常注册(邮箱已存在、邮箱格式无效、密码过短、隐私政策校验)
  • ✅ 购物车管理(加购、修改数量、删除商品)
  • ✅ 完整结算流程(搜索 → 加购 → 地址 → 配送 → 支付 → 提交订单)

⚠️ 如果你还没有这张截图,先跑一次测试,打开 Allure 报告截一张 Overview 全图,保存到项目 screenshots/ 目录。


七、踩坑记录

坑 1:每个用例都启动浏览器,太慢

现象: 刚开始每个用例都启动一次浏览器,19 条跑完要 60 多秒。

排查: fixture 作用域是 function(默认)。

解决: 改为 scope="session",复用同一个浏览器实例,执行时间降到 18 秒

坑 2:视觉回归测试跨平台失败

现象: 同样一张截图,Windows 本地能过,CI 的 Linux 环境就失败。

原因: 不同操作系统字体渲染不一样,像素对比有差异。

解决: 使用 Playwright 内置截图比对,设置像素容差阈值。

坑 3:购物车修改数量用例总是不稳定(重点)

test_update_cart_quantity 这条用例,我改过 3 个版本才彻底稳定:

第 1 版:用总价变化做断言

python 复制代码
assert initial_total != new_total

翻车: 修改数量后,页面总价没有立即更新(AJAX 异步刷新),脚本执行太快时拿到的还是旧值。

第 2 版:用数量变化做断言

python 复制代码
assert initial_qty != new_qty

翻车: 加购时商品默认数量已经是 2,修改成 2 后数量没变。排查发现 OpenCart 商品详情页的默认数量被改成了 2,不是标准的 1。

第 3 版(最终稳定)

先把数量改为 1(确保初始状态是 1),再改为 2,用精确断言 1 → 2:

python 复制代码
# 先把数量改成 1(确保初始状态是 1)
qty_input.fill("1")
# 提交表单
page.evaluate("document.querySelector('#shopping-cart form').submit()")
page.wait_for_load_state("networkidle")
# 刷新页面,确保数据更新
page.reload()
page.wait_for_load_state("networkidle")
# 然后改成 2
qty_input.fill("2")
# 验证:1 → 2
assert initial_qty == '1' and new_qty == '2'

教训:

  1. 断言要选确定性高的元素(数量输入框的 value 属性比总价更可靠)
  2. 数据要可预期(精确断言 1 → 2 比模糊断言 ≠ 更稳)
  3. 本地环境差异要考虑(OpenCart 不同版本的默认配置可能不同)

八、快速开始

1. 安装依赖

bash 复制代码
pip install -r requirements.txt
playwright install chromium

2. 启动 OpenCart

确保 XAMPP 已启动,OpenCart 可正常访问:http://127.0.0.1/opencart

3. 运行测试

bash 复制代码
# 运行所有测试(有头模式)
pytest tests/ -v --headed

# 运行指定模块
pytest tests/test_login_ui.py -v --headed

# 生成 Allure 报告
pytest tests/ -v --alluredir=./allure-results
allure generate ./allure-results -o ./allure-report --clean
allure open ./allure-report

4. 一键运行(Windows)

bash 复制代码
# 手动运行(生成报告并打开)
run_ui_tests.bat

# 定时任务模式(静默运行)
daily_test.bat

九、持续集成

方式 说明
定时任务 Windows 任务计划程序,每天 12:15 自动执行测试
飞书通知 测试完成后,飞书群自动收到统计报告
邮件备份 飞书不可用时的备用通知
GitHub Actions 每次 push/PR 自动运行测试
日志记录 每次运行记录到 test_log.txt,便于追溯
报告更新 Allure 报告自动更新,随时可查看

飞书通知示例:


十、能力清单

能力 体现
POM 四层架构 BasePage → 页面层 → 流程层 → 用例层
失败自动截图 pytest hook + allure.attach
浏览器复用 session 级 fixture,60 秒 → 18 秒
飞书通知 测试完成自动推送
环境自动配置 Allure 自动注入浏览器/OS/Python 版本

十一、自学建议:如果你想复刻

  1. 先跑起来,再谈架构 别一上来就四层架构,先写 3 个用例跑通,再慢慢拆分。

  2. 截图比日志有用 开发看不懂你的 print,但一定能看懂截图。

  3. 用例之间要隔离 清空 Cookie、清空购物车,是 19 条用例全绿的关键。

  4. 断言要选确定性高的 总价会变、文案会变,但输入框的 value 属性最稳。


十二、系列文章回顾

接口 + UI 全链路覆盖,形成一个完整的电商系统测试体系。


相关链接


你在做 UI 自动化时遇到过哪些坑?欢迎在评论区交流 👇

相关推荐
databook1 小时前
Beta 分布:如何用“成功”和“失败”来预测下次尝试
python·数据挖掘·数据分析
zhanghaha13141 小时前
Python进阶教程:27_subprocess 模块 零基础超详细教程
开发语言·python
兄弟加油,别颓废了。1 小时前
bugku题目
开发语言·前端·python
新网企兴1 小时前
2026西安GEO推广选哪家?新网企兴帮您精准获客
人工智能·python
2601_962297481 小时前
一、Python GUI 的十字路口:免费凑活还是付费破局
python·gui·tkinter·customtkinter·bravurasdk
万年咸鱼1 小时前
Java BufferedInputStream 详解:原理、用法与实战
java·开发语言·python
my05922 小时前
跳出单一信息工具局限:奥米豆构建认知与心智并行的学习范式
大数据·人工智能·python·学习
weixin199701080162 小时前
[特殊字符]《二手ERP × 闲鱼消息驱动架构:正向+逆向交易消息如何驱动WMS出库与回传》(附Python源码)
开发语言·python·架构
遇见小修修2 小时前
打印机连不上电脑无法打印
大数据·运维·python·电脑