1、UI自动化规划
熟悉业务-》确定方案-》选取场景-》了解前端-》定位元素-》编写脚本-》运行优化-》回归报告-》项目汇总
价值意义:
自动化执行需要:模块化
需要可制定化执行
可复用性
PO模式:
将页面定位和业务分开,元素的定位单独处理,执行脚本单独封装。维护方便。
封装BasePage类,在基类中具有webdriver的实例的属性,把每个页面继承BasePage,通过driver管理每个页面的元素,将页面再细分一个个方法。Testcase依赖page类,进行组织化的测试步骤。
维护:
元素变化了:维护每个page
测试步骤变化:维护Testcase
每个page的常规操作封装基类:
点击动作、输入、文本处理、滑动、弹窗……
业务分类:
登录模块(登录、注册新用户)、
首页模块(菜单栏、主页显示)、
管理模块(数据列表展示、新增、分类、……)、
用户模糊(用户数据信息展示)……
功能测试用例依据以此分类设计
2、PageObject设计模式
系统梳理分类
代码层面分类:
系统System:
其中:pageObjects
包testCase
包common
包configs
包utils
包outFiles
文件夹(logs/reports/screenshoot
)docs
文件夹data
文件夹
PO模式从下往上,从底层开始写:driver-》BasePage-》pages-》test执行-》报告python+selenium+pytest+allure
.
3、PO模式封装
1、驱动模块(固定写法)
封装驱动
写在:common
包下
新建myDriver.py
文件,建一个兼容浏览器的驱动类,就是dirver
"""
驱动模块
1、考虑到浏览器的兼容性,可以分为主流的几个浏览器驱动
1、谷歌
2、火狐
3、其他
"""
from selenium import webdriver
from configs.config import implicitly_wait_time
# 设置单例模式
'''
类创建实例:
1、先调用—__new__()创建方法,一般类中不写,自动调用
2、再调用初始化方法__init__()
判断一个类是否有实例,如果有,就不会创建新的实例
'''
# 哪一个类的实例需要使用单例模式,就直接继承这个类(`固定写法`)
class Single(object):
#new方法创建对象
def __new__(cls, *args, **kwargs):
# 判断当前的类是否已经实例化
if not hassttr(cls, '_instance'):
cls._instance = super().__new__(cls)# super是父类的new方法
return cls._instance
class Driver(Single):
# 新建一个初始值
_driver = None
# 判断使用浏览器,---------默认指定谷歌浏览器
def get_driver(self, browser_name='chrome'):
if self._driver is None:
if browser_name == "chrome":
self._driver = webdriver.Chrome()
elif browser_name == "firefox":
self._driver = webdriver.Firefox()
else:
raise (f"没有这个{browser_name}浏览器,请使用可用浏览器打开")
# 设置隐式等等时间---设置`**全局变量`**等等时间5s
self._driver.implicitly_wait(implicitly_wait_time)
# 浏览器对大化
self._driver.maximize_window()
return self._driver # 返回浏览器对象
if __name__ == '__main__':
Driver().get_driver('chrome')# 创建实例对象,调用对应的方法,传什么浏览器使用什么浏览器
驱动模块封装完毕,后续只需要调用Driver().get_driver('chrome')
就可以使用webdriver进行元素定位
等同于对driver= webdriver.Chrome()
的封装,方便兼容不同浏览器。
1、隐式等待时间全局变量参数化
在configs
包下创建config
文件,设置隐式等待时间参数5s或者10s,自定义
implicitly_wait_time = 5
2、基类封装(基本框架固定写法)
基类
写在:common
包下,新建一个文件basePage.py
- 创建基类basePage
作用:1、把基本的页面操作封装好,给其他页面继承使用
包含:2、点击操作(click
)、输入框输入(input_text
)、获取元素属性值(get_attrbute
)、元素可见(visibility
)、截图(save_screenshot
)、清除(clear
),获取文本内容(get_text
)、显示等待presence_of
(元素可见(visibility_of
),可操作点击(clickable))……等操作指令的封装
from common.myDriver import Driver
import time
import os.path
from utils.handle_logs import logger
from utils.handle_path import screenshot_path, config_path
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from utils.handle_yaml import get_yaml_data
from common.myDriver import Driver
from configs_delivery.config import wait_timeout, wait_poll_frequency
class BasePage:
def __init__(self): # 调用basepage 需要的浏览器对象,默认谷歌浏览器
self._driver = Driver().get_driver()
# ------------------------封装截图方法---------------------------
# 截图操作:可以使用浏览器对象去调用,也可以使用元素调用
def get_page_screenshot(self, action=None):
# 注意截图的描述:哪个动作+时间
curTime = time.strftime('%Y%m%d%H%M%S', time.localtime())
filepath = os.path.join(screenshot_path, f'{action}_{curTime}.png')
# 截图操作
self._driver.save_screenshot(filepath)
# ----------------------封装页面操作------------------------
# 1、封装-输入-操作
def input_text(self, locator, text, action=None):
'''
:param locator: 元素定位: locator定义为包含-元素定位方法-和-定位方式-的包 ----['id','name']
:param text: 输入文本的内容
:param action: 执行的动作描述,缺省参数
'''
# 1、元素定位
# find_element(定位方式,定位表达式)
element = self._driver.find_element(*locator) # "*"号解包
# 2、很多输入框有默认提示文本,需要先清除
element.clear()
# 3、输入值
element.send_keys(text)
# 2、封装-点击-操作
def click(self, locator, action=None):
# 1、元素定位
# find_element(定位方式,定位表达式)
element = self._driver.find_element(*locator) # "*"号解包
# 2、点击
element.click()
# 3、封装-清空-操作
def clear(self, locator, action=None):
# 1/元素定位
element = self._driver.find_element(*locator)
# 2/清空
element.clear()
# 4、封装 -获取文本内容-操作
def get_text(self, locator, action=None):
return WebDriverWait(
self._driver,
timeout=wait_timeout,
poll_frequency=wait_poll_frequency).until(
EC.visibility_of_element_located(locator)).text
# ----封装显式等待---判断元素是否存在,如果没有获取到元素,使用显示等待判断----日志及截图-------
def element_is_presence(
self,
locator,
action=None,
timeout=5,
poll_frequency=0.5):
'''
:param locator: 定位元素和定位方法和表达式,包('','')
:param action: 动作描述,
:param timeout: 等待时间设置
:param poll_frequency: 等待频率
'''
# 1/设置显式等待时间
try:
WebDriverWait(
self._driver,
timeout=timeout,
poll_frequency=poll_frequency).until(
EC.presence_of_element_located(locator))
except BaseException:
# 1/打印log信息
log.error(action)
# 2/截图
self.get_page_screenshot(action)
# 3/元素不存在返回False
return False
# 如果存在返回True
return True
-----------------------注
----------------------------
-
1、路径配置
上述:screenshot_path
,在公共方法utils
中封装,新建handle_path.py
需要的路径都可以提前封装好:(固定写法
)
import os
# 1、工程路径
# os.path.obspath(__file__) 当前文件路径
# os.path.dirname()------获取上一层路径
project_path = os.path.dirname(os.path.dirname(__file__))
# 2、截图路径
screenshot_path = os.path.join(project_path, r'outFiles\screenshot')
# 3、日志路径
logs_path = os.path.join(project_path, r'outFiles\logs')
# 4、测试数据路径
testcase_path = os.path.join(project_path, 'data')
# 5、测试报告路径
reports_path = os.path.join(project_path, r'outFiles\reports\tmp')
# 6、配置路径
config_path = os.path.join(project_path, 'configs')
if __name__ == '__main__':
# print(project_path)
# print(screenshot_path)
# print(logs_path)
# print(reports_path)
# print(testcase_path)
print(config_path)
-
2、log的封装(
固定方法
)
在utils
包下新建handle_logs.py
基类中封装显示等待的时间需要截图、并抓取log
。
"""
日志相关内容
1、日志的输出渠道:文件xxx.logs 控制台输出
2、日志级别:CRITICAL-ERROR-WARNING-INFO-DEBUG
3、日志内容:2023-03-30 18:42:12, 234 INFO XXXXXXXX
年月日.时分秒.级别-哪行代码出错-具体内容
"""
import logging
import datetime
from utils.handle_path import logs_path
def logger(file_Log=True, name=__name__):
logDir = f'{logs_path}-{datetime.datetime.now().strftime("%Y%m%d%H%M%S")}.txt'
# 1/创建日志收集器对象
logObject = logging.getLogger()
# 2/ 设置日志的级别
logObject.setLevel(logging.DEBUG)
# 3/ 日志内容格式
fmt = '%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'
formater = logging.Formatter(fmt)
if file_Log: # log文件模式
# 设置日志渠道 -文件输出
handle = logging.FileHandler(logDir, encoding='utf-8')
# 日志内容与渠道绑定
handle.setFormatter(formater)
# 把日志对象与渠道绑定
logObject.addHandler(handle)
else:
# 设置日志渠道控制台 控制台输出
handle2 = logging.StreamHandler()
# 日志内容与渠道绑定
handle2.setFormatter(formater)
# 把日志对象与渠道绑定
logObject.addHandler(handle2)
return logObject
log = logger() # 控台输出日志
if __name__ == '__main__':
log = logger() # 控制台输出日志
log.debug('日志信息:')
-
3、
locator
的定义:
selenium4 升级后定位方式变化为:driver.find_element(By.定位方法, '定位方式')
格式
例如:ID定位某个元素:login = driver.find_element(By.ID, 'btnLogin')
locator即是
By.定位方法, '定位方式'
这个包 -
4、封装页面操作:后续需要自行添加函数封装即可
- 显示等待如上封装,
可见元素定位的封装方法如下
:
- 显示等待如上封装,
def element_is_presence(
self,
locator,
action=None,
timeout=5,
poll_frequency=0.5):
'''
def element_is_visibility(
self,
locator,
action=None,
timeout=5,
poll_frequency=0.5):
# 1/设置显式等待可见元素定位时间
try:
ele = WebDriverWait(
self._driver,
timeout=timeout,
poll_frequency=poll_frequency).until(
#判断元素是否可见再执行定位
EC.visibility_of_element_located(locator))
except BaseException:
# 1/打印log信息
log.error(action)
# 2/截图
self.get_page_screenshot(f'{action}-元素定位不到')
# 3/元素不存在直接抛出异常信息
raise
# 如果存在返回元素本身
return ele
而封装的页面操作使用可见元素定位的方法修改为:
# 2、输入操作
def click(self, locator, action=None):
# 1、元素定位
# element_is_visibility(定位方式,定位表达式)。元素是可见的再进行定位
element = self.element_is_visibility(locator)
# 2、点击
element.click()
封装定位元素是可点击的方法
# ----------------------可见元素且是可点击的-----------------
def element_is_clickable(
self,
locator,
action=None,
timeout=5,
poll_frequency=0.5):
# 1/设置显式等待可见元素定位时间
try:
ele = WebDriverWait(
self._driver,
timeout=timeout,
poll_frequency=poll_frequency).until(
# 判断元素是否可点击执行定位
EC.element_to_be_clickable(locator))
except BaseException:
# 1/打印log信息
log.error(action)
# 2/截图
self.get_page_screenshot(f'{action}-元素定位不到')
# 3/元素不可点击直接抛出异常信息
raise
# 如果存在返回元素本身
return ele
相应页面的操作封装方法:
# 2、输入操作
def click(self, locator, action=None):
# 1、元素定位
# element_is_visibility(定位方式,定位表达式)。元素是可见的再进行定位
element = self.element_is_clickable(locator)
# 2、点击
element.click()
-
5、wait_timeout, wait_poll_frequency在config中提前配置号
---------------------------------.----------------------------------------
3、设置yaml定位元素(固定写法)
在config
包下新建一个文件locator.yaml
写定位配置数据
-
【”定位方法“,”定位方式“】
的写法即:.find_element_by_id('username ')
,取id和对应的属性值,八大元素定位法之一。 - 若采用css或者xpath写法:message_text: [‘
css selector
’,‘.el-message--error
’] #登录错误消息文本:css selector
即定位方法,.el-message--error
定位方式。构成了字典格式{xxx:{ss:['v','v'],ss:['v','v'],ss:['v','v']},{[],[]},{[],[]}}
,后续通过字典的键获取值执行操作
LoginPage:
username_input: ['id','username']#登录账户
pwd_input: ['id','pwd']#登录密码
login_btn: ['id','btnLogin']#登录按钮
message_text: ['css selector','.el-message--error'] #登录错误消息文本
OtherPage:
home_button: ['xpath','//*[text()="首页"]'] #首页按钮
logout_button: ['xpath',"//span[text()='退出']"] #退出按钮
personal_button: ['xpath','//img'] #个人中心按钮
4、读取yaml定位数据(固定写法)
在utils
包下新建一个文件,handle_yanl.py
处理获取yaml数据
import yaml
from utils.handle_path import config_path
def get_yaml_data(fileDir):
#方法一
# # 1、打开文件
# fo = open(fileDir, 'r', encoding='utf-8')
# # 2、读取文件
# yaml_data = yaml.load(fo, Loader=yaml.FullLoader)
# return yamldata
#方法二
with open(fileDir, encoding='utf-8') as fo:
return yaml.safe_load(fo.read())
if __name__ == '__main__':
print(get_yaml_data(f'{config_path}\locator.yaml'))
5、写PO页面-继承基类
先写个基础常规的页面:后续还有优化的方法
在pageObject新建操作模块页面:比如:login_page.py
,继承basePage
登录页面:
from utils.handle_path import config_path
from common.basePage import BasePage
from configs.config import url
from time import sleep
from utils.handle_yaml import get_yaml_data
class LoginPage(BasePage): # 继承基类
def to_login_page(self):
self._driver.get(url) # 打开登录页面,url在config包中配置
return self #登录网址返回self本身,方便后续调用
# 1、登录
def login(self, username, pwd):
#读取yaml定位参数locator['LoginPage']提取键值数据
locator = get_yaml_data(f'{config_path}\locator.yaml')['LoginPage']
# 输入账号,locator['username_input']获取定位方法和定位方式,xxx填写容
self.input_text(locator['username_input'], 'username', action='输入账号')
# 输入密码
self.input_text(locator['pwd_input'], 'pwd', action="输入密码")
# 点击登录按钮
self.click(locator['login_btn'], action="点击登录按钮")
sleep(5)
if __name__ == '__main__':
# 登录页面
LoginPage().to_login_page().login('uername','pwd' )
------------------------**注**
---------------------------------
- 1、
url
在configs
中配置后再调用
定位获取优化方法:
locator = get_yaml_data(f’{config_path}\locator.yaml’)[‘LoginPage’]
self.input_text(locator[‘username_input’], ‘username’, action=‘输入账号’)
一个项目上百个定位元素,每次都通过get_yaml_data文件处理数据,然后再通locator
一个获取定位方法和定位方式比较繁琐。
so:设计了巧妙的定位调用方法,脚本中对于定位不可见,但是却省事省力的方法:
在设计locator.yaml文件是,把字典的键设计成语po页面的类同名即可。
在基类中封装一下:
import time
import os.path
from utils.handle_logs import logger
from utils.handle_path import screenshot_path, config_path
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from utils.handle_yaml import get_yaml_data
from common.myDriver import Driver
class BasePage:
def __init__(self): # 调用basepage 需要的浏览器对象,默认谷歌浏览器
self._driver = Driver().get_driver()
# --------------获取所有需要的定位的元素参数,且实现对号入座
# loginpage-----只获取loginPage
# [self.__class__.__name__]获取到对应的类的名字,yaml定位元素时命名与后续页面操作类的名字保持一样
locator = get_yaml_data(
config_path + r'\locator.yaml')[self.__class__.__name__] # 字典
# print(locator)
for k, v in locator.items(): # k自定义键名,v(定位方法,定位表达式)
setattr(self, k, v)
"""
setattr(self, k, v)
1、self是页面的实例
2、使用set attrbute--setattr
3、创建self这个实例的新的属性,将v赋值给她,self.k = v
例子:
class Test:
pass
test = Test() 创建实例
#对实例再创建属性,并将对应属性的值赋给它
setattr(test, 'name', '123')
print(test.name)
结果就是test.name = 123
"""
------------------------**注**
-----------------------------
上述locator的封装方式就是通过[self.__class__.__name__]
获取到对应po页面的本身类的名字
locator = get_yaml_data(
config_path + r'\locator.yaml')[self.__class__.__name__] # 字典格式
例如登录的定位元素打印出来是:
所以对字典处理:setattr方法将键值和self绑定
for k, v in locator.items(): # k自定义键名,v(定位方法,定位表达式)
setattr(self, k, v)
则,self.k = v,则最终可以获取[‘id’, ‘username’],也就是我们需要的,定位方法和定位方式
基类封装中可以直接使用self.自定义键名
find_element(定位方式,定位表达式) ==find_element(self.自定义键名)
则优化后登录页面可以写成:
from pageObjcets.homePage import HomePage
from common.basePage import BasePage
from configs.config import url
from time import sleep
# 登录类,继承基类
class LoginPage(BasePage):
def to_loginPage(self):
self._driver.get(url) # 打开登录页面
return self
# 1、登录
def login(self, username, pwd):
# 输入账号self.username_input:优化后的方法
self.input_text(self.username_input, username, action='输入账号')
# 输入密码
self.input_text(self.pwd_input, pwd, action="输入密码")
# 点击登录按钮"self.login_btn"就是优化后的使用方法
self.click(self.login_btn, action="点击登录按钮")
sleep(2)
if __name__ == '__main__':
# # 登录页面
homepage = LoginPage().to_loginPage().login('xxx', 'xxx')
# 首页展示
# # print('这个类的名字是', LoginPage().__class__.__name__)
这样的话,整个测试框架中看不到定位:只需要配置locator.yaml即可。后续保持键与各个page的类名一样即可。
-------------------------------.----------------------------------
6、测试用例yaml
在data
文件夹下新建一个文件loginData.yaml
登录的的测试用例编写格式:
# 登录成功用例
- ['usernamexxx','pswxxx'] # 账号密码正确
--- #分隔符
# 登录失败用例
- [['xpath',"//*[contains(text(),'该用户不存在!')]"],'该用户不存在!','usernamexx','pswxxx']
- [['xpath',"//*[contains(text(),'输入的密码错误!')]"],'输入的密码错误!','usernamexx','pswxxx']
- [['xpath',"//*[contains(text(),'该用户不存在!')]"],'该用户不存在!','','111111']
- [['css selector','.el-form-item__error'],'The password can not be less than 5 digits','xxxx','']
- [['css selector','.el-form-item__error'],'The password can not be less than 5 digits','','']
用例编写完,需要进一步处理数据成为自己想要的:[(‘username’,‘pwd’),(‘us’,‘pwd’),(‘usme’,‘wd’)]这种格式的,就可以使用pytest.mark.parametrize()
参数化了。
在utile包新建一个handle_yaml.py
文件,因为之前处理定位yaml有了,就在当前文件下封装个读取用例的get_case_yaml函数:因为我们把正向用例和反向用例写在一个yaml文件里,分隔成两个yaml文件数据了,需要单独处理
。
def get_case_yaml(filedir):
caselist = []
# 1、打开文件
fo = open(filedir, 'r', encoding='utf-8')
# 2、读取文件
testcases = yaml.load(fo, Loader=yaml.FullLoader)
for one in testcases:
caselist.append(one)
return caselist
# print(testcases)
test_login.py
提取数据时需要使用角标提取就行了:get_case_yaml(filedir[0])
,提取的就是正确的密码和账号get_case_yaml(filedir[1])
,提取反向用例的数据
7、pytest测试模块+allure测试报告输出
在testCase
包下新建一个文件test_login.py
1、参数化测试用例:@pytest.mark.parametrize('username, pwd', get_yamlData(testcase_path+r'\loginData.yaml'))
2、优化测试报告:
标题level: ---------------------测试步骤:
@allure.epic() -----------------with allure.step(’ ‘):
@allure.feature()--------------
@allure.story()
@allure.title()
测试报告输出:标红的部分
pytest.main([‘test_login.py’, '--alluredir', reports_path, '--clean-alluredir']
)os.system(f'allure serve {reports_path}'
)
3、测试报告版本展示:
在reports/tmp下新建一个文件:environment.properties
文件,也就是在你生成报告的路径下新建这个文件
里面填写你测试软件平台的版本信息,丰富allure测试报告的展示
Browser=Chromium
Browser.Version=21.1.18
Stand=test
python.Version=3.9.2
allure.version=2.13.3
pytest测试代码如下:
import os, pytest, allure
from utils.handle_yaml import get_yamlData
from pageObjcets.loginPage import LoginPage
from utils.handle_path import testcase_path, reports_path
loginData = f"{testcase_path}/login_cases.yaml"
@allure.epic('登录模块test')
@allure.story('登录模块test')
class TestLogin: # 测试类
@pytest.mark.parametrize('username, pwd',
get_case_yaml(loginData)[0])
@allure.title('登录成功case')
def test_login(self, username, pwd):
#第一步:输入账户密码登录
with allure.step('输入账户密码执行登录'):
# 1/登录
homepage = LoginPage().to_loginPage().login(username, pwd)
# 2/登录是否成功,断言四否具有首页元素。assert断言失败后就无法继续执行。
# assert homepage.element_is_presence(homepage.home_button)
# 2/解决方案:assume断言失败后可以继续执行下一步,有时间也需要反向的用例测试失败可以继续下一步执行
# 第2步:验证登录成功
with allure.step('验证登录是否成功'):
pytest.assume(
homepage.element_is_presence(
homepage.home_button,
action='登录后验证首页'))
# 第3步:退出返回到登录页面,方便后续的反向用例执行
with allure.step('退出返回到登录页面'):
# 3/做完登录退回到登录页面
homepage.to_login_page(action='退出登录')
if __name__ == '__main__':
pytest.main(['-sq', 'test_login.py', '--alluredir', reports_path, '--clean-alluredir'])
os.system(f'allure serve {reports_path}')
验证首页元素,需要先在projectPage页新建一个homePage.py文件,因为首页不属于登录页,需要写在首页中:
# 点击回到首页
def homepage(self):
self.click(self.home_button, '点击首页')
homepage.element_is_presence(homepage.home_button,action='登录后验证首页'))
中的home_button
即是首页元素。
反向用例处理:
测试反向用例若使用assert,断言失败就无法继续执行:
引入新的插件:assume
需要安装pytest-assume库,在pycharm终端使用命令安装:
pip3 install pytest-assume
引自:https://blog.csdn.net/weixin_50829653/article/details/113179401
反向用例准备好了,在test_login.py再封装一个函数处理反向测试场景:
1、在locator.yaml准备好相应的登录失败的提示文本的元素定位:
2、编写处理反向用例代码
# 执行反向用例,登录失败的场景
@pytest.mark.parametrize('locator, expected, username, password',
get_case_yaml(loginData)[1])
# @pytest.mark.skip(reason='先不跑')
@allure.title('登录失败用例')
def test_loginerr(self, locator, expected, username, password):
with allure.step('执行登录'):
homepage = LoginPage().login_page()
homepage.login(username, password)
with allure.step('断言登录失败提示'):
pytest.assume(homepage.get_text(locator) == expected)
# homepage.get_screenshot('报错截图')
四个参数分别对应用例中的定位元素(locator),期望结果(expected),账号和密码
通过BasePage的get_text方法获取登录失败提示文本信息,与期望对比,这里使用assert也行,建议使用assume,不然失败了就停止测试了。
通过装饰器mark.skip()选择是否跑反向用例
最后合起来的pytest执行脚本:
import os, pytest, allure
from utils.handle_yaml import get_yamlData
from pageObjcets.loginPage import LoginPage
from utils.handle_path import testcase_path, reports_path
loginData = f"{testcase_path}/login_cases.yaml"
@allure.epic('登录模块test')
@allure.story('登录模块test')
class TestLogin: # 测试类
@pytest.mark.parametrize('username, pwd',
get_case_yaml(loginData)[0])
@allure.title('登录成功case')
def test_login(self, username, pwd):
#第一步:输入账户密码登录
with allure.step(' 1、输入账户密码执行登录'):
# 1/登录
homepage = LoginPage().to_loginPage().login(username, pwd)
# 2/登录是否成功,断言四否具有首页元素。assert断言失败后就无法继续执行。
# assert homepage.element_is_presence(homepage.home_button)
# 2/解决方案:assume断言失败后可以继续执行下一步,有时间也需要反向的用例测试失败可以继续下一步执行
# 第2步:验证登录成功
with allure.step('2 、验证登录是否成功'):
pytest.assume(
homepage.element_is_presence(
homepage.home_button,
action='登录后验证首页'))
# 第3步:退出返回到登录页面,方便后续的反向用例执行
with allure.step(' 3、退出返回到登录页面'):
# 3/做完登录退回到登录页面
homepage.to_login_page(action='退出登录')
# 执行反向用例,登录失败的场景
@pytest.mark.parametrize('locator, expected, username, password',
get_case_yaml(loginData)[1])
# @pytest.mark.skip(reason='先不跑')
@allure.title('登录失败用例')
def test_loginerr(self, locator, expected, username, password):
with allure.step('1、执行登录'):
homepage = LoginPage().login_page()
homepage.login(username, password)
with allure.step('2、断言登录失败提示'):
pytest.assume(homepage.get_text(locator) == expected)
# homepage.get_screenshot('报错截图')
if __name__ == '__main__':
pytest.main(['-sq', 'test_login.py', '--alluredir', reports_path, '--clean-alluredir'])
os.system(f'allure serve {reports_path}')
8、PO模式结合fixture的数据清除与格式化
在testCase
包下新建一个conftest.py
注:命名必须为conftest.py
, pytest的规则命名。只有conftest才能识别
"""
scope指的是fixture的作用域:
4种:
function(函数)<class(类)<module(模块级别)<session(包)
auto
1、手动调用执行
2、自动执行
"""
import pytest
from time import sleep
from common.myDriver import Driver
@pytest.fixture(scope='session', autouse=True)
def start_running():
print("------开始运行UI自动化测试-----")
yield
# 自动化完成之后,关闭浏览器进程
sleep(3) #避免浏览器关闭太快,最后一部操作了但响应时间时浏览器关闭太快导致问题。
Driver().get_driver().quit()
print("-----结束-UI自动化测试-----")
--------------------**注**
--------------------------
1、sleep(3)是为了保证关闭退出浏览器过快而报错。有时候最后一步执行操作未完成就关闭了浏览器。
2、关于数据清除:例如在增加设置或者新增数据时,保证不改变别人的数据,我们只删除自己的数据,一般处理代码放在执行完成测试后:以下非登录模块的数据清除代码。删除代码编写在page模块中调用。
# 1、测试完毕删除测试数据
homepage.to_product_list().delete_first_product()
# 2、回到首页方便后续执行测试
homepage.to_home_page()
放置位置如下:
本地一键执行所有测试模块:
run.bat的创建:
本地项目的路径下创建一个run.bat文件
在文件内使用如下:
cd到测试路径->pytest执行所有文件,->输出allure报告文章来源:https://www.toymoban.com/news/detail-757975.html
cd ./testCase_polly
pytest -sv --alluredir ./outFiles/reports/tmp --clean-alluredir
allure serve ./outFiles/reports/tmp
操作汇总
1:需要主要的是操作导致刷新页面需要重新获取定位元素,否则会失效
2:若操作会导致切换页面,需要加一定强制等待sleep(3),不然很容易导致定位报错或者定位不到
3:conftest 的fixture配置退出浏览器或者结束关闭浏览器需要强制等待sleep(5),不然最后一步的执行可能会报错文章来源地址https://www.toymoban.com/news/detail-757975.html
到了这里,关于selenium UI自动化PO模式测试框架搭建的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!