news 2026/7/19 2:34:48

从 0 到 1 搭建 Selenium + Pytest + Page Object 自动化测试框架实战(二):购物车页面与跨页面业务流测试

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从 0 到 1 搭建 Selenium + Pytest + Page Object 自动化测试框架实战(二):购物车页面与跨页面业务流测试

上一篇完成了商品列表页的 Page Object 封装与基础用例设计。本篇继续推进 SauceDemo 自动化测试项目,目标是完成购物车页面的自动化覆盖,并解决商品页与购物车页之间的数据传递、状态校验和业务流程复用问题。

上一篇:从 0 到 1 搭建 Selenium + Pytest + Page Object 自动化测试框架实战(一):商品列表页自动化测试设计

测试网站:https://www.saucedemo.com/

一、本阶段目标与成果

如果只验证“点击购物车图标后 URL 是否正确”,购物车测试其实很快就能写完。

但真实的购物流程不只包含页面跳转,还需要验证:加入购物车的商品是否正确、名称和价格是否一致、删除后状态是否恢复、刷新后数据是否保留,以及是否能够继续进入结算页面。

因此,本阶段的重点从“单页面操作”进一步推进到了“跨页面业务闭环”。

模块本阶段实现内容当前状态
BasePage增加无等待的多元素查询,支持购物车为空等正常状态已完成
InventoryPage获取商品名称与价格、等待购物车数量及按钮文字变化已完成
CartPage查询、删除、清空商品,继续购物与进入结算已完成
InventoryService组合“获取商品信息 → 加入购物车 → 进入购物车”业务流程已完成
test_cart.py覆盖数据一致性、删除、状态切换、刷新和结算跳转8 条用例通过
Checkout 页面用户信息填写、订单汇总与完成订单下一阶段

为了方便读者复现,本次项目运行环境如下:

环境版本或配置
Python3.11.2
Selenium4.45.0
Pytest8.3.5
测试网站SauceDemo
批量执行模式Chrome Headless

二、项目结构升级:从 Page 到 Service

1. 当前目录结构

本阶段新增了cart_page.pyinventory_service.py,项目不再只有 Base 层、Page 层和测试层,而是开始引入业务流程层。

SWAG_Lags_Autotest/ ├── base/ │ └── base_page.py ├── pages/ │ ├── login_page.py │ ├── inventory_page.py │ └── cart_page.py ├── services/ │ └── inventory_service.py ├── testcases/ │ ├── test_login.py │ ├── test_inventory.py │ └── test_cart.py ├── utils/ │ └── utils.py └── conftest.py

各层之间的调用关系如下:

test_cart.py │ ├── 调用 InventoryService 组织跨页面流程 │ └── 调用 InventoryPage 获取商品信息并加入购物车 │ ├── 调用 CartPage 完成购物车页面操作 │ └── 使用 assert 校验最终业务结果 InventoryPage / CartPage └── 继承 BasePage,复用 Selenium 基础能力

这里需要注意:Service 层并不是为了增加项目复杂度,而是为了存放需要组合多个页面动作的业务流程。单次点击、读取文字等操作仍然由 Page 层负责。

2. InventoryService:封装跨页面业务流程

最初的测试代码通常会直接写成:

读取商品名称 → 读取商品价格 → 点击 Add to cart → 点击购物车图标

当多个用例都需要这段准备流程时,重复代码会越来越多。因此新增InventoryService,专门组合跨页面动作。

frompages.inventory_pageimportInventoryPageclassInventoryService:def__init__(self,driver):self.inventory_page=InventoryPage(driver)defadd_product_and_go_to_cart(self,index):self.inventory_page.add_product_by_index(index)self.inventory_page.go_to_cart()defprepare_single_product_cart(self,index):name=self.inventory_page.get_product_name_by_index(index)price=self.inventory_page.get_product_price_by_index(index)self.add_product_and_go_to_cart(index)returnname,price

prepare_single_product_cart()在离开商品页之前先保存预期名称和价格,然后完成加入购物车与页面跳转,最后把预期数据交给测试层。

三层职责因此变得清晰:

层级关注点示例
Page 层页面有什么元素、能够执行什么操作点击按钮、读取名称、删除商品
Service 层多个页面动作如何组成业务流程获取商品信息并进入购物车
Test 层预期结果是否成立名称、价格、数量和状态断言

图 1 项目分层与调用关系

三、购物车页面对象设计

图 2 SauceDemo 购物车页面核心元素

1. 使用无等待查询处理购物车空状态

在商品列表页中,购物车角标只有在购物车存在商品时才会显示;在购物车页面中,删除全部商品后,.cart_item元素也会直接从 DOM 中消失。

如果继续使用带显式等待的find_elements(),程序会把“元素本来就应该不存在”的正常状态当成异常,并等待到超时。

因此在BasePage中增加一个允许返回空列表的方法:

deffind_elements_no_wait(self,locator):"""不等待,直接返回元素列表,允许结果为空。"""returnself.driver.find_elements(*locator)

这样,获取购物车数量时就可以自然地处理空状态:

defget_cart_count(self):badges=self.find_elements_no_wait(self.cart_badge)ifnotbadges:return0returnint(badges[0].text)

这个改动看起来很小,但它体现了一个重要原则:

显式等待适合“元素最终应该出现”的场景;无等待查询适合“元素不存在也是合法结果”的场景。

2. CartPage:只描述购物车页面能够做什么

购物车页面主要包含商品列表、商品名称、价格、删除按钮、继续购物按钮和结算按钮。

2.1 核心元素定位
frombase.base_pageimportBasePagefromselenium.webdriver.common.byimportByclassCartPage(BasePage):cart_items=(By.CLASS_NAME,"cart_item")item_names=(By.CSS_SELECTOR,".inventory_item_name")item_prices=(By.CSS_SELECTOR,".inventory_item_price")remove_buttons=(By.CSS_SELECTOR,".cart_item button")checkout_button=(By.ID,"checkout")continue_shopping_button=(By.ID,"continue-shopping")

定位器统一放在页面对象中。后续如果页面属性发生变化,只需要修改CartPage,测试用例不需要到处调整定位代码。

2.2 获取购物车中的所有商品名称
defget_all_product_names(self):elements=self.find_elements(self.item_names)return[el.text.strip()forelinelements]

该方法返回一个纯字符串列表,测试层不需要直接处理WebElement,断言会更加直观。

2.3 按商品名称删除,而不是依赖固定索引
defremove_product_by_name(self,target_name):names=self.find_elements(self.item_names)buttons=self.find_elements(self.remove_buttons)forindex,elementinenumerate(names):ifelement.text.strip()==target_name:buttons[index].click()returnraiseException(f"购物车中未找到商品:{target_name}")

索引适合操作固定位置的商品,但购物车经过删除后,剩余商品的索引会发生变化。测试“删除指定商品”时,使用商品名称作为业务标识更稳定,也更符合真实测试场景。

2.4 清空购物车时重新获取按钮
defremove_all_products(self):whileTrue:buttons=self.find_elements_no_wait(self.remove_buttons)ifnotbuttons:breakself.driver.execute_script("arguments[0].click();",buttons[0],)

每删除一个商品,页面 DOM 都会发生变化,之前保存的按钮元素可能失效。因此循环中需要重新查询按钮,而不是在循环外一次性获取全部元素。

四、购物车自动化测试用例设计

本阶段共编写 8 条购物车测试用例:

编号测试场景核心断言
CART-01单个商品加入购物车名称和价格与商品页一致
CART-02多个商品加入购物车实际商品集合与预期集合一致
CART-03删除指定商品目标商品不再出现在购物车中
CART-04删除全部商品购物车商品数量为 0
CART-05添加后再删除商品角标数量与按钮文字正确切换
CART-06返回商品页后再次进入购物车已添加商品仍然保留
CART-07非空购物车点击 Checkout进入checkout-step-one页面
CART-08刷新购物车页面商品数据保持不变

这 8 条用例不仅验证页面能否操作,还覆盖了数据一致性、状态变化和页面间的数据持久性。

五、关键业务场景验证

1. 验证商品名称与价格一致

deftest_cart_product_consistency(driver):login(driver)inventory_service=InventoryService(driver)cart_page=CartPage(driver)expected_name,expected_price=(inventory_service.prepare_single_product_cart(3))cart_index=cart_page.get_product_index_by_name(expected_name)actual_name=cart_page.get_product_name_by_index(cart_index)actual_price=cart_page.get_product_price_by_index(cart_index)assertactual_name==expected_nameassertactual_price==expected_price

该用例没有直接写死商品名称和价格,而是在商品页动态获取预期数据,再与购物车页的实际数据进行比较。

这样做的好处是:即使测试网站以后调整商品价格,只要商品页和购物车页保持一致,用例仍然能够正确验证业务逻辑。

2. 验证添加与删除后的状态闭环

购物车测试不能只关注购物车页面,还要确认返回商品列表页后,按钮和角标是否恢复。

deftest_add_and_remove_product_state_toggle(driver):login(driver)inventory_page=InventoryPage(driver)cart_page=CartPage(driver)index=0initial_cart_count=inventory_page.get_cart_count()assertinitial_cart_count==0inventory_page.add_product_by_index(index)button_text=inventory_page.wait_for_product_button_text_by_index(index,"Remove",).lower()assert"remove"inbutton_textassertinventory_page.get_cart_count()==initial_cart_count+1target_name=inventory_page.get_product_name_by_index(index)inventory_page.go_to_cart()cart_page.remove_product_by_name(target_name)cart_page.go_back_to_inventory()button_text=inventory_page.wait_for_product_button_text_by_index(index,"Add to cart",).lower()assert"add to cart"inbutton_textassertinventory_page.get_cart_count()==initial_cart_count

这条用例形成了完整闭环:

购物车为空 ↓ 添加商品 ↓ 按钮变为 Remove,角标数量 +1 ↓ 进入购物车并删除商品 ↓ 返回商品页 ↓ 按钮恢复 Add to cart,角标数量恢复

图 3 添加与删除后的页面状态闭环

六、稳定性与执行策略优化

1. 用显式等待替代固定休眠

在前一阶段的学习代码中,部分操作依赖time.sleep()。固定休眠虽然直观,但页面可能提前完成,也可能在网络较慢时仍未完成,既浪费时间又不稳定。

本阶段根据真实状态进行等待:

defwait_for_cart_count(self,expected_count):WebDriverWait(self.driver,self.TIMEOUT).until(lambda_driver:self.get_cart_count()==expected_count)returnself.get_cart_count()

按钮文字变化也采用同样思路,并额外处理 DOM 更新时可能出现的StaleElementReferenceException

defwait_for_product_button_text_by_index(self,index,expected_text):expected=expected_text.strip().lower()defbutton_text_matches(_driver):buttons=self.find_elements_no_wait(self.add_to_cart_buttons)ifindex<0orindex>=len(buttons):returnFalsetry:actual_text=buttons[index].text.strip()exceptStaleElementReferenceException:returnFalseifactual_text.lower()==expected:returnactual_textreturnFalsereturnWebDriverWait(self.driver,self.TIMEOUT).until(button_text_matches)

等待的对象从“固定秒数”变成了“业务状态已经满足”,测试稳定性和执行效率都会更合理。

2. Fixture 的运行模式优化

为了兼顾单条用例调试和批量执行效率,conftest.py会根据 pytest 的执行目标切换浏览器模式:

  • 直接指定某一条用例:使用可视化浏览器,方便观察和调试;
  • 执行整个测试文件或全部用例:使用无头模式,减少界面干扰。
defis_single_test_target(config:pytest.Config)->bool:invocation_args=config.invocation_params.argsreturnany("::"instr(argument)forargumentininvocation_args)

这种方式不需要在代码中手动修改headless配置,只需要改变 pytest 命令即可。

七、测试执行与结果分析

执行购物车测试文件:

python-mpytest testcases/test_cart.py-q

本机实际执行结果:

........ [100%] 8 passed in 116.76s (0:01:56)

图 4:Pytest 执行结果截图

至此,当前购物车页面的 8 条自动化测试全部通过。

八、阶段复盘与下一步计划

本阶段最重要的收获并不是多写了一个CartPage,而是让项目开始具备跨页面业务测试的结构。

已解决的问题

  1. 使用无等待查询正确处理“元素不存在也是正常结果”的场景;
  2. 使用商品名称定位目标商品,降低删除操作对固定索引的依赖;
  3. 通过 Service 层复用跨页面准备流程;
  4. 使用显式等待替代固定休眠;
  5. 从简单 URL 断言扩展到名称、价格、数量和状态一致性断言;
  6. 完成添加、删除、返回、刷新和结算跳转等购物车核心路径覆盖。

仍可继续优化的地方

  1. 将账号、密码、基础地址等固定数据移入配置文件;
  2. 为测试数据增加 pytest 参数化;
  3. 增加失败截图、日志和 Allure 测试报告;
  4. 对异常用户和边界场景进行补充;
  5. 继续完成 Checkout 信息页、订单概览页和完成订单页的自动化测试。

下一阶段计划

下一阶段将进入 Checkout 结算流程,计划覆盖:

  • 姓名、邮编等用户信息填写;
  • 必填项为空时的错误提示;
  • 订单商品与价格汇总校验;
  • 商品小计、税费和总价计算;
  • 完成订单后的成功提示;
  • 从购物车到订单完成的端到端业务流程。

项目也会继续从“能够运行”向“稳定、可维护、可扩展”推进。


如果你也在学习 Selenium、Pytest 和 Page Object Model,欢迎一起交流自动化测试中的设计思路与踩坑记录。

推荐标签:SeleniumPytestPythonPage Object ModelUI 自动化测试软件测试

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/19 2:33:30

2026最新8款团队协作AI编程工具平替实测合集

我是一名带过3个研发团队的DevOps工程师&#xff0c;2024年下半年我们团队承接了星云V2.0车联网数据平台的迭代需求&#xff0c;当时12个后端开发、6个前端、3个运维要协同完成300接口的重构&#xff0c;最头疼的就是不同成员用的编程工具不统一&#xff0c;代码风格差异大&…

作者头像 李华
网站建设 2026/7/19 2:32:32

基于MSP432与CC3120的嵌入式Wi-Fi通信实战:RTOS集成与系统设计

1. 项目概述与核心价值最近在做一个基于TI MSP432的智能小车项目&#xff0c;核心需求是让小车能通过Wi-Fi实时上报传感器数据&#xff0c;并接收来自云端的控制指令。这听起来像是很多物联网&#xff08;IoT&#xff09;项目的标准配置&#xff0c;但真动手把Wi-Fi模块、实时操…

作者头像 李华
网站建设 2026/7/19 2:32:23

Meta AI青少年心理风险检测:NLP技术与隐私保护的平衡实践

最近&#xff0c;Meta AI 推出了一项备受关注的新功能&#xff1a;当系统检测到青少年用户在平台上讨论自杀或自残话题时&#xff0c;将自动向家长发送通知。这项功能看似简单&#xff0c;背后却涉及复杂的技术实现和伦理考量。作为技术从业者&#xff0c;我们更关心的是&#…

作者头像 李华
网站建设 2026/7/19 2:31:49

短视频直播出海欧美调优实战:360CDN 搭配 Nginx 流媒体完整配置指南

做跨境短视频、海外实时直播的运维常会踩坑&#xff1a;直接套用网站静态资源 CDN 配置&#xff0c;没有针对流媒体推拉流、大文件视频做专项优化&#xff0c;最终出现缓冲严重、带宽成本失控、盗链泛滥等问题。本文结合线上欧美流媒体真实运维经验&#xff0c;完整讲解 360CDN…

作者头像 李华
网站建设 2026/7/19 2:31:42

Arduino UNO开发板详解与实战应用

1. Arduino UNO开发板全面解析作为全球最受欢迎的开源硬件平台之一&#xff0c;Arduino UNO已经成为了无数电子爱好者和专业开发者的入门首选。这款基于ATmega328P微控制器的开发板&#xff0c;以其稳定的性能、丰富的扩展性和友好的开发环境&#xff0c;在创客教育、智能家居、…

作者头像 李华
网站建设 2026/7/19 2:28:31

C++编码问题解析与UTF-8最佳实践

1. C编码问题引发的编译报错深度解析上周在重构一个多语言项目时&#xff0c;我的VS2019突然弹出一连串"warning C4819: 该文件包含不能在当前代码页(936)中表示的字符"的警告&#xff0c;紧接着就是各种"error C2001: 常量中有换行符"的编译错误。这让我不…

作者头像 李华