看了好多关于Web自动化框架方面的知识,也用过一些Web自动化框架,自己也写了一个简单的框架,记录一下!
先说下自己写框架时候的思路,我们都知道Web自动化是根据元素来对页面进行定位的,但是在系统更新迭代时可能会导致我们之前的脚本不可用,这就需要我们翻出来以前的脚本进行修改;为了解决这个问题,我写的这个框架就是把元素和代码分离,把所有的元素都写到一个Excel表格里,一个表格对应一个功能;我们通过调用表格里面的元素来进行自动化测试,这样的话如果变更的话我们只需要更改对应表格里面的元素就可以了,不需要在代码层面对程序进行更改。先看一下这个框架的目录吧,具体的一些想法我会在讲每个目录时逐渐的写出来。
首先是configm目录,里面主要是一个配置文件,文件的内容如下:
# this is config file,only store browser type and server URL
[browserType]
#browserName = Firefox
browserName = Chrome
#browserName = IE
[testServer]
#URL = https://www.baidu.com
#URL = www.google.com
URL = http://localhost/mgr/login/login.html
[browserPath]
path = D:\selenium\chromedriver.exe
[excelPath]
exPath = D:\robotframe1\automation_test\testcase\case.xls
[scrPath]
scrPath = D:\robotframe1\automation_test\screen
第一项个是浏览器类型,这里不多说,大家都懂;第二项是url,这里写的有点少就一个URL,其实这个地方我扩展了下,就是根据不同的功能吧每个功能的URL都写上,这样我们需要进行不同功能的自动化测试时,可以直接调用这里对应的URL,其实就是我们登录了之后,直接有了session,再打开新网页输入需要进行测试的功能URL,这样的话可以省下很多中间页,直奔主题,lib1里面有一个打开新网页的办法就是为了这种情况准备的;第三项是浏览器驱动的位置,这里也不多说;第四项是excel文件的位置,也就是用例,这个地方就写了一个,其实是根据不同的功能的用例写的,我们后期根据不同功能调用不同的用例;第五项是截图的位置,这个可以分开放可以根据不同的功能建立不同的文件夹,这个主要是在系统报错提示时给出的截图,可以让我们看的更加直观。
第二个目录是frame,这个目录里面是我们的核心代码,主要包括了我们的日志,selenium的一些方法以及我们的用例读取的方法,先说一下lib1,不多说看代码:
from selenium import webdriver
import time
import os
import configparser
from automation_test.frame import logger
logger = logger.Logger('Page').getlog()
class Page():
# 浏览器选择
def __init__(self, driver):
self.driver = driver
# 读取配置文件数据
# @classmethod
def readfile(self, select, option):
config = configparser.ConfigParser()
dir_path = os.getcwd() # 获取项目的相对路径
confilep = dir_path + '\..\config\config.ini'
config.read(confilep)
value = config.get(select, option)
return value
# 选择浏览器
def browser(self):
browserType = self.readfile('browserType', 'browserName')
browserPath = self.readfile('browserPath', 'path')
if browserType == 'Chrome':
self.driver = webdriver.Chrome(browserPath)
elif browserType == 'Firefox':
self.driver = webdriver.firefox(browserPath)
# 选择网址
def geturl(self):
url = self.readfile('testServer', 'URL')
self.driver.get(url)
# 打开新的网址
def getnewurl(self,url):
newurl = self.readfile('testServer',url)
js = "window.open(" + '"' + newurl + '"' + ")"
self.driver.execute_script(js)
# 元素定位方法
def find_ele(self, b, c):
if b == 'xpath':
try:
a = self.driver.find_element_by_xpath(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'css':
try:
a = self.driver.find_element_by_css_selector(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'id':
try:
a = self.driver.find_element_by_id(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'class':
try:
a = self.driver.find_element_by_class_name(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'tag':
try:
a = self.driver.find_element_by_tag_name(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'link':
try:
a = self.driver.find_element_by_link_text(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'name':
try:
a = self.driver.find_element_by_name(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'partial':
try:
a = self.driver.find_element_by_partial_link_text(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'idS':
try:
a = self.driver.find_elements_by_id(c).send_keys(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'cssS':
try:
a = self.driver.find_elements_by_css_selector(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'xpathS':
try:
a = self.driver.find_elements_by_xpath(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
elif b == 'classS':
try:
a = self.driver.find_elements_by_class_name(c)
logger.info('use %s to locate the element' % b)
return a
except:
logger.error('定位错误')
# 输入框输入信息
def sendKeys(self, b, c, value):
ele = self.find_ele(b, c)
ele.clear()
try:
ele.send_keys(value)
logger.info('The input box says %s' % value)
except Exception as e:
logger.error('The box error', e)
# 鼠标点击操作
def click(self, b, c):
try:
self.find_ele(b, c).click()
logger.info('click the button')
except Exception as e:
logger.error('The button is error', e)
# 网页输入框清除
def clear(self, b, c):
self.find_ele(b, c).clear()
# 关闭网页
def quit(self):
try:
self.driver.quit()
logger.info('Close the browser')
except Exception as e:
logger.error('The browser is not found', e)
# 关闭网页
def close(self):
try:
self.driver.close()
logger.info('Close the browser')
except Exception as e:
logger.error('The browser is not found', e)
# 等待时间
def sleep(self, sec):
time.sleep(sec)
# 网页前进
def forword(self):
try:
self.driver.forward()
logger.info('The browser is forword')
except Exception as e:
logger.error('Can not find the next page', e)
# 网页后退
def back(self):
try:
self.driver.back()
logger.info('The browser is back')
except Exception as e:
logger.error('Can not find the last page', e)
# 网页最大化
def max(self):
self.driver.maximize_window()
# 网页刷新
def refresh(self):
self.driver.refresh()
# 获取信息
def curInfor(self, x):
if x == 'url':
return self.driver.current_url
elif x == 'title':
return self.driver.title
elif x == 'text':
return self.driver.title
# 多窗口操作
def handles(self, title):
# 获取当前页面窗口句柄
handle = self.driver.current_window_handle
# 获取所有打开的页面窗口句柄
handles = self.driver.window_handles
for oneHandle in handles:
try:
if title in self.curInfor('title'):
self.driver.switch_to_window(oneHandle)
logger.info('Found this page')
break
except:
logger.error('Can not fount this page with this %s'%title)
# 获取截图
def scr(self):
name = time.strftime('%Y%m%d%H%M%S') + '.png'
path = self.readfile('scrPath','scrPath')
try:
self.driver.get_screenshot_as_file(path + '/' + name)
logger.info('Screenshot of success')
except Exception as e:
logger.error('Screenshots failed')
# 断言
def ass(self, title1):
try:
assert title1 in self.driver.title
logger.info('assert success')
except Exception as e :
logger.error('The page with %s can not fount'%title1,e)
这个lib1里面主要是selenium中的一些方法,以及一些我们在自动化中需要用到的一些方法,是可以持续集成的,我们可以根据我们个性化的需求对这些方法进行增加或者减少。里面有对各个方法的说明,这里不多说。
再看看我们的日志文件:
import logging
import os.path
import time
class Logger():
def __init__(self, logger):
'''
指定保存日志的文件路径,日志级别,以及调用文件
将日志存入到指定的文件中
'''
# 创建一个logger
self.logger = logging.getLogger(logger)
self.logger.setLevel(logging.DEBUG)
# 创建一个handler,用于写入日志文件
rq = time.strftime('%Y%m%d%H%M', time.localtime(time.time()))
# log_path = os.path.dirname(os.getcwd()) + '/Logs/' # 项目根目录下/Logs 保存日志
log_path = os.path.dirname(os.path.abspath('.')) + '/logs/'
print(log_path)
# 如果case组织结构式 /testsuit/featuremodel/xxx.py , 那么得到的相对路径的父路径就是项目根目录
log_name = log_path + rq + '.log'
print(log_name)
fh = logging.FileHandler(log_name, encoding='utf-8')
fh.setLevel(logging.INFO)
# # 再创建一个handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.INFO)
# 定义handler的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 给logger添加handler
self.logger.addHandler(fh)
self.logger.addHandler(ch)
def getlog(self):
return self.logger
接着是我们的excel文件读取的代码:
import xlrd
import xlwt
from xlutils.copy import copy
from automation_test.frame.lib1 import Page
from automation_test.frame import logger
logger = logger.Logger('PHandle_ex').getlog()
class Handle_ex(Page):
def file(self,excelpath,expname):
#通过配置文件查找该文件路径
excelPath = self.readfile(excelpath,expname)
tabopen = xlrd.open_workbook(excelPath)
#读取sheet页名字,获取该sheet页行,列数
table = tabopen.sheet_by_name('Sheet1')
l = table.ncols #获取列数
h = table.nrows #获取行数
m = 4
while m < l:#这个是字母l
print('第%d次运行' % (m - 3))
for i in range(1,h):
rowValue = table.row_values(i)
if rowValue[1] == 'click':
try:
self.click(rowValue[2],rowValue[3])
logger.info('Click the %s'%rowValue[0])
except:
logger.error('点击错误')
elif rowValue[1] == 'sendKeys':
self.sendKeys(rowValue[2], rowValue[3],rowValue[m])
logger.info('%s输入框输入了%s' %(rowValue[0],rowValue[m]))
elif rowValue[1] == 'clear':
self.clear(rowValue[2], rowValue[3])
logger.info('%s输入框清除了原有内容' % (rowValue[0]))
elif rowValue[1] == 'sleep':
try:
self.sleep(rowValue[m])
logger.info('浏览器暂停%ss' % rowValue[m])
except:
logger.error('休息错误')
elif rowValue[1] == 'assert':
try:
self.ass(rowValue[m])
logger.info('%s含有%s'%(rowValue[0],rowValue[m]))
except Exception as e :
logger.error('assert error',e)
break
elif rowValue[1] == 'scr':
self.scr()
logger.info('Screenshot of success ')
elif rowValue[1] == 'refresh':
self.refresh()
logger.info('The web page refresh ')
self.sleep(3)
m = m + 1
上面主要是对excel的表格里的数据进行读取处理,excel的格式时固定的,根据excel的格式写测试用例,核心的代码暂时就这些。
接着是logs文件夹下的东西,这个里面存放的是我们定义的日志文件,是我们自动化测试时所有的日志信息
screen中存放的是我们在测试时需要的截图信息,这个截图信息是我们感觉我们需要截图的时候在用例里面填写的,在哪一步需要截图就在哪一步写上,然后该截图会在此处文件夹保存。
testcase文件夹中存放的是excel表格,也就是我们的用例,根据功能对该表格进行分类
testsuits文件夹中存放的是我们的测试入口,我们把需要进行自动化测试的所有用例的入口都写到这里面,进行执行
有了一点想法,先记下来,怕忘记,testsuits不做为测试入库,而是作为各个测试场景的集合,举例说明:里面一个类,里面面包括所有的测试场景集合,登录写一个函数,新增询价单写一个函数,新增公告写一个函数等等。然后在建立一个文件夹,作为执行函数的入口,在后期修改或者更新内容时,我们根据不同的场景执行不同的函数;比如需要测试一个询价单的功能,我们在入口里面只需要执行登录+新增询价单的函数;这个东西有一个前提也是先要把系统的各个功能点全部写好,还有一点是系统比较稳定,没有太频繁的大版本更新。这个大概是UI自动化的硬伤了吧!
其实还有一个想法,把接口自动化和UI自动化结合起来,UI主要测的是功能,而接口测的是接口,在一些场景里面UI自动化是没有办法进行的,比如说图片验证码,这个时候可以用接口,根据不同的场景选择不同的方法。其实说到底手工仍是必不可少的,接口自动化忽略了对前端的校验;UI自动化对元素的依赖太强,再加上介入的时间太晚,导致UI自动化成了一个鸡肋,除了一些特殊的网站可以使用,使得UI自动化只能是一个加分项,而不能作为一个制胜手段,或者说只能是一个噱头;但是不明真相的人们好像依然感觉他特别高大上,而很多培训机构也拿它当噱头,只能说呵呵。也有可能是我的见识太过短浅,看到的太少,就当我胡说了吧!当前测试中手工测试依然是主流,可以说一个最好的测试依然只能是手工测试,手工测试是站在用户的使用角度,基于需求的规则进行的最全面的测试。