赞
踩
目录
一个迭代频繁的项目,少不了自动化测试,冒烟与回归全部使用自动化测试来实现,释放我们的人工来测试一些重要的,复杂的工作。节省成本是自动化测试最终目标
Python搭建自动化测试框架是高级测试的标志之一
核心处理工厂是一个骚操作
如果大家都懂了的核心工厂代码,实现了UI自动化框架后,做UI自动化测试时,时间成本比PO模式要低100倍,人力成本可以用初级测试工程师
PO :PageObject
为啥要注释PO呢,有人私下给我留言,意思是我这框架没用PageObject,不行,没写好。
事实上,PO时间和人力成本过于高了。个人追求高效高速低成本的目标,对于这个追求来说,PO它已经跟不上时代了,注定要淘汰。
介如很多朋友问源码,我就将其放在这里。
源码: Selenium--Web自动化测试框架-Python文档类资源-CSDN下载
为了大家能自己去敲代码,而不是拿来主义,源码设置了一点点利益限制。
因为是自动化代码,有用360的伙伴们可能会被提示风险,请忽略这个提示,正常下载。
这套框架主要的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获取一些工程里的绝对路径
目录:
分层设计如目录
所谓分层设计:即是相同属性或类型的代码模块放到同一个目录下,使代码管理,扩展,维护待方便。
basefactory :存放浏览器操作与网页操作的基础代码库与一些浏览器驱动
common:存放执行工厂,收集用例,收集路径,读取配置文件的一代码库
config:存放配置文件
data:存放用例文件
excutetest:存放数据驱动用例代码
library:存放独立三方库,方便自行优化第三方库,引用方便,三方库跟着工程走,不会换环境又要重新下载
result:有三个子目录,log目录,report目录,screenshot目录
浏览器操作,有开浏览器,关闭浏览器,切换网页,上传附件等等
个人喜好谷歌浏览器,所以IE与火狐暂没兼容,
1、配置浏览器驱动
首先我们需要下载一个浏览器驱动,下驱动之前先确认下本地浏览器版本
从谷歌浏览器右上角-点击自定义与控制-帮助-关于Google Chrome(G) 找到版本信息,我的是83.0.4103.61
浏览器输入淘宝的谷歌驱动镜像
http://npm.taobao.org/mirrors/chromedriver
找到一个与版本相同或相近的版本驱动
选择与电脑系统对应的驱动下载,一般有linux、mac、win三种。
我的是window系统。所以就下载win32版本,放心64位的系统可以用
下载下来的驱动是个zip,解压出一个chromedriver.exe文件
驱动如何配置,可以参照
python+selenium跑chorme时chromedriver放在哪里_yinshuilan的博客-CSDN博客_webdriver放在哪
而我个人喜欢直接丢在当前工程的目录下,放在基础目录 ,方便直接用,随时换。
其实建议按上面链接里的来,换环境方便,我这只是简单粗暴。
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 +']成功'
-
-
-
-
-
先在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()语名来打印目录,调试一把,结果如下,所以目录已成功得到
先在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类没有什么好解析的,暂时是为了封装而封装
具体代码如下:
- 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
-
-
-
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
HTMLTestRunnerNew.py_htmltestrunnernew-软件测试文档类资源-CSDN下载
下载上面链接里的一个文件放到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
好,整个框架完成了,只剩扩展一些边边角角的了
比如:下面打印日志,与发送邮件的,便不细说了,可以直接看代码学习学习,很简单的。
相关代码
- 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)
-
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。