【实战】电子商务产品实战¶
目录¶
- 产品分析
- 测试用例分析
- 编写脚本
- 脚本优化
- 生成测试报告
产品分析¶
- 产品:litemall 管理后台
- 功能:优惠券管理
- 使用账户 - 用户名: hogwarts - 密码: test12345
测试用例分析¶
| 用例标题 | 前提条件 | 用例步骤 | 预期结果 | 实际结果 |
|---|---|---|---|---|
| 添加优惠券 | 1. 登录并进入用户管理后台 2. 登录账号有商场管理的权限 |
1. 点击增加 2. 输入优惠券名称等全部信息 3. 点击确定 |
1. 跳转优惠券列表,有成功提示信息 2. 新增优惠券在列表最顶部一行 |
|
| 删除优惠券 | 1. 进入用户管理后台 2. 商品列表里面有已存在的优惠券(新增) |
1. 点击删除按钮 | 1. 有删除成功提示信息 2. 被删除商品不在优惠券列表展示 |
编写脚本思路¶
前置后置¶
- 在setup_class 打开浏览器
- teardown_class 关闭浏览器进程
- 添加隐式等待配置
class TestLitemallDiscountCoupon:
def setup_class(self):
# 打开浏览器
self.driver = webdriver.Chrome()
# 添加隐式等待配置
self.driver.implicitly_wait(15)
def teardown_class(self):
# 关闭浏览器进程
self.driver.quit()
初步实现功能¶
- 登录功能
- 新增功能
- 删除功能
- 整体调试
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
class TestLitemallDiscountCoupon:
def setup_class(self):
# 初始化 driver
self.driver = webdriver.Chrome()
# 隐式等待
self.driver.implicitly_wait(15)
# 窗口最大化
self.driver.maximize_window()
# 登录
# 访问登录页面 url
self.driver.get("https://litemall.hogwarts.ceshiren.com/#/login?redirect=%2Fdashboard")
# 获取用户名输入框元素
username = self.driver.find_element(By.NAME, "username")
# 清空用户名输入框
username.clear()
# 输入用户名
username.send_keys("hogwarts")
# 获取密码输入框元素
password = self.driver.find_element(By.NAME, "password")
# 清空密码输入框
password.clear()
# 输入密码
password.send_keys("test12345")
# 点击登录按钮
self.driver.find_element(By.CSS_SELECTOR, '.el-button').click()
# 准备测试数据
self.coupon_name = "新人优惠券-test"
def teardown_class(self):
self.driver.quit()
def test_add_discount_coupon(self):
'''
添加优惠券冒烟用例
'''
# 点击左侧边栏中的 推广管理 按钮
self.driver.find_element(
By.XPATH,
"//span[text()='推广管理']"
).click()
# 点击展开之后的二级菜单 优惠券管理 按钮
self.driver.find_element(
By.XPATH,
"//span[text()='优惠券管理']"
).click()
# 点击优惠券管理页面的 添加 按钮
self.driver.find_element(
By.XPATH,
"//span[text()='添加']"
).click()
# 打开添加窗口,输入优惠券名称
self.driver.find_element(
By.XPATH,
"//label[text()='优惠券名称']/..//*[@class='el-input__inner']"
).send_keys(self.coupon_name)
# 输入优惠券介绍
self.driver.find_element(
By.XPATH,
"//label[text()='介绍']/..//*[@class='el-input__inner']"
).send_keys("测试优惠券")
# 输入标签
self.driver.find_element(
By.XPATH,
"//label[text()='标签']/..//*[@class='el-input__inner']"
).send_keys("测试")
# 输入最低消费金额
low_ele = self.driver.find_element(
By.XPATH,
"//label[text()='最低消费']/..//*[@class='el-input__inner']"
)
low_ele.clear()
low_ele.send_keys("100")
# 输入满减金额
reduce_ele = self.driver.find_element(
By.XPATH,
"//label[text()='满减金额']/..//*[@class='el-input__inner']"
)
reduce_ele.clear()
reduce_ele.send_keys("100")
# 输入每人限领数量
per_num_ele = self.driver.find_element(
By.XPATH,
"//label[text()='每人限领']/..//*[@class='el-input__inner']"
)
per_num_ele.clear()
per_num_ele.send_keys("2")
# 点击分发类型的下拉按钮
time.sleep(2)
self.driver.find_element(
By.XPATH,
"//label[text()='分发类型']/..//span[@class='el-input__suffix-inner']"
).click()
# 点击下拉列表中的 注册赠券
time.sleep(2)
eles = self.driver.find_elements(
By.XPATH,
"//div[@class='el-scrollbar']//span[text()='注册赠券']")
eles[1].click()
# 输入优惠券数量
num_ele = self.driver.find_element(
By.XPATH,
"//label[text()='优惠券数量']/..//*[@class='el-input__inner']"
)
num_ele.clear()
num_ele.send_keys("100")
# 输入优惠券有效的天数
days_ele = self.driver.find_element(
By.XPATH,
"//div[text()='天']/..//*[@class='el-input__inner']"
)
days_ele.clear()
days_ele.send_keys("15")
# 点击确定按钮
self.driver.find_element(
By.XPATH,
"//span[text()='确定']"
).click()
# //5秒后执行debugger命令,可以冻结当前页面,让弹窗不消失
# setTimeout(function(){debugger}, 5000)
time.sleep(1)
result = self.driver.find_element(
By.CSS_SELECTOR,
".el-notification__title"
).text
print(result)
result_msg = self.driver.find_element(
By.CSS_SELECTOR,
".el-notification__content > p"
).text
print(result_msg)
assert result == "成功"
assert result_msg == "创建优惠券成功"
# 验证新增的优惠券在列表中
eles = self.driver.find_elements(
By.CSS_SELECTOR,
".el-table_1_column_2.is-center.el-table__cell"
)
counpun_names = [e.text for e in eles]
print(counpun_names)
assert self.coupon_name in counpun_names
def test_delete_discount_coupon(self):
'''
删除优惠券冒烟用例
'''
time.sleep(5)
# 点击新增优惠券的删除按钮
self.driver.find_element(
By.XPATH,
f"//div[text()='{self.coupon_name}']/../..//*[text()='删除']"
).click()
time.sleep(1)
result = self.driver.find_element(
By.CSS_SELECTOR,
".el-notification__title"
).text
print(result)
result_msg = self.driver.find_element(
By.CSS_SELECTOR,
".el-notification__content > p"
).text
print(result_msg)
assert result == "成功"
assert result_msg == "删除优惠券成功"
# 验证删除的优惠券不在列表中
eles = self.driver.find_elements(
By.CSS_SELECTOR,
".el-table_1_column_2.is-center.el-table__cell"
)
counpun_names = [e.text for e in eles]
print(counpun_names)
assert self.coupon_name not in counpun_names
代码优化¶
- 强制等待全部替换为显式等待
- 使用
expected_conditions提供的方法 - 自定义显式等待条件
- 注意:代码片段,不要直接复制粘贴
# 注意:代码片段,不要直接复制粘贴
# 使用`expected_conditions`提供的方法
WebDriverWait(self.driver, 10).until_not(
expected_conditions.visibility_of_any_elements_located(
(By.XPATH, "元素定位")))
# 自定义显式等待条件
def click_execption(by, element, attempts=5):
def _inner(driver):
"""
多次点击同个按钮
:param driver:
:return:
"""
count = 0 # 实际循环次数
while count<attempts:
try:
count +=1
# 因为点击的过程可能出现报错,所以需要添加try 捕获报错的异常
driver.find_element(by, element).click()
return True
except Exception:
print("出现异常啦")
return False
return _inner
# 使用自定义的显式等待条件
WebDriverWait(self.driver,10).\
until(click_execption(
By.CSS_SELECTOR, "元素定位"))
添加日志¶
- 日志配置
import logging
import os
from logging.handlers import RotatingFileHandler
# 绑定绑定句柄到logger对象
logger = logging.getLogger(__name__)
# 获取当前⼯具⽂件所在的路径
root_path = os.path.dirname(os.path.abspath(__file__))
# 拼接当前要输出⽇志的路径
log_dir_path = os.sep.join([root_path, '..', f'/logs'])
if not os.path.isdir(log_dir_path):
os.mkdir(log_dir_path)
# 创建⽇志记录器,指明⽇志保存路径,每个⽇志的⼤⼩,保存⽇志的上限
file_log_handler = RotatingFileHandler(os.sep.join([log_dir_path, 'log.txt']),
maxBytes=1024 * 1024, backupCount=10, encoding="utf-8")
# 设置⽇志的格式
date_string = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter(
'[%(asctime)s] [%(levelname)s] [%(filename)s]/[line: %(lineno)d]/[%(funcName)s] %(message)s ', date_string)
# ⽇志输出到控制台的句柄
stream_handler = logging.StreamHandler()
# 将⽇志记录器指定⽇志的格式
file_log_handler.setFormatter(formatter)
stream_handler.setFormatter(formatter)
# 为全局的⽇志⼯具对象添加⽇志记录器
# 绑定绑定句柄到logger对象
logger.addHandler(stream_handler)
logger.addHandler(file_log_handler)
# 设置⽇志输出级别
logger.setLevel(level=logging.INFO)
添加截图¶
save_screenshot方法- 提前创建图片保存路径
def get_step_screenshot(self):
'''
截图
'''
timestamp = int(time.time())
# 前提-在当前路径需要有一个images 文件夹
# 获取当前文件所在的目录路径
root_path = os.path.dirname(os.path.abspath(__file__))
# 拼接截图保存路径,windows 系统的同学注意这里拼接 \\
file_path = f"{root_path}/screenshot_{timestamp}.png"
logger.info(f"截图保存路径为 {file_path}")
self.driver.save_screenshot(file_path)
allure.attach.file(
file_path, name="pic",
attachment_type=allure.attachment_type.PNG
)
生成测试报告¶
- 安装 Allure 程序
- 安装 allure-pytest 插件
# 执行测试用例,生成测试报告数据
pytest 用例文件 --alluredir=报告路径
# 生成测试报告在线地址
allure serve 报告路径
目前脚本存在的问题¶
- 很多重复代码,维护困难
- 没有办法清晰的描述业务场景
学习PO设计模式之后解决这些问题