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'
教训:
- 断言要选确定性高的元素(数量输入框的 value 属性比总价更可靠)
- 数据要可预期(精确断言 1 → 2 比模糊断言 ≠ 更稳)
- 本地环境差异要考虑(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 版本 |
十一、自学建议:如果你想复刻
-
先跑起来,再谈架构 别一上来就四层架构,先写 3 个用例跑通,再慢慢拆分。
-
截图比日志有用 开发看不懂你的 print,但一定能看懂截图。
-
用例之间要隔离 清空 Cookie、清空购物车,是 19 条用例全绿的关键。
-
断言要选确定性高的 总价会变、文案会变,但输入框的 value 属性最稳。
十二、系列文章回顾
- 第一篇: OpenCart 接口自动化测试实战:pytest + Allure + 数据驱动(API 层)
- 第二篇: OpenCart UI 自动化测试实战(本文)
接口 + UI 全链路覆盖,形成一个完整的电商系统测试体系。
相关链接
你在做 UI 自动化时遇到过哪些坑?欢迎在评论区交流 👇