1、UI自動化規(guī)劃
熟悉業(yè)務(wù)-》確定方案-》選取場景-》了解前端-》定位元素-》編寫腳本-》運(yùn)行優(yōu)化-》回歸報告-》項目匯總
價值意義:
自動化執(zhí)行需要:模塊化
需要可制定化執(zhí)行
可復(fù)用性
PO模式:
將頁面定位和業(yè)務(wù)分開,元素的定位單獨(dú)處理,執(zhí)行腳本單獨(dú)封裝。維護(hù)方便。
封裝BasePage類,在基類中具有webdriver的實(shí)例的屬性,把每個頁面繼承BasePage,通過driver管理每個頁面的元素,將頁面再細(xì)分一個個方法。Testcase依賴page類,進(jìn)行組織化的測試步驟。
維護(hù):
元素變化了:維護(hù)每個page
測試步驟變化:維護(hù)Testcase
每個page的常規(guī)操作封裝基類:
點(diǎn)擊動作、輸入、文本處理、滑動、彈窗……
業(yè)務(wù)分類:
登錄模塊(登錄、注冊新用戶)、
首頁模塊(菜單欄、主頁顯示)、
管理模塊(數(shù)據(jù)列表展示、新增、分類、……)、
用戶模糊(用戶數(shù)據(jù)信息展示)……
功能測試用例依據(jù)以此分類設(shè)計
2、PageObject設(shè)計模式
系統(tǒng)梳理分類
代碼層面分類:
系統(tǒng)System:
其中:pageObjects
包testCase
包common
包configs
包utils
包outFiles
文件夾(logs/reports/screenshoot
)docs
文件夾data
文件夾
PO模式從下往上,從底層開始寫:driver-》BasePage-》pages-》test執(zhí)行-》報告python+selenium+pytest+allure
.
3、PO模式封裝
1、驅(qū)動模塊(固定寫法)
封裝驅(qū)動
寫在:common
包下
新建myDriver.py
文件,建一個兼容瀏覽器的驅(qū)動類,就是dirver
"""
驅(qū)動模塊
1、考慮到瀏覽器的兼容性,可以分為主流的幾個瀏覽器驅(qū)動
1、谷歌
2、火狐
3、其他
"""
from selenium import webdriver
from configs.config import implicitly_wait_time
# 設(shè)置單例模式
'''
類創(chuàng)建實(shí)例:
1、先調(diào)用—__new__()創(chuàng)建方法,一般類中不寫,自動調(diào)用
2、再調(diào)用初始化方法__init__()
判斷一個類是否有實(shí)例,如果有,就不會創(chuàng)建新的實(shí)例
'''
# 哪一個類的實(shí)例需要使用單例模式,就直接繼承這個類(`固定寫法`)
class Single(object):
#new方法創(chuàng)建對象
def __new__(cls, *args, **kwargs):
# 判斷當(dāng)前的類是否已經(jīng)實(shí)例化
if not hassttr(cls, '_instance'):
cls._instance = super().__new__(cls)# super是父類的new方法
return cls._instance
class Driver(Single):
# 新建一個初始值
_driver = None
# 判斷使用瀏覽器,---------默認(rèn)指定谷歌瀏覽器
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}瀏覽器,請使用可用瀏覽器打開")
# 設(shè)置隱式等等時間---設(shè)置`**全局變量`**等等時間5s
self._driver.implicitly_wait(implicitly_wait_time)
# 瀏覽器對大化
self._driver.maximize_window()
return self._driver # 返回瀏覽器對象
if __name__ == '__main__':
Driver().get_driver('chrome')# 創(chuàng)建實(shí)例對象,調(diào)用對應(yīng)的方法,傳什么瀏覽器使用什么瀏覽器
驅(qū)動模塊封裝完畢,后續(xù)只需要調(diào)用Driver().get_driver('chrome')
就可以使用webdriver進(jìn)行元素定位
等同于對driver= webdriver.Chrome()
的封裝,方便兼容不同瀏覽器。
1、隱式等待時間全局變量參數(shù)化
在configs
包下創(chuàng)建config
文件,設(shè)置隱式等待時間參數(shù)5s或者10s,自定義
implicitly_wait_time = 5
2、基類封裝(基本框架固定寫法)
基類
寫在:common
包下,新建一個文件basePage.py
- 創(chuàng)建基類basePage
作用:1、把基本的頁面操作封裝好,給其他頁面繼承使用
包含:2、點(diǎn)擊操作(click
)、輸入框輸入(input_text
)、獲取元素屬性值(get_attrbute
)、元素可見(visibility
)、截圖(save_screenshot
)、清除(clear
),獲取文本內(nèi)容(get_text
)、顯示等待presence_of
(元素可見(visibility_of
),可操作點(diǎn)擊(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): # 調(diào)用basepage 需要的瀏覽器對象,默認(rèn)谷歌瀏覽器
self._driver = Driver().get_driver()
# ------------------------封裝截圖方法---------------------------
# 截圖操作:可以使用瀏覽器對象去調(diào)用,也可以使用元素調(diào)用
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: 輸入文本的內(nèi)容
:param action: 執(zhí)行的動作描述,缺省參數(shù)
'''
# 1、元素定位
# find_element(定位方式,定位表達(dá)式)
element = self._driver.find_element(*locator) # "*"號解包
# 2、很多輸入框有默認(rèn)提示文本,需要先清除
element.clear()
# 3、輸入值
element.send_keys(text)
# 2、封裝-點(diǎn)擊-操作
def click(self, locator, action=None):
# 1、元素定位
# find_element(定位方式,定位表達(dá)式)
element = self._driver.find_element(*locator) # "*"號解包
# 2、點(diǎn)擊
element.click()
# 3、封裝-清空-操作
def clear(self, locator, action=None):
# 1/元素定位
element = self._driver.find_element(*locator)
# 2/清空
element.clear()
# 4、封裝 -獲取文本內(nèi)容-操作
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: 定位元素和定位方法和表達(dá)式,包('','')
:param action: 動作描述,
:param timeout: 等待時間設(shè)置
:param poll_frequency: 等待頻率
'''
# 1/設(shè)置顯式等待時間
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__) 當(dāng)前文件路徑
# 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、測試數(shù)據(jù)路徑
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
。
"""
日志相關(guān)內(nèi)容
1、日志的輸出渠道:文件xxx.logs 控制臺輸出
2、日志級別:CRITICAL-ERROR-WARNING-INFO-DEBUG
3、日志內(nèi)容:2023-03-30 18:42:12, 234 INFO XXXXXXXX
年月日.時分秒.級別-哪行代碼出錯-具體內(nèi)容
"""
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/創(chuàng)建日志收集器對象
logObject = logging.getLogger()
# 2/ 設(shè)置日志的級別
logObject.setLevel(logging.DEBUG)
# 3/ 日志內(nèi)容格式
fmt = '%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'
formater = logging.Formatter(fmt)
if file_Log: # log文件模式
# 設(shè)置日志渠道 -文件輸出
handle = logging.FileHandler(logDir, encoding='utf-8')
# 日志內(nèi)容與渠道綁定
handle.setFormatter(formater)
# 把日志對象與渠道綁定
logObject.addHandler(handle)
else:
# 設(shè)置日志渠道控制臺 控制臺輸出
handle2 = logging.StreamHandler()
# 日志內(nèi)容與渠道綁定
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、封裝頁面操作:后續(xù)需要自行添加函數(shù)封裝即可
- 顯示等待如上封裝,
可見元素定位的封裝方法如下
:
- 顯示等待如上封裝,
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/設(shè)置顯式等待可見元素定位時間
try:
ele = WebDriverWait(
self._driver,
timeout=timeout,
poll_frequency=poll_frequency).until(
#判斷元素是否可見再執(zhí)行定位
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(定位方式,定位表達(dá)式)。元素是可見的再進(jìn)行定位
element = self.element_is_visibility(locator)
# 2、點(diǎn)擊
element.click()
封裝定位元素是可點(diǎn)擊的方法
# ----------------------可見元素且是可點(diǎn)擊的-----------------
def element_is_clickable(
self,
locator,
action=None,
timeout=5,
poll_frequency=0.5):
# 1/設(shè)置顯式等待可見元素定位時間
try:
ele = WebDriverWait(
self._driver,
timeout=timeout,
poll_frequency=poll_frequency).until(
# 判斷元素是否可點(diǎn)擊執(zhí)行定位
EC.element_to_be_clickable(locator))
except BaseException:
# 1/打印log信息
log.error(action)
# 2/截圖
self.get_page_screenshot(f'{action}-元素定位不到')
# 3/元素不可點(diǎn)擊直接拋出異常信息
raise
# 如果存在返回元素本身
return ele
相應(yīng)頁面的操作封裝方法:
# 2、輸入操作
def click(self, locator, action=None):
# 1、元素定位
# element_is_visibility(定位方式,定位表達(dá)式)。元素是可見的再進(jìn)行定位
element = self.element_is_clickable(locator)
# 2、點(diǎn)擊
element.click()
-
5、wait_timeout, wait_poll_frequency在config中提前配置號
---------------------------------.----------------------------------------
3、設(shè)置yaml定位元素(固定寫法)
在config
包下新建一個文件locator.yaml
寫定位配置數(shù)據(jù)
-
【”定位方法“,”定位方式“】
的寫法即:.find_element_by_id('username ')
,取id和對應(yīng)的屬性值,八大元素定位法之一。 - 若采用css或者xpath寫法:message_text: [‘
css selector
’,‘.el-message--error
’] #登錄錯誤消息文本:css selector
即定位方法,.el-message--error
定位方式。構(gòu)成了字典格式{xxx:{ss:['v','v'],ss:['v','v'],ss:['v','v']},{[],[]},{[],[]}}
,后續(xù)通過字典的鍵獲取值執(zhí)行操作
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',"http://span[text()='退出']"] #退出按鈕
personal_button: ['xpath','//img'] #個人中心按鈕
4、讀取yaml定位數(shù)據(jù)(固定寫法)
在utils
包下新建一個文件,handle_yanl.py
處理獲取yaml數(shù)據(jù)
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頁面-繼承基類
先寫個基礎(chǔ)常規(guī)的頁面:后續(xù)還有優(yōu)化的方法
在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 #登錄網(wǎng)址返回self本身,方便后續(xù)調(diào)用
# 1、登錄
def login(self, username, pwd):
#讀取yaml定位參數(shù)locator['LoginPage']提取鍵值數(shù)據(jù)
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="輸入密碼")
# 點(diǎn)擊登錄按鈕
self.click(locator['login_btn'], action="點(diǎn)擊登錄按鈕")
sleep(5)
if __name__ == '__main__':
# 登錄頁面
LoginPage().to_login_page().login('uername','pwd' )
------------------------**注**
---------------------------------
- 1、
url
在configs
中配置后再調(diào)用
定位獲取優(yōu)化方法:
locator = get_yaml_data(f’{config_path}\locator.yaml’)[‘LoginPage’]
self.input_text(locator[‘username_input’], ‘username’, action=‘輸入賬號’)
一個項目上百個定位元素,每次都通過get_yaml_data文件處理數(shù)據(jù),然后再通locator
一個獲取定位方法和定位方式比較繁瑣。
so:設(shè)計了巧妙的定位調(diào)用方法,腳本中對于定位不可見,但是卻省事省力的方法:
在設(shè)計locator.yaml文件是,把字典的鍵設(shè)計成語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): # 調(diào)用basepage 需要的瀏覽器對象,默認(rèn)谷歌瀏覽器
self._driver = Driver().get_driver()
# --------------獲取所有需要的定位的元素參數(shù),且實(shí)現(xiàn)對號入座
# loginpage-----只獲取loginPage
# [self.__class__.__name__]獲取到對應(yīng)的類的名字,yaml定位元素時命名與后續(xù)頁面操作類的名字保持一樣
locator = get_yaml_data(
config_path + r'\locator.yaml')[self.__class__.__name__] # 字典
# print(locator)
for k, v in locator.items(): # k自定義鍵名,v(定位方法,定位表達(dá)式)
setattr(self, k, v)
"""
setattr(self, k, v)
1、self是頁面的實(shí)例
2、使用set attrbute--setattr
3、創(chuàng)建self這個實(shí)例的新的屬性,將v賦值給她,self.k = v
例子:
class Test:
pass
test = Test() 創(chuàng)建實(shí)例
#對實(shí)例再創(chuàng)建屬性,并將對應(yīng)屬性的值賦給它
setattr(test, 'name', '123')
print(test.name)
結(jié)果就是test.name = 123
"""
------------------------**注**
-----------------------------
上述locator的封裝方式就是通過[self.__class__.__name__]
獲取到對應(yīng)po頁面的本身類的名字
locator = get_yaml_data(
config_path + r'\locator.yaml')[self.__class__.__name__] # 字典格式
例如登錄的定位元素打印出來是:
所以對字典處理:setattr方法將鍵值和self綁定
for k, v in locator.items(): # k自定義鍵名,v(定位方法,定位表達(dá)式)
setattr(self, k, v)
則,self.k = v,則最終可以獲取[‘id’, ‘username’],也就是我們需要的,定位方法和定位方式
基類封裝中可以直接使用self.自定義鍵名
find_element(定位方式,定位表達(dá)式) ==find_element(self.自定義鍵名)
則優(yōu)化后登錄頁面可以寫成:
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:優(yōu)化后的方法
self.input_text(self.username_input, username, action='輸入賬號')
# 輸入密碼
self.input_text(self.pwd_input, pwd, action="輸入密碼")
# 點(diǎn)擊登錄按鈕"self.login_btn"就是優(yōu)化后的使用方法
self.click(self.login_btn, action="點(diǎn)擊登錄按鈕")
sleep(2)
if __name__ == '__main__':
# # 登錄頁面
homepage = LoginPage().to_loginPage().login('xxx', 'xxx')
# 首頁展示
# # print('這個類的名字是', LoginPage().__class__.__name__)
這樣的話,整個測試框架中看不到定位:只需要配置locator.yaml即可。后續(xù)保持鍵與各個page的類名一樣即可。
-------------------------------.----------------------------------
6、測試用例yaml
在data
文件夾下新建一個文件loginData.yaml
登錄的的測試用例編寫格式:
# 登錄成功用例
- ['usernamexxx','pswxxx'] # 賬號密碼正確
--- #分隔符
# 登錄失敗用例
- [['xpath',"http://*[contains(text(),'該用戶不存在!')]"],'該用戶不存在!','usernamexx','pswxxx']
- [['xpath',"http://*[contains(text(),'輸入的密碼錯誤!')]"],'輸入的密碼錯誤!','usernamexx','pswxxx']
- [['xpath',"http://*[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','','']
用例編寫完,需要進(jìn)一步處理數(shù)據(jù)成為自己想要的:[(‘username’,‘pwd’),(‘us’,‘pwd’),(‘usme’,‘wd’)]這種格式的,就可以使用pytest.mark.parametrize()
參數(shù)化了。
在utile包新建一個handle_yaml.py
文件,因?yàn)橹疤幚矶ㄎ粂aml有了,就在當(dāng)前文件下封裝個讀取用例的get_case_yaml函數(shù):因?yàn)槲覀儼颜蛴美头聪蛴美龑懺谝粋€yaml文件里,分隔成兩個yaml文件數(shù)據(jù)了,需要單獨(dú)處理
。
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
提取數(shù)據(jù)時需要使用角標(biāo)提取就行了:get_case_yaml(filedir[0])
,提取的就是正確的密碼和賬號get_case_yaml(filedir[1])
,提取反向用例的數(shù)據(jù)
7、pytest測試模塊+allure測試報告輸出
在testCase
包下新建一個文件test_login.py
1、參數(shù)化測試用例:@pytest.mark.parametrize('username, pwd', get_yamlData(testcase_path+r'\loginData.yaml'))
2、優(yōu)化測試報告:
標(biāo)題level: ---------------------測試步驟:
@allure.epic() -----------------with allure.step(’ ‘):
@allure.feature()--------------
@allure.story()
@allure.title()
測試報告輸出:標(biāo)紅的部分
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('輸入賬戶密碼執(zhí)行登錄'):
# 1/登錄
homepage = LoginPage().to_loginPage().login(username, pwd)
# 2/登錄是否成功,斷言四否具有首頁元素。assert斷言失敗后就無法繼續(xù)執(zhí)行。
# assert homepage.element_is_presence(homepage.home_button)
# 2/解決方案:assume斷言失敗后可以繼續(xù)執(zhí)行下一步,有時間也需要反向的用例測試失敗可以繼續(xù)下一步執(zhí)行
# 第2步:驗(yàn)證登錄成功
with allure.step('驗(yàn)證登錄是否成功'):
pytest.assume(
homepage.element_is_presence(
homepage.home_button,
action='登錄后驗(yàn)證首頁'))
# 第3步:退出返回到登錄頁面,方便后續(xù)的反向用例執(zhí)行
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}')
驗(yàn)證首頁元素,需要先在projectPage頁新建一個homePage.py文件,因?yàn)槭醉摬粚儆诘卿涰?,需要寫在首頁中?/p>
# 點(diǎn)擊回到首頁
def homepage(self):
self.click(self.home_button, '點(diǎn)擊首頁')
homepage.element_is_presence(homepage.home_button,action='登錄后驗(yàn)證首頁'))
中的home_button
即是首頁元素。
反向用例處理:
測試反向用例若使用assert,斷言失敗就無法繼續(xù)執(zhí)行:
引入新的插件:assume
需要安裝pytest-assume庫,在pycharm終端使用命令安裝:
pip3 install pytest-assume
引自:https://blog.csdn.net/weixin_50829653/article/details/113179401
反向用例準(zhǔn)備好了,在test_login.py再封裝一個函數(shù)處理反向測試場景:
1、在locator.yaml準(zhǔn)備好相應(yīng)的登錄失敗的提示文本的元素定位:
2、編寫處理反向用例代碼
# 執(zhí)行反向用例,登錄失敗的場景
@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('執(zhí)行登錄'):
homepage = LoginPage().login_page()
homepage.login(username, password)
with allure.step('斷言登錄失敗提示'):
pytest.assume(homepage.get_text(locator) == expected)
# homepage.get_screenshot('報錯截圖')
四個參數(shù)分別對應(yīng)用例中的定位元素(locator),期望結(jié)果(expected),賬號和密碼
通過BasePage的get_text方法獲取登錄失敗提示文本信息,與期望對比,這里使用assert也行,建議使用assume,不然失敗了就停止測試了。
通過裝飾器mark.skip()選擇是否跑反向用例
最后合起來的pytest執(zhí)行腳本:
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、輸入賬戶密碼執(zhí)行登錄'):
# 1/登錄
homepage = LoginPage().to_loginPage().login(username, pwd)
# 2/登錄是否成功,斷言四否具有首頁元素。assert斷言失敗后就無法繼續(xù)執(zhí)行。
# assert homepage.element_is_presence(homepage.home_button)
# 2/解決方案:assume斷言失敗后可以繼續(xù)執(zhí)行下一步,有時間也需要反向的用例測試失敗可以繼續(xù)下一步執(zhí)行
# 第2步:驗(yàn)證登錄成功
with allure.step('2 、驗(yàn)證登錄是否成功'):
pytest.assume(
homepage.element_is_presence(
homepage.home_button,
action='登錄后驗(yàn)證首頁'))
# 第3步:退出返回到登錄頁面,方便后續(xù)的反向用例執(zhí)行
with allure.step(' 3、退出返回到登錄頁面'):
# 3/做完登錄退回到登錄頁面
homepage.to_login_page(action='退出登錄')
# 執(zhí)行反向用例,登錄失敗的場景
@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、執(zhí)行登錄'):
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模式結(jié)合fixture的數(shù)據(jù)清除與格式化
在testCase
包下新建一個conftest.py
注:命名必須為conftest.py
, pytest的規(guī)則命名。只有conftest才能識別
"""
scope指的是fixture的作用域:
4種:
function(函數(shù))<class(類)<module(模塊級別)<session(包)
auto
1、手動調(diào)用執(zhí)行
2、自動執(zhí)行
"""
import pytest
from time import sleep
from common.myDriver import Driver
@pytest.fixture(scope='session', autouse=True)
def start_running():
print("------開始運(yùn)行UI自動化測試-----")
yield
# 自動化完成之后,關(guān)閉瀏覽器進(jìn)程
sleep(3) #避免瀏覽器關(guān)閉太快,最后一部操作了但響應(yīng)時間時瀏覽器關(guān)閉太快導(dǎo)致問題。
Driver().get_driver().quit()
print("-----結(jié)束-UI自動化測試-----")
--------------------**注**
--------------------------
1、sleep(3)是為了保證關(guān)閉退出瀏覽器過快而報錯。有時候最后一步執(zhí)行操作未完成就關(guān)閉了瀏覽器。
2、關(guān)于數(shù)據(jù)清除:例如在增加設(shè)置或者新增數(shù)據(jù)時,保證不改變別人的數(shù)據(jù),我們只刪除自己的數(shù)據(jù),一般處理代碼放在執(zhí)行完成測試后:以下非登錄模塊的數(shù)據(jù)清除代碼。刪除代碼編寫在page模塊中調(diào)用。
# 1、測試完畢刪除測試數(shù)據(jù)
homepage.to_product_list().delete_first_product()
# 2、回到首頁方便后續(xù)執(zhí)行測試
homepage.to_home_page()
放置位置如下:
本地一鍵執(zhí)行所有測試模塊:
run.bat的創(chuàng)建:
本地項目的路徑下創(chuàng)建一個run.bat文件
在文件內(nèi)使用如下:
cd到測試路徑->pytest執(zhí)行所有文件,->輸出allure報告文章來源:http://www.zghlxwxcb.cn/news/detail-757975.html
cd ./testCase_polly
pytest -sv --alluredir ./outFiles/reports/tmp --clean-alluredir
allure serve ./outFiles/reports/tmp
操作匯總
1:需要主要的是操作導(dǎo)致刷新頁面需要重新獲取定位元素,否則會失效
2:若操作會導(dǎo)致切換頁面,需要加一定強(qiáng)制等待sleep(3),不然很容易導(dǎo)致定位報錯或者定位不到
3:conftest 的fixture配置退出瀏覽器或者結(jié)束關(guān)閉瀏覽器需要強(qiáng)制等待sleep(5),不然最后一步的執(zhí)行可能會報錯文章來源地址http://www.zghlxwxcb.cn/news/detail-757975.html
到了這里,關(guān)于selenium UI自動化PO模式測試框架搭建的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!