测试场景:用UI自动化测试一个流程:登录———加入购物车场景
以小米商城为例,测试流程:
首页(定位登录)-登录(输入用户和密码)- 首页(查询)- 商品页(定位第一个商品)-商品详情页(定位购物车)-购物车页面(认购)-购物车页面
1、common层
deal_log文件:
import logging
from datetime import datetime
from config.config import *
class Logs:
# logging模块默认设置的日志级别是warning,而debug和info的级别是低于warning的,所以不会打印这两种日志信息
def __init__(self, logger=None):
# 1.定义日志收集器
# 创建日志器logger对象以及日志级别
self.logger = logging.getLogger(logger) # 初始化日志类,定义一个日志收集器
self.logger.setLevel(logging.DEBUG) # 设置收集器的级别,不设定的话,默认收集warning及以上级别的日志
# self.logger.setLevel("DEBUG") # 也可以设置日志级别
# self.logTime = datetime.now().strftime("%Y_%m_%d_%H_%M")
self.logTime = datetime.now().strftime("%Y_%m_%d")
self.logPath = LogPath
self.logName = self.logPath + self.logTime + '.log'
# self.logName = r'../logs\{}.log'.format(time.strftime('%Y-%m-%d-%H:%M:%S', time.localtime())) # r是防止字符转转义,保留原有的样子(注意:返回上一次的路径是两个..)
# 创建处理器handler以及日志级别
# conlHandler = logging.StreamHandler() # 此处理器是输出到控制台
fileHandler = logging.FileHandler(self.logName, 'a', encoding='utf-8') # 此处理器是输出到文件
# conlHandler.setLevel('DEBUG') # 设置控制台输出日志级别ERROR 或DEBUG
fileHandler.setLevel('INFO') # 设置文件输出日志级别 ERROR'
# 设置日志输出格式
# %(asctime)s:打印日志的时间, %(filename)s:打印当前执行程序名, %(levelname)s:打印日志级别名称,
# %(lineno)s:打印日志的当前行号, %(message)s:打印日志信息, %(name)s:Logger的名字
# conlFormatter = logging.Formatter(
# "%(asctime)s-%(lineno)d-[%(levelname)s]-[msg]: %(message)s") # 输出到控制台的格式
fileFormatter = logging.Formatter(
"%(asctime)s-%(name)s-%(lineno)d-[%(levelname)s]-[msg]: %(message)s") # 输出到文件中的格式
# 用setFormatter()将上面设置的formatter配置到handler中
# conlHandler.setFormatter(conlFormatter) # 配置控制台日志输出格式
fileHandler.setFormatter(fileFormatter) # 配置文件日志输出格式
# 用addHandler()将配置好格式的Haddler添加到logger中,进行过滤
# self.logger.addHandler(conlHandler)
self.logger.addHandler(fileHandler)
# 添加下面一句,在记录日志之后移除句柄
# self.logger.removeHandler(ch)
# self.logger.removeHandler(fh)
# 关闭处理器
# conlHandler.close()
fileHandler.close()
def get_log(self):
return self.logger
deal_operate.py文件:
# !/usr/bin python3
# encoding : utf-8 -*-
# @author : ShanShan
# @software : PyCharm
# @file : deal_operate.py
# @Time : 2021/5/31 16:06
from datetime import datetime
from commons.deal_log import Logs
from selenium import webdriver
from configs.config import ImagePath
class Operates():
# 初始化页面的操作
def __init__(self):
"""
构造函数,创建必要的实例变量
"""
self.driver = None
self.log = Logs().get_log() # 初始化一个log对象
def openBrowser(self, driver='gc'):
"""
打开不同的浏览器
:param driver: gc=谷歌浏览器;ff=Firefox浏览器; ie=IE浏览器
:return:
"""
if driver == 'gc':
self.driver = webdriver.Chrome()
elif driver == 'ff':
self.driver = webdriver.Firefox()
elif driver == 'ie':
self.driver = webdriver.Ie()
else:
self.log.info("不支持,请在此添加其他浏览器代码实现!")
pass
# 默认隐式等待10s
self.driver.implicitly_wait(10)
def click(self, *locator):
"""
找到元素,并点击
:param locator: 定位器
:return:
"""
try:
self.driver.find_element(*locator).click()
except Exception as e:
self.log.error("定位点击元素失败!")
def click_ele_exist(self, *locator):
"""
当定为元素出现时,定位元素,并点击
:param locator: 定位器
:return:
"""
try:
self.driver.find_element(*locator)
btns = self.driver.find_elements(*locator)
# 如果出现用户协议弹出按钮
if btns:
btns[0].click()
except Exception as e:
self.log.error("定位有时出现的点击元素失败!")
def input_text(self, value, *locator):
"""
定位元素,并完成输入
:param text:
:param locator:
:return:
"""
try:
self.driver.find_element(*locator).send_keys(str(value))
except Exception as e:
self.log.info("定位输入元素失败!")
# 根据css定位的元素
def by_js(self, css):
try:
self.driver.execute_script(f"document.querySelector('{css}')")
except Exception as e:
self.log.error("定位css元素失败")
def switch_window(self, target_title):
"""
切换到新窗口
:param target_title:
:return:
"""
try:
for handle in self.driver.window_handles:
self.driver.switch_to.window(handle)
# 判断切换到窗口句柄——-判断当前窗口的标题是否是:<header>中的<title>
if target_title == self.driver.title:
self.log.info("切换窗口句柄到产品详情页")
break
except Exception as e:
self.log.error("切换新窗口失败!")
def into_iframe(self, element):
"""
进入iframe
:param element: ?
:return:
"""
try:
ele = self.driver.find_element(element)
self.driver.switch_to.frame(ele)
except Exception as e:
self.log.error("切换框架frame失败!")
def get_text(self, *locator):
"""
获取文本元素
:param locator:
:return:
"""
try:
ele = self.driver.find_element(*locator)
except Exception as e:
self.log.error("获取元素文本失败!")
else:
return ele.text
def get_url(self, url=None):
"""
打开网站
:param url: 网站地址
:return:
"""
try:
self.driver.get(url)
except Exception as e:
self.log.error("打开url失败!")
def getImage(self, image_name):
"""
生成用例失败的截图
:param image_name: 截图的名称
:return:
"""
try:
nowTime = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
NewPicture = ImagePath + '\\' + nowTime + '_' + image_name + '.png' # 保存图片为png格式
self.driver.get_screenshot_as_file(NewPicture)
except Exception as e:
self.log.error(u'截图失败!')
def quit(self):
"""
退出浏览器
:return:
"""
try:
self.driver.quit()
except Exception as e:
self.log.error("退出浏览器失败!")
# 后退
def back(self):
self.driver.back()
# 前进
def forword(self):
self.driver.forword()
# if __name__ == '__main__':
# # ele =BasePage()
# path = os.path.dirname(os.path.abspath('.'))
# print(path)
deal_yaml.py文件
import yaml
def read_yaml(key):
"""
读取yaml文件
:return:
"""
try:
# with open(path, encoding='utf-8') as f:
# eles = yaml.safe_load(f)
path = DataPath # 配置在congfig中的相对路径
# path = '../data/test02.yaml'
openYaml = open(path, 'r', encoding='UTF-8')
datas = yaml.load(openYaml, Loader=yaml.FullLoader)
data = datas[key]
return data
except Exception as e:
print(u"未找到yaml文件")
2、config层
config.py文件
DataPath = "../data/test03.yaml" # yaml文件的路径
LogPath = "../logs/" # 日志文件存放路径
ImagePath = "../pictures/" # 失败用例截图存放路径
3、data层
test03.yaml文件
xiaomiProcedure: # 一个执行函数的数据
-
title: 流程成功
description: 流程成功的用例,用来测试正确的用户名和密码
caseDatas:
stepName1: 点击首页的登录按钮 # 也可也直接写在代码中
login_btn: ['css selector','#J_siteUserInfo>a:nth-child(1)'] # 函数中定位的数据和输入的值
stepName2: 登录页面输入用户名和密码
user_proxy: ['css selector','.btn.btn-primary']
user_input: ['xpath','//input[@name="account"]']
psw_input: ['xpath','//input[@name="password"]']
search_input: ['id','search']
log_btn: ['css selector','.mi-button.mi-button--primary']
first_item: ['css selector','.goods-list.clearfix>div:nth-child(1) a']
buy_btn: ['css selector','.btn.btn-small.btn-primary.J_nav_buy']
addCart_btn: ['css selector','.sale-btn']
check_ele: ['id','J_miniHeaderTitle']
4、test_cases层
test_procedure002文件
import allure
import pytest
from common.deal_operate import Operates
from common.deal_yaml import read_yaml
from common.deal_log import Logs
import time
# 首页(定位登录)-登录(输入用户和密码)- 首页(查询)- 商品页(定位第一个商品)-商品详情页(定位购物车)-购物车页面(认购)-购物车页面
@allure.feature('小米商城认购流程自动化')
class Test_indexPage:
def setup_class(self):
"""
构造函数,创建对象时会执行
初始化浏览器
:return:
"""
print("执行setup_class-------")
self.baseWeb = Operates() # 创建一个对象
self.baseWeb.openBrowser() # 打开浏览器
self.log = Logs().get_log() # 获取一个对象的get_log()函数
self.item = "小米11"
def teardown_class(self):
print("结束teardown_class—————————")
self.baseWeb.quit()
# 进入登录页
@allure.story('认购流程') # 同一个分组,比如成功和失败
@pytest.mark.parametrize('data', read_yaml('xiaomiProcedure'))
def test_procedure(self, data, get_user):
# 首页
print('获取data的数据类型:------', type(data))
print('获取data数据:------', data)
self.log.info("执行test_to_login-------")
allure.dynamic.title(data['title']) # allure:获取yaml文件中的title
allure.description(data['description']) # allure: 获取yaml文件中的description
print('获取data的title数据:------', data['title'])
self.baseWeb.get_url('https://www.mi.com/index.html') # 小米首页
caseData = data['caseDatas']
with allure.step(caseData['stepName1']):
self.baseWeb.click(*caseData['login_btn']) # 点击首页的登录按钮
self.baseWeb.click_ele_exist(*caseData['user_proxy']) # 首页————点击协议声明的同意按钮(注意:这个跳转窗口不一定每次都出现,比如只有第一次才会出现)
# 登录页面————输入登录用户名和密码
with allure.step(caseData['stepName2']):
name, psw = get_user
self.baseWeb.input_text(name, *caseData['user_input'])
self.baseWeb.input_text(psw, *caseData['psw_input'])
time.sleep(2)
# 登录页面——点击登录按钮
with allure.step('点击登录页面的登录按钮'):
self.baseWeb.click(*caseData['log_btn'])
time.sleep(2)
# 返回首页_进行查询
allure.step('在首页进行查询')
self.baseWeb.input_text(self.item+'\n', *caseData['search_input'])
# 商品页面——点击第一个商品
with allure.step('点击商品页面的第一个商品'):
self.baseWeb.click(*caseData['first_item'])
# 切换新窗口
self.baseWeb.switch_window('小米11 12GB+256GB')
# 商品详情页——点击 立即购买
with allure.step("点击商品详情页上立即认购"):
time.sleep(2)
self.baseWeb.click(*caseData['buy_btn'])
# 切换一个新窗口
self.baseWeb.switch_window('小米11立即购买-小米商城')
# 立即认购页面-点击 立即预定
with allure.step("点击立即认购页面上的立即预定"):
self.baseWeb.click(*caseData['addCart_btn'])
# 购物车页面——校验
with allure.step("校验是否进入认购页面"):
try:
res = self.baseWeb.get_text(*caseData['check_ele'])
print("检查的数据:--", res)
assert '儿童节' in res # 校验是失败的
except AssertionError:
self.baseWeb.getImage('购物车页面') # 添加了失败截图的功能
# assert False
else:
assert True
conftest.py文件
import pytest
from selenium.webdriver.android import webdriver
@pytest.fixture(scope='session')
def get_user():
Name = '15********'
Pwd = '*******'
print('调用Name------')
yield Name, Pwd
5、run.py层
import os
import pytest
pytest.main(['-s', 'test_cases/test_procedure002.py', '--alluredir', './temp']) # 生成allure报告,并放在./temp
os.system('allure generate ./temp -o ./report --clean')
6、pictures层
主要是用来存放用例失败时生成的截图(在test_procedure002.py 最后一步有添加此功能)