跳转至

【实战】电子商务产品实战


目录

  • 产品分析
  • 测试用例分析
  • 编写脚本
  • 脚本优化
  • 生成测试报告

产品分析

  • 产品:litemall 管理后台
  • 功能:优惠券管理
  • 使用账户 - 用户名: hogwarts - 密码: test12345

被测产品地址


测试用例分析

用例标题 前提条件 用例步骤 预期结果 实际结果
添加优惠券 1. 登录并进入用户管理后台
2. 登录账号有商场管理的权限
1. 点击增加
2. 输入优惠券名称等全部信息
3. 点击确定
1. 跳转优惠券列表,有成功提示信息
2. 新增优惠券在列表最顶部一行
删除优惠券 1. 进入用户管理后台
2. 商品列表里面有已存在的优惠券(新增)
1. 点击删除按钮 1. 有删除成功提示信息
2. 被删除商品不在优惠券列表展示

编写脚本思路

uml diagram


前置后置

  • 在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设计模式之后解决这些问题