目录
前言
一个迭代频繁的项目,少不了自动化测试,冒烟与回归全部使用自动化测试来实现,释放我们的人工来测试一些重要的,复杂的工作。节省成本是自动化测试最终目标
Python搭建自动化测试框架是高级测试的标志之一
核心处理工厂是一个骚操作
如果大家都懂了的核心工厂代码,实现了UI自动化框架后,做UI自动化测试时,时间成本比PO模式要低100倍,人力成本可以用初级测试工程师
PO :PageObject
为啥要注释PO呢,有人私下给我留言,意思是我这框架没用PageObject,不行,没写好。
事实上,PO时间和人力成本过于高了。个人追求高效高速低成本的目标,对于这个追求来说,PO它已经跟不上时代了,注定要淘汰。
介如很多朋友问源码,我就将其放在这里,:https://gitee.com/kerici/testUI.git
1、Python库选择
这套框架主要的Python库有 Selenium、unittest、ddt、HTMLTestRunner、win32gui、win32con、openpyxl、configparser、logging、smtplib、os等等
其中Selenium、unittest、ddt、HTMLTestRunner是框架核心模块,Selenium通过html属性得到元素,进而操作元素的方法属性,unittes单元测试模块与ddt数据驱动结合可以实现用例的收集与执行,HTMLTestRunner输出自动化测试报告。
win32gui、win32con操作浏览器一些windows的弹出框,比如上传文件的弹出框
openpyxl库读取excel文件,收集用例
configparser库读取config配置文件,实现常规流程设置可配
logging库输出自动化用例执行日志
smtplib使用email发送测试报告
os获取一些工程里的绝对路径
2、分层设计
目录:
分层设计如目录
所谓分层设计:即是相同属性或类型的代码模块放到同一个目录下,使代码管理,扩展,维护待方便。
basefactory :存放浏览器操作与网页操作的基础代码库与一些浏览器驱动
common:存放执行工厂,收集用例,收集路径,读取配置文件的一代码库
config:存放配置文件
data:存放用例文件
excutetest:存放数据驱动用例代码
library:存放独立三方库,方便自行优化第三方库,引用方便,三方库跟着工程走,不会换环境又要重新下载
result:有三个子目录,log目录,report目录,screenshot目录
3、基础类
浏览器
浏览器操作,有开浏览器,关闭浏览器,切换网页,上传附件等等
个人喜好谷歌浏览器,所以IE与火狐暂没兼容,
1、配置浏览器驱动
首先我们需要下载一个浏览器驱动,下驱动之前先确认下本地浏览器版本
从谷歌浏览器右上角-点击自定义与控制-帮助-关于Google Chrome(G) 找到版本信息,我的是83.0.4103.61
浏览器输入淘宝的谷歌驱动镜像
http://npm.taobao.org/mirrors/chromedriver
找到一个与版本相同或相近的版本驱动
选择与电脑系统对应的驱动下载,一般有linux、mac、win三种。
我的是window系统。所以就下载win32版本,放心64位的系统可以用
下载下来的驱动是个zip,解压出一个chromedriver.exe文件
驱动如何配置,可以参照
https://blog.csdn.net/yinshuilan/article/details/78742728
而我个人喜欢直接丢在当前工程的目录下,放在基础目录 ,方便直接用,随时换。
其实建议按上面链接里的来,换环境方便,我这只是简单粗暴。
2、调试驱动是否有用
在browseroperator.py里写一段代码,
-
from selenium import webdriver
-
driver = webdriver.Chrome()
-
driver.get('https://www.baidu.com')
run,结果是自动打开谷歌进入百度了,安排
3、封装浏览器--初始化浏览器
谷歌驱动能用,万事俱备只欠东风,现我们来实现这个东风
代码引用里有from common.getconf import Config
要先实现一个Config类,因为我们要的基础类里要用到一个配置项,这也是为了以后做大做强的需要一个东西,先说一下,具体见【读取config配置】
在config 目录下有一个配置文件,配置文件里配置了浏览器类型,只是为了以后能兼容三种浏览器,目前配置了chrome
首先我们新建名为BrowserOperator的类,初始化配置类的对象,获取到浏览器类型的配置项,得到驱动的目录。初始化用到了os
所以我们要import os
-
class BrowserOperator(object):
-
-
def __init__(self):
-
self.conf = Config()
-
self.driver_path = os.path.join(BASEFACTORYDIR, 'chromedriver.exe')
-
self.driver_type = str(self.conf.get('base', 'browser_type')).lower()
然后我们在基础类里实现一个方法def open_url(self, **kwargs): 打开浏览器,方法使用了不定长参数 **kwargs接收传参,所以只能传指定参数或整个字典,
-
def open_url(self, **kwargs):
-
"""
-
打开网页
-
:param url:
-
:return: 返回 webdriver
-
"""
-
try:
-
url = kwargs['locator']
-
except KeyError:
-
return False, '没有URL参数'
这一段,kwargs里如果有locator,这是用例用例设计的一个参数,它负责传url与元素定位,后面讲excel读取时会说到的。
可以写成url = kwargs.get('locator'),就不需要try了,但要判断url是不是为None,是None还是要return False, ‘没有参数’,两个返回值是全框架设计的返回形式,用一个布尔值确认我的用例执行的成败,后面返回对象或执行日志。至于我写try,个人代码风格。
-
try:
-
if self.driver_type == 'chrome':
-
#处理chrom弹出的info
-
# chrome_options = webdriver.ChromeOptions()
-
# #option.add_argument('disable-infobars')
-
# chrome_options.add_experimental_option("excludeSwitches", ['enable-automation'])
-
# self.driver = webdriver.Chrome(options=chrome_options, executable_path=self.driver_path)
-
self.driver = webdriver.Chrome(executable_path=self.driver_path)
-
self.driver.maximize_window()
-
self.driver.get(url)
-
elif self.driver_type == 'ie':
-
print('IE 浏览器')
-
else:
-
print('火狐浏览器')
-
except Exception as e:
-
return False, e
-
return True, self.driver
上面一段内容,判断是哪个浏览器,然后指定驱动浏览器打开url。
核心代码
self.dirver = webdriver.Chrome(executable_path=self.driver_path)
此时代码会自动帮我们打开一个浏览器,将浏览的句柄赋值给self.driver。这个句柄是操作网页元素的一个把手,不到浏览器关闭,它不会释放。
然后我们就可以用self.driver.get(url) 打开网站了。
外围包了一个try,就是如果浏览器驱动失败了,我们将返回一个Flase和异常,如果成功了,返回一个True 和浏览器 对象driver,
这里有人会问,driver已经返回了,还把它搞成self.driver 干啥呢,因为这个类后期的关闭浏览器,上传附件等操作要用到这个对象。
哦对了,你们不要问这个把浏览器最大化的方法了,万能固定写法: self.driver.maximize_window()
写完这个,我们来调试一把,在browseroperator.py文件下面写下两代码,然后运行
-
wd = BrowserOperator()
-
wd.open_url(locator='https://www.qq.com')
对的,locator='https://ww.....' 这就是指定传参
run,结果是打开了qq网站,说明代码已经执行成功了
然后我们改一下代码,
-
wd = BrowserOperator()
-
isOK, deiver = wd.open_url(locator='https://www.qq.com')
-
time.sleep(5)
-
deiver.find_elements_by_xpath('//*[@id="sougouTxt"]')[0].send_keys('飞人')
-
deiver.find_elements_by_xpath('//*[@id="searchBtn"]')[0].click()
结果他开始搜索飞人了,说明第一步打开浏览器返回操作对象很成功。
4、关闭浏览器
在类下面实现一个方法def close_browser(self, **kwargs):
-
def close_browser(self, **kwargs):
-
"""
-
关闭浏览器
-
:return:
-
"""
-
time.sleep(1)
-
self.driver.quit()
-
time.sleep(2)
-
return True, '关闭浏览器成功'
里面的的睡眠时间很重要的,不然调试时肉眼看不到最后执行结果与否,浏览器就关掉了。
关键代码self.driver.quit(),这个方法退出浏览器,哈哈哈
-
wd = BrowserOperator()
-
isOK, deiver = wd.open_url(locator='https://www.qq.com')
-
time.sleep(5)
-
deiver.find_elements_by_xpath('//*[@id="sougouTxt"]')[0].send_keys('飞人')
-
deiver.find_elements_by_xpath('//*[@id="searchBtn"]')[0].click()
-
wd.close_browser()
在执行代码后面添加一行wd.close_browser()
run,看着他打开浏览器,搜索飞人,然后关闭浏览器,执行通过。
上传附件,后面写完用例模块时,才能涉及到。网上也有很多上传附件的实现方法。所以就不说了,里面涉及windows的消息机制,很高深,照搬代码即可。
browseroperator.py里所有的代码提供给各位看官,希望多给两个赞,谢谢
-
import os
-
import win32gui
-
import win32con
-
import time
-
from selenium import webdriver
-
from common.getconf import Config
-
from common.getfiledir import BASEFACTORYDIR
-
from pywinauto import application
-
-
-
-
class BrowserOperator(object):
-
-
def __init__(self):
-
self.conf = Config()
-
self.driver_path = os.path.join(BASEFACTORYDIR, 'chromedriver.exe')
-
self.deriver_type = str(self.conf.get('base', 'browser_type')).lower()
-
-
def open_url(self, **kwargs):
-
"""
-
打开网页
-
:param url:
-
:return: 返回 webdriver
-
"""
-
try:
-
url = kwargs['locator']
-
except KeyError:
-
return False, '没有URL参数'
-
try:
-
if self.deriver_type == 'chrome':
-
#处理chrom弹出的info
-
# chrome_options = webdriver.ChromeOptions()
-
# #option.add_argument('disable-infobars')
-
# chrome_options.add_experimental_option("excludeSwitches", ['enable-automation'])
-
# self.driver = webdriver.Chrome(options=chrome_options, executable_path=self.driver_path)
-
self.driver = webdriver.Chrome(executable_path=self.driver_path)
-
self.driver.maximize_window()
-
self.driver.get(url)
-
elif self.deriver_type == 'ie':
-
print('IE 浏览器')
-
else:
-
print('火狐浏览器')
-
except Exception as e:
-
return False, e
-
return True, self.driver
-
-
-
-
-
def close_browser(self, **kwargs):
-
"""
-
关闭浏览器
-
:return:
-
"""
-
time.sleep(1)
-
self.driver.quit()
-
time.sleep(2)
-
return True, '关闭浏览器成功'
-
-
-
def upload_file(self, **kwargs):
-
"""
-
上传文件
-
:param kwargs:
-
:return:
-
"""
-
try:
-
dialog_class = kwargs['type']
-
file_dir = kwargs['locator']
-
button_name = kwargs['index']
-
except KeyError:
-
return True, '没传对话框的标记或没传文件路径,'
-
-
if self.deriver_type == "chrome":
-
title = "打开"
-
elif self.deriver_type == "firefox":
-
title = "文件上传"
-
elif self.deriver_type == "ie":
-
title = "选择要加载的文件"
-
else:
-
title = "" # 这里根据其它不同浏览器类型来修改
-
# 找元素
-
# 一级窗口"#32770","打开"
-
dialog = win32gui.FindWindow(dialog_class, title)
-
if dialog == 0:
-
return False, '传入对话框的class定位器有误'
-
# 向下传递
-
ComboBoxEx32 = win32gui.FindWindowEx(dialog, 0, "ComboBoxEx32", None) # 二级
-
comboBox = win32gui.FindWindowEx(ComboBoxEx32, 0, "ComboBox", None) # 三级
-
# 编辑按钮
-
edit = win32gui.FindWindowEx(comboBox, 0, 'Edit', None) # 四级
-
# 打开按钮
-
-
button = win32gui.FindWindowEx(dialog, 0, 'Button', button_name) # 二级
-
if button == 0:
-
return False, '按钮text属性传值有误'
-
# 输入文件的绝对路径,点击“打开”按钮
-
win32gui.SendMessage(edit, win32con.WM_SETTEXT, None, file_dir) # 发送文件路径
-
time.sleep(1)
-
win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button) # 点击打开按钮
-
return True, '上传文件成功'
页面操作
浏览器的代码先告一段落,接着得实现页面元素的操作类了
1、引用模块,导包
-
import os
-
import time
-
from selenium.common.exceptions import NoSuchElementException, TimeoutException
-
from selenium.webdriver import Chrome
-
from selenium.webdriver.support import expected_conditions as EC
-
from selenium.webdriver.support.ui import WebDriverWait
-
from selenium.webdriver.common.by import By
-
from common.getfiledir import SCREENSHOTDIR
os 用来处理截图保存的路径
time处理等待、隐式、显示等待
NoSuchElementException, TimeoutException 前一个隐式等待的异常,后一个显示等的异常
Chrome用来声明driver是Chrome对象,方便driver联想出来方法
expected_conditions 判断元素的16种方法,显示等待用到
WebDriverWait 显示等待
By 可以By.ID,By.NAME等来用来决定元素定位,显示等待中用
SCREENSHOTDIR 截图路径,从getfiledir模块出来,具体见【获取工程绝对路径】
2、初始化类
创建一个类WebdriverOperator类并初始化,因为他需要浏览器driver对象,所以初始化这个对象为私有属性
-
class WebdriverOperator(object):
-
-
def __init__(self, driver:Chrome):
-
self.driver = driver
这个也不好调试,先过
2、实现截图
先初始化文件路径与文件名称,文件名使用时间戳命名,保存为png
pic_name = str.split(str(time.time()), '.')[0] + str.split(str(time.time()), '.')[1] + '.png' screent_path = os.path.join(SCREENSHOTDIR, pic_name)
这两行就是实现文件路径的代码
self.driver.get_screenshot_as_file(screent_path)
截屏代码,因为我们是截浏览器的屏,所以使用self.driver对象调用截屏方法,传入路径,它便会自动截屏保存在screent_path文件中,最后返回路径,具休代码如下
-
def get_screenshot_as_file(self):
-
"""
-
截屏保存
-
:return:返回路径
-
"""
-
pic_name = str.split(str(time.time()), '.')[0] + str.split(str(time.time()), '.')[1] + '.png'
-
screent_path = os.path.join(SCREENSHOTDIR, pic_name)
-
self.driver.get_screenshot_as_file(screent_path)
-
return screent_path
我们来调试一下这个代码
在basefactory目录下面请建一个test.py文件
-
from basefactory.browseroperator import BrowserOperator
-
from basefactory.webdriveroperator import WebdriverOperator
-
-
bo = BrowserOperator()
-
isOK, deiver = bo.open_url(locator='https://www.qq.com')
-
wb = WebdriverOperator(deiver)
-
result = wb.get_screenshot_as_file() #调用截图方法,返回路径打印
-
print(result)
输入这么一段代码。
run,它会打开浏览器,进入qq网站主页,然后打印截图文件目录
找到对应目录 与文件,截图方法调试成功
3、隐式等待
gotosleep(),这个方法不说了,死等。
隐式等待,等待页面元素加载成功便完成等待任务,只需要在用例初始化浏览器之处调用一次,全局可用,其实他只是一行代码,但为了符合框架统一,也封装一下,代码如下:
-
def web_implicitly_wait(self, **kwargs):
-
"""
-
隐式等待
-
:return:
-
type 存时间
-
"""
-
try:
-
s = kwargs['time']
-
except KeyError:
-
s = 10
-
try:
-
self.driver.implicitly_wait(s)
-
except NoSuchElementException:
-
return False, '隐式等待设置失败'
-
return True, '隐式等待设置成功'
其实只有一行代码 self.driver.implicitly_wait(s) 有用的,里面的s是用例传过来的,调试时,只需要传指定time传一个数字,例:time=5,每次页面刷新,程序将等待页面元素加载5秒,5秒后,不管加载成功与否都执行下一行代码,如果2秒有加载完,那么不必等5秒,直接执行下一行代码
切记,隐式等待只需要初始化浏览器调用一次,后面的代码都会隐式等待。
因为我本地网速太好,打开了两个网络视频播放器去调试网页都没法使网页长时间加载,所以只贴上调试代码,结果自己看吧。
在test.py里更新调试代码
-
from basefactory.browseroperator import BrowserOperator
-
from basefactory.webdriveroperator import WebdriverOperator
-
-
bo = BrowserOperator()
-
isOK, deiver = bo.open_url(locator='https://www.baidu.com')
-
wb = WebdriverOperator(deiver)
-
isOK, result = wb.web_implicitly_wait() #设置隐式等待,打印隐式等待的结果
-
print(result)
-
deiver.find_elements_by_xpath('//*[@id="kw"]')[0].send_keys('飞人')
-
deiver.find_elements_by_xpath('//*[@id="su"]')[0].click()
-
deiver.find_elements_by_xpath('//*[@id="rs"]/div')
4、显示等待
隐式等待在页面切换后,加载成功但没展示出来,disable,hide等场景时,也能等待成功。所以不能满足需求。我们需要更强大的等待元素的方法,显示等待,代码如下:
-
def web_element_wait(self, **kwargs):
-
"""
-
等待元素可见
-
:return:
-
"""
-
try:
-
type = kwargs['type']
-
locator = kwargs['locator']
-
except KeyError:
-
return False, '未传需要等待元素的定位参数'
-
try:
-
s = kwargs['time']
-
if s is None:
-
s = 30
-
except KeyError:
-
s = 30
-
-
try:
-
if type == 'id':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.ID, locator)))
-
elif type == 'name':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.NAME, locator)))
-
elif type == 'class':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.CLASS_NAME, locator)))
-
elif type == 'xpath':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.XPATH, locator)))
-
elif type == 'css':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.CSS_SELECTOR, locator)))
-
else:
-
return False, '不能识别元素类型[' + type + ']'
-
except TimeoutException:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '元素[' + locator + ']等待出现失败,已截图[' + screenshot_path + '].'
-
return True, '元素[' + locator + ']等待出现成功'
这两行
type = kwargs['type']
locator = kwargs['locator']
type是用例设计里的locator定位器的类型,有id,name,xpath等主要定位类型,locator定位参数
所以我们调式时,就传两个参数,一个type='' 一个locator=‘’ ,例type='xpath', locator='//*[@id="kw"]'
s = kwargs['time'] 哦,还有这个,传入时间,如果没传,默认等待30秒
核心代码
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.ID, locator)))
每0.5秒轮寻一次属性id为locator的元素是否可见,可见就跳出等待,返回等等元素出现成功;超过s秒便等待失败,返回元素等待出现失败,截图等信息。
调试一把,在test.py里修改代码如下
-
from basefactory.browseroperator import BrowserOperator
-
from basefactory.webdriveroperator import WebdriverOperator
-
-
bo = BrowserOperator()
-
isOK, deiver = bo.open_url(locator='https://www.baidu.com')
-
wb = WebdriverOperator(deiver)
-
isOK, result = wb.web_element_wait(type='xpath', locator='//*[@id="kw"]', s=0.001)
-
print(result)
设置0.001秒,也能成功,网速太快了,失败的例子等各位来重现了,不想去使用高延迟工具了。
5、输入操作
输入,元素的send_key() 方法,所以在WebdriverOperator类里实现一个element_input方法,代码如下:
-
def element_input(self, **kwargs):
-
"""
-
-
:param kwargs:
-
:return:
-
"""
-
try:
-
type = kwargs['type']
-
locator = kwargs['locator']
-
text = str(kwargs['input'])
-
except KeyError:
-
return False, '缺少传参'
-
try:
-
index = kwargs['index']
-
except KeyError:
-
index = 0
-
try:
-
if type == 'id':
-
elem = self.driver.find_elements_by_id(locator)[index]
-
elif type == 'name':
-
elem = self.driver.find_elements_by_name(locator)[index]
-
elif type == 'class':
-
elem = self.driver.find_elements_by_class_name(locator)[index]
-
elif type == 'xpath':
-
elem = self.driver.find_elements_by_xpath(locator)[index]
-
elif type == 'css':
-
elem = self.driver.find_elements_by_css_selector(locator)[index]
-
else:
-
return False, '不能识别元素类型:[' + type + ']'
-
except Exception as e:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '获取[' + type + ']元素[' + locator + ']失败,已截图[' + screenshot_path + '].'
-
try:
-
elem.send_keys(text)
-
except Exception:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '元素['+ locator +']输入['+ text +']失败,已截图[' + screenshot_path + '].'
-
return True, '元素['+ locator +']输入['+ text +']成功'
代码
-
type = kwargs['type']
-
locator = kwargs['locator']
-
text = str(kwargs['input'])
-
index = kwargs['index']
type是用例设计里的locator定位器的类型,有id,name,xpath等主要类型,locator定位参数,input输入的内容,index是元素的在List里下标,一般页面相同的元素会有很多,我们find元素是得到一个list的结果,需要通过下标来取到自己想到的那个元素。不传默认为第0个。
所以我们调式时,就传四个参数,一个type='' 一个locator=‘’input='' index= ,例type='xpath', locator='//*[@id="kw"]', input='飞人',index=0
这一部分代码
-
try:
-
if type == 'id':
-
elem = self.driver.find_elements_by_id(locator)[index]
-
elif type == 'name':
-
elem = self.driver.find_elements_by_name(locator)[index]
-
elif type == 'class':
-
elem = self.driver.find_elements_by_class_name(locator)[index]
-
elif type == 'xpath':
-
elem = self.driver.find_elements_by_xpath(locator)[index]
-
elif type == 'css':
-
elem = self.driver.find_elements_by_css_selector(locator)[index]
-
else:
-
return False, '不能识别元素类型:[' + type + ']'
-
except Exception as e:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '获取[' + type + ']元素[' + locator + ']失败,已截图[' + screenshot_path + '].'
是用来查找页面元素是否存在的,如果存在,将元素找到存在elem变量,继续执行,如果失败,截图,返回False与失败日志
通过type判断,我们使用哪种元素定位方法,这些是基础,不懂的朋友可百度一下寻找元素定位的方法。
输入input的代码:
-
try:
-
elem.send_keys(text)
-
except Exception:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '元素['+ locator +']输入['+ text +']失败,已截图[' + screenshot_path + '].'
-
return True, '元素['+ locator +']输入['+ text +']成功'
输入成功,返回True与成功日志,失败,截图并返回False与失败日志
然后我们来调试一把,在test.py文件里更新代码如下:
-
bo = BrowserOperator()
-
isOK, deiver = bo.open_url(locator='https://www.baidu.com')
-
wb = WebdriverOperator(deiver)
-
isOK, result = wb.web_element_wait(type='xpath', locator='//*[@id="kw"]', s=0.001)
-
print(result)
-
isOK, result = wb.element_input(type='xpath', locator='//*[@id="kw"]', input='飞人', index=0) #元素输入方法
-
print(result)
run,结果打开百度,在编搜索框输入 ‘飞人’,因为还没做点击,先不click百度一下了
再看打印的运行日志
运行成功。
6、优化输入方法
输入方法中的find页面元素是否存在的代码,很多元素操作都需要的。所以再把它分离出来写一个单独的方法find_element,代码如下:
-
def find_element(self, type, locator, index=None):
-
"""
-
定位元素
-
:param type:
-
:param itor:
-
:param index:
-
:return:
-
"""
-
time.sleep(1)
-
#isinstance(self.driver, selenium.webdriver.Chrome.)
-
if index is None:
-
index = 0
-
type = str.lower(type)
-
try:
-
if type == 'id':
-
elem = self.driver.find_elements_by_id(locator)[index]
-
elif type == 'name':
-
elem = self.driver.find_elements_by_name(locator)[index]
-
elif type == 'class':
-
elem = self.driver.find_elements_by_class_name(locator)[index]
-
elif type == 'xpath':
-
elem = self.driver.find_elements_by_xpath(locator)[index]
-
elif type == 'css':
-
elem = self.driver.find_elements_by_css_selector(locator)[index]
-
else:
-
return False, '不能识别元素类型:[' + type + ']'
-
except Exception as e:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '获取[' + type + ']元素[' + locator + ']失败,已截图[' + screenshot_path + '].'
-
return True, elem
然后我们的输入方法代码修改为:
-
def element_input(self, **kwargs):
-
"""
-
-
:param kwargs:
-
:return:
-
"""
-
try:
-
type = kwargs['type']
-
locator = kwargs['locator']
-
text = str(kwargs['input'])
-
except KeyError:
-
return False, '缺少传参'
-
try:
-
index = kwargs['index']
-
except KeyError:
-
index = 0
-
isOK, result = self.find_element(type, locator, index)
-
if not isOK: # 元素没找到,返回失败结果
-
return isOK, result
-
elem = result
-
try:
-
elem.send_keys(text)
-
except Exception:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '元素['+ locator +']输入['+ text +']失败,已截图[' + screenshot_path + '].'
-
return True, '元素['+ locator +']输入['+ text +']成功'
继续使用用test.py中的代码调试,运行结果与第5点输入操作的运行结果一样,就优化成功了。
7、点击操作
点击操作,元素的click()方法,没有什么好说的,因为他的代码与第5点输入操作一样,只是少了个input参数。所以直接贴代码
-
def element_click(self, **kwargs):
-
"""
-
-
:param kwargs:
-
:return:
-
"""
-
try:
-
type = kwargs['type']
-
locator = kwargs['locator']
-
-
except KeyError:
-
return False, '缺少传参'
-
try:
-
index = kwargs['index']
-
except KeyError:
-
index = 0
-
isOK, result = self.find_element(type, locator, index)
-
if not isOK: #元素没找到,返回失败结果
-
return isOK, result
-
elem = result
-
try:
-
elem.click()
-
except Exception:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '元素['+ locator +']点击失败,已截图[' + screenshot_path + '].'
-
return True, '元素['+ locator +']点击成功'
调试,在test.py里更新代码
-
bo = BrowserOperator()
-
isOK, deiver = bo.open_url(locator='https://www.baidu.com')
-
wb = WebdriverOperator(deiver)
-
isOK, result = wb.web_element_wait(type='xpath', locator='//*[@id="kw"]', s=0.001)
-
print(result)
-
isOK, result = wb.element_input(type='xpath', locator='//*[@id="kw"]', input='飞人', index=0)
-
print(result)
-
isOK, result = wb.element_click(type='xpath', locator='//*[@id="su"]', index=0) #元素点击方法
-
print(result)
run,运行结果,一切正常
好,基本元素操作的代码解说完了,全部代码如下,不想自己写的可以copy。
还缺少执行JS与一些偏门控件的方法,这些给各位去自个儿写。
-
import os
-
import time
-
-
from selenium.common.exceptions import NoSuchElementException, TimeoutException
-
from selenium.webdriver import Chrome
-
from selenium.webdriver.support import expected_conditions as EC
-
from selenium.webdriver.support.ui import WebDriverWait
-
from selenium.webdriver.common.by import By
-
from common.getfiledir import SCREENSHOTDIR
-
-
-
-
-
class WebdriverOperator(object):
-
-
def __init__(self, driver:Chrome):
-
self.driver = driver
-
-
def get_screenshot_as_file(self):
-
"""
-
截屏保存
-
:return:返回路径
-
"""
-
pic_name = str.split(str(time.time()), '.')[0] + str.split(str(time.time()), '.')[1] + '.png'
-
screent_path = os.path.join(SCREENSHOTDIR, pic_name)
-
self.driver.get_screenshot_as_file(screent_path)
-
return screent_path
-
-
def gotosleep(self, **kwargs):
-
time.sleep(3)
-
return True, '等待成功'
-
-
-
def web_implicitly_wait(self, **kwargs):
-
"""
-
隐式等待
-
:return:
-
type 存时间
-
"""
-
try:
-
s = kwargs['time']
-
except KeyError:
-
s = 10
-
try:
-
self.driver.implicitly_wait(s)
-
except NoSuchElementException:
-
return False, '隐式等待 页面元素未加载完成'
-
return True, '隐式等待 元素加载完成'
-
-
-
def web_element_wait(self, **kwargs):
-
"""
-
等待元素可见
-
:return:
-
"""
-
try:
-
type = kwargs['type']
-
locator = kwargs['locator']
-
except KeyError:
-
return False, '未传需要等待元素的定位参数'
-
try:
-
s = kwargs['time']
-
if s is None:
-
s = 30
-
except KeyError:
-
s = 30
-
-
try:
-
if type == 'id':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.ID, locator)))
-
elif type == 'name':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.NAME, locator)))
-
elif type == 'class':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.CLASS_NAME, locator)))
-
elif type == 'xpath':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.XPATH, locator)))
-
elif type == 'css':
-
WebDriverWait(self.driver, s, 0.5).until(EC.visibility_of_element_located((By.CSS_SELECTOR, locator)))
-
else:
-
return False, '不能识别元素类型[' + type + ']'
-
except TimeoutException:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '元素[' + locator + ']等待出现失败,已截图[' + screenshot_path + '].'
-
return True, '元素[' + locator + ']等待出现成功'
-
-
-
def find_element(self, type, locator, index=None):
-
"""
-
定位元素
-
:param type:
-
:param itor:
-
:param index:
-
:return:
-
"""
-
time.sleep(1)
-
#isinstance(self.driver, selenium.webdriver.Chrome.)
-
if index is None:
-
index = 0
-
type = str.lower(type)
-
try:
-
if type == 'id':
-
elem = self.driver.find_elements_by_id(locator)[index]
-
elif type == 'name':
-
elem = self.driver.find_elements_by_name(locator)[index]
-
elif type == 'class':
-
elem = self.driver.find_elements_by_class_name(locator)[index]
-
elif type == 'xpath':
-
elem = self.driver.find_elements_by_xpath(locator)[index]
-
elif type == 'css':
-
elem = self.driver.find_elements_by_css_selector(locator)[index]
-
else:
-
return False, '不能识别元素类型:[' + type + ']'
-
except Exception as e:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '获取[' + type + ']元素[' + locator + ']失败,已截图[' + screenshot_path + '].'
-
return True, elem
-
-
-
def element_click(self, **kwargs):
-
"""
-
-
:param kwargs:
-
:return:
-
"""
-
try:
-
type = kwargs['type']
-
locator = kwargs['locator']
-
-
except KeyError:
-
return False, '缺少传参'
-
try:
-
index = kwargs['index']
-
except KeyError:
-
index = 0
-
isOK, result = self.find_element(type, locator, index)
-
if not isOK: #元素没找到,返回失败结果
-
return isOK, result
-
elem = result
-
try:
-
elem.click()
-
except Exception:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '元素['+ locator +']点击失败,已截图[' + screenshot_path + '].'
-
return True, '元素['+ locator +']点击成功'
-
-
-
def element_input(self, **kwargs):
-
"""
-
-
:param kwargs:
-
:return:
-
"""
-
try:
-
type = kwargs['type']
-
locator = kwargs['locator']
-
text = str(kwargs['input'])
-
except KeyError:
-
return False, '缺少传参'
-
try:
-
index = kwargs['index']
-
except KeyError:
-
index = 0
-
isOK, result = self.find_element(type, locator, index)
-
if not isOK: # 元素没找到,返回失败结果
-
return isOK, result
-
elem = result
-
try:
-
elem.send_keys(text)
-
except Exception:
-
screenshot_path = self.get_screenshot_as_file()
-
return False, '元素['+ locator +']输入['+ text +']失败,已截图[' + screenshot_path + '].'
-
return True, '元素['+ locator +']输入['+ text +']成功'
-
-
-
-
-
4、公共类
获取框架项目目录的绝对路径
先在common目录下新增一个,getfiledir.py的文件
这一段没什么多说的,就是获取本框架各目录的绝对路径
-
import os
-
-
dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-
DATADIR = os.path.join(dir, 'data')
-
-
CONFDIR = os.path.join(dir, 'config')
-
-
BASEFACTORYDIR = os.path.join(dir, 'basefactory')
-
-
RESULTDIR = os.path.join(dir, 'result')
-
-
LOGDIR = os.path.join(RESULTDIR, 'log')
-
-
REPORTDIR = os.path.join(RESULTDIR, 'report')
-
-
SCREENSHOTDIR = os.path.join(RESULTDIR, 'screenshot')
-
-
CASEDIR = os.path.join(dir, 'excutetest')
-
-
#print(SCREENSHOTDIR)
-
-
我们可以在每行代码下面写一个print()语名来打印目录,调试一把,结果如下,所以目录已成功得到
读取excel用例
先在common目录下新增一个getcase.py文件
1、用例设计与用例格式
首先,我们设计测试用例,格式如下:
保存为一个xlsx格式的excel文档,放在data目录下,这个我们的数据目录,也是存放测试用例的目录,数据驱动的数据,也就是这个目录。
读取用例在框架里存放的格式如下,
这个是基础,看得懂就行,写好了代码一万年不要动,似懂非懂利害了,需要去学习巩固一下Python dict,其他语言的json串
[
{ 整个xlsx文件的用例
"a": [ 整个sheet页的用例
{用例
"A": 2
},
{
"A": 3
}
]
},
{
"b": [
{
"A": 2
},
{
"A": 3
}
]
}
]
2、引用导包,初始化case.xlsx目录
import openpyxl
import os
from common.getfiledir import DATADIR #导入data目录
file = os.path.join(DATADIR, 'case.xlsx') #得到case文件的路径
导入了openpyxl,它只能操作xlsx,支持读写,本人只喜欢读,不喜欢写。
os,初始化用例目录有用到。写死用例文件,如果想灵活多变,可以把用例名字变成传参或变成配置
用例目录
3、初始化ReadCase
-
class ReadCase(object):
-
def __init__(self):
-
self.sw = openpyxl.load_workbook(file)
-
print(self.sw)
初始化类,顺便读取一下用例文件
调试
在文件里输入,
xlsx = ReadCase()
run,打印出这么一串人类不认识,那么就成功了,他打印了一个内存对象,保存了整个case文件
4、读取单个sheet页用例
实现一个方法readcase, 代码如下
-
def readcase(self, sh):
-
"""
-
组合sheet页的数据
-
:param sh:
-
:return: list,返回组合数据
-
"""
-
if sh is None:
-
return False, '用例页参数未传'
-
datas = list(sh.rows)
-
if datas == []:
-
return False, '用例[' + sh.title + ']里面是空的'
-
title = [i.value for i in datas[0]]
-
rows = []
-
sh_dict = {}
-
for i in datas[1:]:
-
data = [v.value for v in i]
-
row = dict(zip(title, data))
-
try:
-
if str(row['id'])[0] is not '#':
-
row['sheet'] = sh.title
-
rows.append(row)
-
except KeyError:
-
raise e
-
rows.append(row)
-
sh_dict[sh.title] = rows
-
return True, sh_dict
参数传入一个sheet页对象,
然后判空,
-
if sh is None:
-
return False, '用例页参数未传'
通过列表保存sheet的每一行,判空
-
datas = list(sh.rows)
-
if datas == []:
-
return False, '用例[' + sh.title + ']里面是空的'
得到第一行为title,为啥呢,看excel文件的格式
title = [i.value for i in datas[0]]
用一个循环得到每一行的用例与title结合成一个字典json串,返回
-
for i in datas[1:]:
-
data = [v.value for v in i]
-
row = dict(zip(title, data))
-
try:
-
if str(row['id'])[0] is not '#':
-
row['sheet'] = sh.title
-
rows.append(row)
-
except KeyError:
-
raise e
-
rows.append(row)
-
sh_dict[sh.title] = rows
-
return True, sh_dict
好了,来调试一发,在下面输入代码:
-
xlsx = ReadCase()
-
for sh in xlsx.sw:
-
isOK, result = xlsx.readcase(sh)
-
print(result)
先看我们用例文件
run,运行结果如下
格式化后的结果,完全是我们需要的样子,成功读取用例。
{
'baidu': [{
'id': 1,
'result': None,
'keyword': '打开网页',
'type': 'url',
'locator': 'https://www.baidu.com',
'index': None,
'input': None,
'check': None,
'time': None,
'sheet': 'baidu'
}, {
'id': 4,
'result': None,
'keyword': '等待元素可见',
'type': 'xpath',
'locator': '//*[@id="kjw"]',
'index': None,
'input': None,
'check': None,
'time': 3,
'sheet': 'baidu'
}, {
'id': 2,
'result': None,
'keyword': '输入',
'type': 'xpath',
'locator': '//*[@id="kw"]',
'index': None,
'input': '飞人',
'check': None,
'time': None,
'sheet': 'baidu'
}]
}
5、读取所有用例
因为我们设计的readcase方法,只需要传入一个sheet页,所以实现一个readallcase的方法,遍历所有sheet页,传给readcase读取所有用例,代码如下:
-
def readallcase(self):
-
"""
-
取所有sheet页
-
:return:list,返回sheet页里的数据
-
"""
-
sheet_list = []
-
for sh in self.sw: #遍历sheet,
-
if 'common' != sh.title.split('_')[0] and 'common' != sh.title.split('-')[0] and sh.title[0] is not '#' : #判断是否可用的用例
-
isOK, result = self.readcase(sh) #传给readcase取用例
-
if isOK:
-
sheet_list.append(result) #得到结果放到列表,又给用例套了一层sheet页的框
-
if sheet_list is None:
-
return False, '用例集是空的,请检查用例'
-
return True, sheet_list
这里面有一个判断条件,是公共用例,common开头的用例不读取,注释用例以#号开头的用例,不读取
调试代码
-
xlsx = ReadCase()
-
isOK, result = xlsx.readallcase()
-
print(result)
运行结果,请看比第4点运行的结果多了一个中括号,表示他已经可以读取所有的用例了
6、读取公共用例
实现一个get_common_case方法,方法很简单,查询是否有传参sheetname的sheet页存在,有的话就读取sheet页的用例,返回,代码如下。
-
def get_common_case(self, case_name):
-
"""
-
得到公共用例
-
:param case_name:
-
:return:
-
"""
-
try:
-
sh = self.sw.get_sheet_by_name(case_name)
-
except KeyError:
-
return False, '未找到公共用例[' + case_name + '],请检查用例'
-
except DeprecationWarning:
-
pass
-
return self.readcase(sh)
要结合核心模块的调用
调试代码如下
-
xlsx = ReadCase()
-
isOK, result = xlsx.get_common_case('baidu')
-
print(result)
结果如第5点结果一样
读取用例的全部代码如下:
-
import openpyxl
-
import os
-
from common.getfiledir import DATADIR
-
file = os.path.join(DATADIR, 'case.xlsx')
-
-
class ReadCase(object):
-
def __init__(self):
-
self.sw = openpyxl.load_workbook(file)
-
print(self.sw)
-
-
-
def openxlsx(self, file):
-
"""
-
打开文件
-
:param dir:
-
:return:
-
"""
-
# self.sw = openpyxl.load_workbook(file)
-
-
#[{"a": [{"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}]},{"a": [5, 3, 2]}, {"a": [10, 4, 6]}]
-
def readallcase(self):
-
"""
-
取所有sheet页
-
:return:list,返回sheet页里的数据
-
"""
-
sheet_list = []
-
for sh in self.sw:
-
if 'common' != sh.title.split('_')[0] and 'common' != sh.title.split('-')[0] and sh.title[0] is not '#' :
-
isOK, result = self.readcase(sh)
-
if isOK:
-
sheet_list.append(result)
-
if sheet_list is None:
-
return False, '用例集是空的,请检查用例'
-
return True, sheet_list
-
-
-
def readcase(self, sh):
-
"""
-
组合sheet页的数据
-
:param sh:
-
:return: list,返回组合数据
-
"""
-
if sh is None:
-
print('sheet页为空')
-
datas = list(sh.rows)
-
if datas == []:
-
return False, '用例[' + sh.title + ']里面是空的'
-
title = [i.value for i in datas[0]]
-
rows = []
-
sh_dict = {}
-
for i in datas[1:]:
-
data = [v.value for v in i]
-
row = dict(zip(title, data))
-
try:
-
if str(row['id'])[0] is not '#':
-
row['sheet'] = sh.title
-
rows.append(row)
-
except KeyError:
-
raise e
-
rows.append(row)
-
sh_dict[sh.title] = rows
-
return True, sh_dict
-
-
-
def get_common_case(self, case_name):
-
"""
-
得到公共用例
-
:param case_name:
-
:return:
-
"""
-
try:
-
sh = self.sw.get_sheet_by_name(case_name)
-
except KeyError:
-
return False, '未找到公共用例[' + case_name + '],请检查用例'
-
except DeprecationWarning:
-
pass
-
return self.readcase(sh)
-
-
-
读取config配置
目录:
Config类没有什么好解析的,暂时是为了封装而封装
具体代码如下:
-
import os
-
from configparser import ConfigParser
-
from common.getfiledir import CONFDIR
-
-
-
class Config(ConfigParser):
-
-
def __init__(self):
-
self.conf_name = os.path.join(CONFDIR, 'base.ini')
-
super().__init__()
-
super().read(self.conf_name, encoding='utf-8')
-
-
-
def save_data(self, section, option, value):
-
super().set(section=section, option=option, value=value)
-
super().write(fp=open(self.conf_name, 'w'))
-
配置文件分析一下
目录
内容
-
[base]
-
browser_type = chrome
-
# browser_type = IE
-
# browser_type = firefox
-
-
[LOG]
-
level = INFO
-
-
[email]
-
host = smtp.qq.com
-
port = 465
-
user = xxxxx@qq.com
-
pwd = *********
-
from_addr = xxxxx@qq.com
-
to_addr = xxxxx@qq.com
-
[Function]
-
打开网页 = open_url
-
关闭浏览器 = close_browser
-
对话框上传文件 = upload_file
-
点击 = element_click
-
输入 = element_input
-
截图 = get_screenshot_as_file
-
寻找元素 = find_element
-
隐式等待 = web_implicitly_wait
-
等待元素可见 = web_element_wait
-
等待 = gotosleep
-
input上传文件 = upload_input_file
-
模拟按键上传文件 = upload_endkey_file
-
切换页面 = toggle_page
-
执行JS = execute_js
1、base配置,框架中的基础配置
目前只有浏览器类型
2、LOG配置
配置LOG的输出等级,为INFO,日志模块会讲到
3、email配置
email的一些参数配置,smtp服务器与端口,smtp账密,收发地址等
4、Function配置
两个基础类操作行方法 与 用例参数里的keyword的映射关系,核心处理工厂会用到
核心处理工厂
核心处理工厂为啥核心呢?
各位自动化测试大佬,应该知道PO模型吧,一些常用公共页面,封装到一个类里。之后哪里要用到,哪里就初始化这个类来调用各页面的代码执行。
但每个项目都有这么多页面,每更新一个项目就要写海量代码,这也是UI自动化不如接口自动化的地方,难维护,成本高,
但我这个核心代码,完全不需要用PO模式了,只需要在excel里面写公共用例,即可替代PO模式。
可以说,如果大家都懂了我这段核心代码,实现了UI自动化框架后,做UI自动化时,时间成本就比PO模式要低100倍,人力成本可以用初级测试工程师
上面只是说明核心是重点,但真正核心还是因为他是全框架的总枢纽,后面会细述说来
目录
1、引用模块,导包
-
from common.getconf import Config
-
from common.getcase import ReadCase
-
from basefactory.browseroperator import BrowserOperator
-
from basefactory.webdriveroperator import WebdriverOperator
导入了Config,配置文件读取
导入了ReadCase,来初始化用例
导入了BrowserOperator,WebdriverOperator,初始化这个两个类,执行用例
2、初始化工厂类
-
class Factory(object):
-
-
def __init__(self):
-
self.con = Config()
-
self.con_fun = dict(self.con.items('Function'))
-
"""
-
浏览器操作对象
-
"""
-
self.browser_opr = BrowserOperator()
-
"""
-
网页操作对象
-
"""
-
self.webdriver_opr = None
将配置项Function初始化进来
-
self.con = Config()
-
self.con_fun = dict(self.con.items('Function'))
再初始化一下两个基础类对象,因为WebdriverOperator类要在打开URL后才能初始化,先初始化为None
self.browser_opr = BrowserOperator()
self.webdriver_opr = None
然后我们将初始化webdriver_opr的代码放在一个函数里面,方便后面执行方法里面调用,里面强制转换一下,不然很难联想其对应的方法
-
def init_webdriver_opr(self, driver):
-
self.webdriver_opr = WebdriverOperator(driver)
下面我们先说如何实现代替PO模型的用例初始化
3、初始化执行用例
初始化执行用例 def init_execute_case(self): 里面的内容:
-
def init_execute_case(self):
-
print("----------初始化用例----------")
-
xlsx = ReadCase()
-
isOK, result = xlsx.readallcase()
-
if not isOK:
-
print(result)
-
print("----------结束执行----------")
-
exit()
-
all_cases = result
-
excu_cases = []
-
for cases_dict in all_cases:
-
for key, cases in cases_dict.items():
-
isOK, result = self.init_common_case(cases)
-
if isOK:
-
cases_dict[key] = result
-
else:
-
cases_dict[key] = cases
-
excu_cases.append(cases_dict)
-
print("----------初始化用例完成----------")
-
return excu_cases
一、读取了excel里所有可执行用例
二、调用用初始化公共用例
4、初始化公共用例
#初始化公共用例 def init_common_case(self, cases): 里面的内容:
-
def init_common_case(self, cases):
-
"""
-
:param kwargs:
-
:return:
-
"""
-
cases_len = len(cases)
-
index = 0
-
for case in cases:
-
if case['keyword'] == '调用用例':
-
xlsx = ReadCase()
-
try:
-
case_name = case['locator']
-
except KeyError:
-
return False, '调用用例没提供用例名,请检查用例'
-
isOK, result = xlsx.get_common_case(case_name)
-
if isOK and type([]) == type(result):
-
isOK, result_1 = self.init_common_case(result) #递归检查公共用例里是否存在调用用例
-
elif not isOK:
-
return isOK, result
-
list_rows = result[case_name]
-
cases[index: index+1] = list_rows #将公共用例插入到执行用例中去
-
index += 1
-
if cases_len == index:
-
return False, ''
-
return True, cases
一、判断是否有‘调用用例’命令,有则取公共用例合并成可执行用例
二、递归取公共用例里是否有‘调用用例’命令,有则继续取公共合并成可执行用例
注意:公共用例不能调用自已,递归死循环
调试这两个方法一起
首先人们得在用例里面写上一个公共用例
再在baidu用例里写一行,调用用例
然后你们看看,这是不是就是PO模型,只需要在excel里面写写就行,完全不需要代码了
我们来调试一下,大工厂类里写下调试代码
-
fac = Factory()
-
isOK, result = fac.init_execute_case()
-
print(result)
运行结果如下,我们初始化用例后,会把调用用例里被调用例的用例插入到执行用例中来,全部去执行。
下面来讲执行部分的
5、获取执行方法
获取两个基础WebdriverOperator、BrowserOperator的方法,具体代码如下:
-
def get_base_function(self, function_name):
-
try:
-
function = getattr(self.browser_opr, function_name)
-
except Exception:
-
try:
-
function = getattr(self.webdriver_opr, function_name)
-
except Exception:
-
return False, '未找到注册方法[' + function_name + ']所对应的执行函数,请检查配置文件'
-
return True, function
传入方法名称function_name,通过getattr得到基础类的方法,成功得到方法,返回True,function,没有得到,返回False,日志
6、方法执行
实现一个方法,execute_keyword,统一入口调用两个基础类的操作
先放代码,再讲原理,代码如下:
-
def execute_keyword(self, **kwargs):
-
"""
-
工厂函数,用例执行方法的入口
-
:param kwargs:
-
:return:
-
"""
-
try:
-
keyword = kwargs['keyword']
-
if keyword is None:
-
return False, '没有keyword,请检查用例'
-
except KeyError:
-
return False, '没有keyword,请检查用例'
-
-
_isbrowser = False
-
-
try:
-
function_name = self.con_fun[keyword]
-
except KeyError:
-
return False, '方法Key['+ keyword +']未注册,请检查用例'
-
-
#获取基础类方法
-
isOK, result = self.get_base_function(function_name)
-
if isOK:
-
function = result
-
else:
-
return isOK, result
-
-
#执行基础方法,如打网点页、点击、定位、隐式等待 等
-
isOK, result = function(**kwargs)
-
-
#如果是打开网页,是浏览器初始化,需要将返回值传递给另一个基础类
-
if '打开网页' == keyword and isOK:
-
url = kwargs['locator']
-
self.init_webdriver_opr(result)
-
return isOK, '网页[' + url + ']打开成功'
-
return isOK, result
原理:先得到用例里的keyword,然后获取到两个基础类里方法,再传入**kwargs调用,执行操作
取到keyword关键字,回顾上面用例excel里的有一个keyword的字段,传入进来,先取这个字段
-
try:
-
keyword = kwargs['keyword']
-
if keyword is None:
-
return False, '没有keyword,请检查用例'
-
except KeyError:
-
return False, '没有keyword,请检查用例'
在self.con_fun键值对里取到keyword对应的方法名,具体方法可见配置文件那一节
-
try:
-
function_name = self.con_fun[keyword]
-
except KeyError:
-
return False, '方法Key['+ keyword +']未注册,请检查用例'
通过get_base_function得到基础类的方法
-
isOK, result = self.get_base_function(function_name)
-
if isOK:
-
function = result
-
else:
-
return isOK, result
执行方法,返回结果
-
#执行基础方法,如打网点页、点击、定位、隐式等待 等
-
isOK, result = function(**kwargs)
当然,里面有一个特别的,如果方法是打开浏览器,这时就要初始化一下self.webdriver
-
if '打开网页' == keyword and isOK:
-
url = kwargs['locator']
-
self.init_webdriver_opr(result)
-
return isOK, '网页[' + url + ']打开成功'
-
-
-
最后我们返回执行结果
-
return isOK, result
调试,我们在common里面新增一个test.py文件,在里面输入调试代码
导入Factory,初始化一个工厂对象fac,用fac,调用execute_keyword()方法来执行所有网页操作
-
from common.factory import Factory
-
-
-
fac = Factory()
-
-
isOK, result = fac.execute_keyword(keyword='打开网页', locator='http://www.baidu.com')
-
print(result)
-
isOK, result = fac.execute_keyword(keyword='隐式等待', time='30')
-
print(result)
-
isOK, result = fac.execute_keyword(keyword='输入', type='xpath', locator='//*[@id="kw"]',input='飞人乔丹')
-
print(result)
-
idOK, result = fac.execute_keyword(keyword='点击', type='xpath', locator='//*[@id="su"]')
-
print(result)
run,运行结果:
再联合读取excel用例来一起调试
因为
fac.execute_keyword(keyword='点击', type='xpath', locator='//*[@id="su"]')
这样传参其实就等于传了一个字典,所以我们可以将用例里面字典直接传与给execute_keyword()
首先我们整理下excel里的用例
公共用例 common-bai里面是在一个id=kw的输入框中输入‘路飞’
可执行用例baidu,是打开百度网站,输入飞人,然后调用公共用例,再点击搜索的一个模拟搜索的用例
我们来写一下调试代码 ,在test.py里面修改代码
先初始化用例init_exceute_case()
然后解析用例,
for acases in result: #遍历外层list
for key, cases in acases.items(): #遍历中间的dict
for case in cases: #遍历将case取出
用例的层级关系在下面,用上面三行代码解析出case
[
{ 整个xlsx文件的用例
"a": [ 整个sheet页的用例
{用例
"A": 2
},
{
"A": 3
}
]
},
{
"b": [
{
"A": 2
},
{
"A": 3
}
]
}
]
解析完了
调用execute_keyword(**case)
-
from common.factory import Factory
-
-
-
fac = Factory()
-
-
isOK, result = fac.init_execute_case()
-
-
for acases in result:
-
for key, cases in acases.items():
-
for case in cases:
-
isOK, result = fac.execute_keyword(**case)
-
print(result)
run,运行结果如下
看,在执行用例里面的输入【飞人】成功,然后调用公共用例里面的,执行输入【路飞】成功
最后又回到执行用例里的点击su百度一下成功
核心代码的讲述解束
核心部分全部代码如下:
-
from common.getconf import Config
-
from common.getcase import ReadCase
-
from basefactory.browseroperator import BrowserOperator
-
from basefactory.webdriveroperator import WebdriverOperator
-
-
-
class Factory(object):
-
-
def __init__(self):
-
self.con = Config()
-
self.con_fun = dict(self.con.items('Function'))
-
-
-
"""
-
浏览器操作对象
-
"""
-
self.browser_opr = BrowserOperator()
-
"""
-
网页操作对象
-
"""
-
self.webdriver_opr = None
-
-
-
def init_webdriver_opr(self, driver):
-
self.webdriver_opr = WebdriverOperator(driver)
-
-
-
def get_base_function(self, function_name):
-
try:
-
function = getattr(self.browser_opr, function_name)
-
except Exception:
-
try:
-
function = getattr(self.webdriver_opr, function_name)
-
except Exception:
-
return False, '未找到注册方法[' + function_name + ']所对应的执行函数,请检查配置文件'
-
return True, function
-
-
def execute_keyword(self, **kwargs):
-
"""
-
工厂函数,用例执行方法的入口
-
:param kwargs:
-
:return:
-
"""
-
try:
-
keyword = kwargs['keyword']
-
if keyword is None:
-
return False, '没有keyword,请检查用例'
-
except KeyError:
-
return False, '没有keyword,请检查用例'
-
-
_isbrowser = False
-
-
try:
-
function_name = self.con_fun[keyword]
-
except KeyError:
-
return False, '方法Key['+ keyword +']未注册,请检查用例'
-
-
#获取基础类方法
-
isOK, result = self.get_base_function(function_name)
-
if isOK:
-
function = result
-
else:
-
return isOK, result
-
-
#执行基础方法,如打网点页、点击、定位、隐式等待 等
-
isOK, result = function(**kwargs)
-
-
#如果是打开网页,是浏览器初始化,需要将返回值传递给另一个基础类
-
if '打开网页' == keyword and isOK:
-
url = kwargs['locator']
-
self.init_webdriver_opr(result)
-
return isOK, '网页[' + url + ']打开成功'
-
return isOK, result
-
-
-
def init_common_case(self, cases):
-
"""
-
:param kwargs:
-
:return:
-
"""
-
cases_len = len(cases)
-
index = 0
-
for case in cases:
-
if case['keyword'] == '调用用例':
-
xlsx = ReadCase()
-
try:
-
case_name = case['locator']
-
except KeyError:
-
return False, '调用用例没提供用例名,请检查用例'
-
isOK, result = xlsx.get_common_case(case_name)
-
if isOK and type([]) == type(result):
-
isOK, result_1 = self.init_common_case(result) #递归检查公共用例里是否存在调用用例
-
elif not isOK:
-
return isOK, result
-
list_rows = result[case_name]
-
cases[index: index+1] = list_rows #将公共用例插入到执行用例中去
-
index += 1
-
if cases_len == index:
-
return False, ''
-
return True, cases
-
-
# [{"a": [{"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}, {"A": 2}]},{"a": [5, 3, 2]}, {"a": [10, 4, 6]}]
-
-
-
def init_execute_case(self):
-
print("----------初始化用例----------")
-
xlsx = ReadCase()
-
isOK, result = xlsx.readallcase()
-
if not isOK:
-
print(result)
-
print("----------结束执行----------")
-
exit()
-
all_cases = result
-
excu_cases = []
-
for cases_dict in all_cases:
-
for key, cases in cases_dict.items():
-
isOK, result = self.init_common_case(cases)
-
if isOK:
-
cases_dict[key] = result
-
else:
-
cases_dict[key] = cases
-
excu_cases.append(cases_dict)
-
print("----------初始化用例完成----------")
-
return excu_cases
-
-
-
ddt驱动代码
1、文件目录
执行用例代码目录 excutetest下新增一个test_caserun.py ,先说,这里最好是以test开头定义执行文件
然后library里面放到ddt,数据驱动装饰器
2、导入库
在test_caserun.py里写代码
-
import unittest
-
from common.factory import Factory
-
from common.log import mylog
-
from library.ddt import ddt, data
unittest UT测试库,作自动化测试的都知道他是干啥的
Factory,这个框架的核心类
mylog,下一章实现的日志打印
ddt, data,数据驱动装饰器
2、代码分析
-
@ddt
-
class Test_caserun(unittest.TestCase):
-
fac = Factory()
-
isOK, excu_cases = fac.init_execute_case()
-
-
@data(*excu_cases)
-
def test_run(self, acases):
-
for key, cases in acases.items():
-
mylog.info('\n----------用例【%s】开始----------' % cases[0].get('sheet'))
-
print('\n')
-
for case in cases:
-
isOK, result = self.fac.execute_keyword(**case)
-
if isOK:
-
print(result)
-
mylog.info(result)
-
else:
-
mylog.error(result)
-
raise Exception(result)
-
mylog.info('\n----------用例【%s】结束----------\n' % cases[0].get('sheet')).
定义一个类Test_caserun,用@ddt来装饰一下这个类,不要问这啥原理,一下子说不清楚,按要求来就行
初始化工厂fac = Factory()
得到用例isOK, excu_cases = fac.init_execute_case()
实现一个执行用例的方法,test_run
用@data() 来装饰这个方法,这个装饰器来帮忙遍历excu_cases用例,然后返回给test_run方法的acases,
@data() 的参数可以是元组、列表、字典等格式
data只遍历了第一层,后面的我们代码里面写
-
for key, cases in acases.items():
-
mylog.info('\n----------用例【%s】开始----------' % cases[0].get('sheet'))
-
print('\n')
-
for case in cases:
-
isOK, result = self.fac.execute_keyword(**case)
-
if isOK:
-
print(result)
-
mylog.info(result)
-
else:
-
mylog.error(result)
-
raise Exception(result)
-
mylog.info('\n----------用例【%s】结束----------\n' % cases[0].get('sheet'))
这一段就是执行用例,决断执行结果如
理念就是简练代码,一个用例执行代码,调用一个执行入口,执行任意网页操作,返回成功或失败
isOK is True 执行成功打info日志
isOK is False 执行失败打error日志,抛出异常日志
调试代码看下章
执行并输出报告
1、目录
在工程主目录下添加一个文件test_run.py
https://download.csdn.net/download/weixin_40331132/12521680
下载上面链接里的一个文件放到library目录下,因为我的
网上有很多,但因为是我写的用例格式,所以HTMLTestRunner里的代码我改了一些。暂时不会修改这个文件的朋友,可以先下载我的来学习用
2、导包
在test_run里导入
-
import os
-
import unittest
-
from library.HTMLTestRunnerNew import HTMLTestRunner
-
from common.getfiledir import CASEDIR, REPORTDIR
os来处理路径
unittest来Loadcase的代码文件
HTMLTestRunner来执行用例,输出报告
具体代码如下,这一段网上通用,我也懒懒不细说了直接上
-
class Test_run(object):
-
-
def __init__(self):
-
self.suit = unittest.TestSuite()
-
self.load = unittest.TestLoader()
-
self.suit.addTest(self.load.discover(CASEDIR))
-
self.runner = HTMLTestRunner(
-
stream=open(os.path.join(REPORTDIR, 'report.html'), 'wb'),
-
title='魂尾自动化工厂',
-
description='唯a饭木领',
-
tester='HUNWEI'
-
)
-
-
def excute(self):
-
self.runner.run(self.suit)
-
-
-
-
-
-
if __name__=="__main__":
-
test_run = Test_run()
-
test_run.excute()
init初始化用例,与报告对象
excute执行
添加工程主执行入口main
直接运行调试,还是在之前的excel用例基础上运行
运行结果
输出日志:
报告结果:
找到这个目录
打开这个html
好,整个框架完成了,只剩扩展一些边边角角的了
比如:下面打印日志,与发送邮件的,便不细说了,可以直接看代码学习学习,很简单的。
打印Log
相关代码
-
import logging
-
import os
-
from logging.handlers import TimedRotatingFileHandler
-
from common.getconf import Config
-
from common.getfiledir import LOGDIR
-
-
class Handlogging():
-
-
@staticmethod
-
def emplorlog():
-
conf = Config()
-
# set format
-
formatter = logging.Formatter("%(asctime)s - %(name)s-%(levelname)s %(message)s")
-
-
# create log set getlog level
-
mylog = logging.getLogger('HunWei')
-
mylog.setLevel(conf.get('LOG', 'level'))
-
-
# create outputsteam set level
-
sh = logging.StreamHandler()
-
sh.setLevel(conf.get('LOG', 'level'))
-
sh.setFormatter(formatter)
-
mylog.addHandler(sh)
-
-
#create file set level
-
#fh = logging.FileHandler(os.path.join(LOGDIR,'test.log'), encoding='utf-8')
-
-
log_path = os.path.join(LOGDIR, 'test')
-
# interval 滚动周期,
-
# when="MIDNIGHT", interval=1 表示每天0点为更新点,每天生成一个文件
-
# backupCount 表示日志保存个数
-
fh = TimedRotatingFileHandler(
-
filename=log_path, when="D", backupCount=15, encoding='utf-8'
-
)
-
fh.suffix = "%Y-%m-%d.log"
-
fh.setLevel(conf.get('LOG', 'level'))
-
fh.setFormatter(formatter)
-
mylog.addHandler(fh)
-
return mylog
-
-
-
mylog = Handlogging.emplorlog()
发送邮件
相关代码
-
import smtplib
-
import os
-
from common.getconf import Config
-
from email.mime.text import MIMEText
-
from email.mime.multipart import MIMEMultipart
-
from email.mime.application import MIMEApplication
-
from common.getfiledir import REPORTDIR
-
-
-
-
class Opr_email(object):
-
-
def __init__(self):
-
"""
-
初始化文件路径与相关配置
-
"""
-
self.conf = Config()
-
all_path = []
-
for maindir, subdir, file_list in os.walk(REPORTDIR):
-
pass
-
for filename in file_list:
-
all_path.append(os.path.join(REPORTDIR, filename))
-
self.filename = all_path[0]
-
self.host = self.conf.get('email', 'host')
-
self.port = self.conf.get('email', 'port')
-
self.user = self.conf.get('email', 'user')
-
self.pwd = self.conf.get('email', 'pwd')
-
self.from_addr = self.conf.get('email', 'from_addr')
-
self.to_addr = self.conf.get('email', 'to_addr')
-
-
-
-
def get_email_host_smtp(self):
-
"""
-
连接stmp服务器
-
:return:
-
"""
-
self.smtp = smtplib.SMTP_SSL(host=self.host, port=self.port)
-
self.smtp.login(user=self.user, password=self.pwd)
-
-
-
def made_msg(self):
-
"""
-
构建一封邮件
-
:return:
-
"""
-
self.msg = MIMEMultipart()
-
-
with open(self.filename, 'rb') as f:
-
content = f.read()
-
# 创建文本内容
-
text_msg = MIMEText(content, _subtype='html', _charset='utf8')
-
# 添加到多组件的邮件中
-
self.msg.attach(text_msg)
-
# 创建邮件的附件
-
report_file = MIMEApplication(content)
-
report_file.add_header('Content-Disposition', 'attachment', filename=str.split(self.filename, '\\').pop())
-
-
self.msg.attach(report_file)
-
# 主题
-
self.msg['subject'] = '自动化测试报告'
-
# 发件人
-
self.msg['From'] = self.from_addr
-
# 收件人
-
self.msg['To'] = self.to_addr
-
-
-
def send_email(self):
-
"""
-
发送邮件
-
:return:
-
"""
-
self.get_email_host_smtp()
-
self.made_msg()
-
self.smtp.send_message(self.msg, from_addr=self.from_addr, to_addrs=self.to_addr)
-
转载:https://blog.csdn.net/weixin_40331132/article/details/106482043