如何搭建关键字驱动自动化测试框架?
前言
那么这篇文章我们将了解关键字驱动测试又是如何驱动自动化测试完成整个测试过程的。关键字驱动框架是一种功能自动化测试框架,它也被称为表格驱动测试或者基于动作字的测试。关键字驱动的框架的基本工作是将测试用例分成四个不同的部分。首先是测试步骤(Test Step),二是测试步骤中的对象(Test Object),三是测试对象执行的动作(Action),四是测试对象需要的数据(Test Data)。
其实我们做关键字的驱动的思想,就是把编码从测试用例和测试步骤中分离出来,这样对于不会编码的人员更容易理解自动化,从而让手工测试人员也可以编写自动脚本。(这并不意味着不需要自动化测试人员,对于自动化框架的构建,自动化代码的更新,结构调整等都需要一个技术性的人员)对于测试小的项目的团队,可以有两个手工测试人员和一个自动化测试人员。
一、项目功能
我们今天将要实现的功能是测试126邮箱的登录及登录后发送一封带附件的邮件
测试地址
https://mail.126.com/
二、项目目录
接下来我们来看看我们的项目目录是如何设计的,每个目录的功能是用来做什么的?
三、框架搭建
接下来我们一步一步来考虑如何搭建整个项目及每个py代码文件如何编写?
3.1框架主要功能模块
1.新建util文件夹,并在此文件夹下新建ObjectMap.py文件,主要实现页面元素查找功能的封装
1 from selenium.webdriver.support.wait import WebDriverWait2 3 4 def getElement(driver, by, locator):5 '''6 查找单一元素7 :param driver:8 :param by:9 :param locator:
10 :return: 元素对象
11 '''
12 try:
13 element = WebDriverWait(driver, 30).until(lambda x : x.find_element(by, locator))
14 except Exception as e:
15 raise e
16 else:
17 return element
18
19 def getElements(driver, by, locator):
20 '''
21 获取一组元素
22 :param driver:
23 :param by:
24 :param locator:
25 :return: 一组元素对象
26 '''
27 try:
28 elements = WebDriverWait(driver, 30).until(lambda x : x.find_element(by, locator))
29 except Exception as e:
30 raise e
31 else:
32 return elements
33
34
35 if __name__=="__main__":
36 from selenium import webdriver
37 import time
38
39 driver = webdriver.Firefox()
40 driver.get('https://mail.126.com')
41 time.sleep(5)
42 driver.switch_to.frame(getElement(driver, 'xpath', "//div[@id='loginDiv']/iframe"))
43 username = getElement(driver, 'xpath', "//input[@name='email']")
44 username.send_keys('linuxxiaochao')
45 driver.switch_to.default_content()
46 driver.quit()
2.util文件夹下新建WaitUntil.py文件,主要实现显示等待元素功能的封装
1 from selenium.webdriver.common.by import By2 from selenium.webdriver.support.wait import WebDriverWait3 from selenium.webdriver.support import expected_conditions as EC4 5 class WaitUnit(object):6 def __init__(self, driver):7 self.byDic = {8 'id': By.ID,9 'name': By.NAME,
10 'class_name': By.CLASS_NAME,
11 'xpath': By.XPATH,
12 'link_text': By.LINK_TEXT
13 }
14 self.driver = driver
15 self.wait = WebDriverWait(self.driver, 50)
16
17 def presenceOfElementLocated(self, by, locator):
18 '''
19 显示等待某个元素出现在dom中,不一定可见,存在返回元素对象
20 :param by:
21 :param locator:
22 :return:
23 '''
24 try:
25 if by.lower() in self.byDic:
26 self.wait.until(EC.presence_of_element_located((self.byDic[by.lower()], locator)))
27 else:
28 raise TypeError('未找到定位方式,请确保定位方式正确')
29 except Exception as e:
30 raise e
31
32 def frameToBeAvailableAndSwtichToIt(self, by, locator):
33 '''
34 检查frame是否存在,存在就切换到frame中
35 :param by:
36 :param locator:
37 :return:
38 '''
39 try:
40 if by.lower() in self.byDic:
41 self.wait.until(EC.frame_to_be_available_and_switch_to_it((self.byDic[by.lower()], locator)))
42 else:
43 raise TypeError('未找到定位方式,请确保定位方式正确')
44 except Exception as e:
45 raise e
46 def visibiltyOfElementLocated(self, by, locator):
47 '''
48 显示等待页面元素出现在dom中, 并且可见, 存在则返回该元素对象
49 :param by:
50 :param locator:
51 :return:
52 '''
53 try:
54 if by.lower() in self.byDic:
55 self.wait.until(EC.visibility_of_element_located((self.byDic[by.lower()], locator)))
56 else:
57 raise TypeError('未找到定位方式,请确保定位方式正确')
58 except Exception as e:
59 raise e
60
61 if __name__=='__main__':
62 from selenium import webdriver
63 from util.ObjectMap import *
64 driver = webdriver.Firefox()
65 driver.get('https://mail.126.com')
66
67 wait = WaitUnit(driver)
68 wait.frameToBeAvailableAndSwtichToIt('xpath', "//div[@id='loginDiv']/iframe")
69 wait.visibiltyOfElementLocated('xpath', "//input[@name='email']")
70 uname = getElement(driver, 'xpath', "//input[@name='email']")
71 uname.send_keys('python')
72 driver.quit()
3.新建ClipboardUtil.py文件,用来实现剪切版的操作(我们发送邮件时,需要添加附件,通过这个功能来实现上传附件)
1 import win32clipboard as w2 import win32con3 4 class Clipboard(object):5 6 @staticmethod7 def getText():8 '''9 获取剪切板的内容
10 :return:
11 '''
12
13 try:
14 # 打开剪切板
15 w.OpenClipboard()
16 # 读取数据
17 value = w.GetClipboardData(win32con.CF_TEXT)
18 # 关闭剪切板
19 w.CloseClipboard()
20 except Exception as e:
21 raise e
22 else:
23 return value
24
25 @staticmethod
26 def setText(value):
27 '''
28 设置剪切板内容
29 :return:
30 '''
31 try:
32 w.OpenClipboard()# 打开剪切板
33 w.EmptyClipboard()# 清空剪切板
34 w.SetClipboardData(win32con.CF_UNICODETEXT, value) # 设置内容
35 w.CloseClipboard() # 关闭
36 except Exception as e:
37 raise e
38
39 if __name__=='__main__':
40 from selenium import webdriver
41
42 value = 'python'
43 driver = webdriver.Firefox()
44 driver.get('http://www.baidu.com')
45 query = driver.find_element_by_id('kw')
46 Clipboard.setText(value)
47 clValue = Clipboard.getText()
48 query.send_keys(clValue.decode('utf-8'))
4.新建KeyBoardUtil.py文件,主要实现模拟键盘的操作(配合上面剪切板的功能实现,粘贴附件的路径,回车等)
1 import win32api2 import win32con3 4 class KeyBoardKeys(object):5 '''6 模拟键盘7 '''8 # 键盘编码9 vk_code ={
10 'enter':0x0D,
11 'tab' : 0x09,
12 'ctrl':0x11,
13 'v':0x56
14 }
15 @staticmethod
16 def keyDown(keyName):
17 '''
18 模拟按下键
19 :param keyName:
20 :return:
21 '''
22 try:
23 win32api.keybd_event(KeyBoardKeys.vk_code[keyName],0,0,0)
24 except Exception as e:
25 raise e
26 @staticmethod
27 def keyUp(keyName):
28 '''
29 释放键
30 :param keyName:
31 :return:
32 '''
33 try:
34 win32api.keybd_event(KeyBoardKeys.vk_code[keyName],0,win32con.KEYEVENTF_KEYUP,0)
35 except Exception as e:
36 raise e
37 @staticmethod
38 def oneKey(key):
39 '''
40 模拟当个按键
41 :param key:
42 :return:
43 '''
44 try:
45 KeyBoardKeys.keyDown(key)
46 KeyBoardKeys.keyUp(key)
47 except Exception as e:
48 raise e
49
50 @staticmethod
51 def twoKeys(key1, key2):
52 '''
53 模拟组合按键
54 :param key1:
55 :param key2:
56 :return:
57 '''
58 try:
59 KeyBoardKeys.keyDown(key1)
60 KeyBoardKeys.keyDown(key2)
61 KeyBoardKeys.keyUp(key1)
62 KeyBoardKeys.keyUp(key2)
63 except Exception as e:
64 raise e
65
66 if __name__=='__main__':
67 from selenium import webdriver
68
69 driver = webdriver.Firefox()
70 driver.get('http://www.baidu.com')
71 driver.find_element_by_id('kw').send_keys('python')
72 KeyBoardKeys.oneKey('enter')
5.新建DirAndTime.py文件,主要实现获取当前时间,生成特殊路径,这里主要用来生成屏幕截图保存的路径及图片名称
1 from datetime import datetime, date2 from config.VarConfig import *3 4 class DirAndTime(object):5 @staticmethod6 def getCurrentDate():7 '''8 获取当前日期9 :return:
10 '''
11 try:
12 currentDate = date.today()
13 except Exception as e:
14 raise e
15 else:
16 return str(currentDate)
17 @staticmethod
18 def getCurrentTime():
19 '''
20 获取当前时间
21 :return:
22 '''
23 try:
24 Time = datetime.now()
25 currentTime = Time.strftime('%H_%M_%S')
26 except Exception as e:
27 raise e
28 else:
29 return currentTime
30 @staticmethod
31 def CreatePicturePath():
32 '''
33 创建图片存放路径路径
34 :return:
35 '''
36 try:
37
38 picturePath = os.path.join(exceptionPath , DirAndTime.getCurrentDate())
39 if not os.path.exists(picturePath):
40 os.makedirs(picturePath) # 生成多级目录
41 except Exception as e:
42 raise e
43 else:
44 return picturePath
45
46 if __name__=='__main__':
47 print(DirAndTime.getCurrentDate())
48 print(DirAndTime.getCurrentTime())
49 print(DirAndTime.CreatePicturePath())
6.新建ParseExcel.py用来解析excel文件
1 from openpyxl import load_workbook2 from config.VarConfig import *3 from datetime import datetime, date4 5 class ParseExcel(object):6 '''7 解析excel文件的封装8 '''9 def __init__(self):
10 # 加载excel文件到内存
11 self.wb = load_workbook(excelPath)
12
13 def getRowValue(self, sheetName, rawNo):
14 '''
15 获取某一行的数据
16 :param sheetName:
17 :param rawNo:
18 :return: 列表
19 '''
20 sh = self.wb[sheetName]
21 rowValueList = []
22 for y in range(2, sh.max_column+1):
23 value = sh.cell(rawNo,y).value
24 rowValueList.append(value)
25 return rowValueList
26 def getColumnValue(self, sheetName, colNo):
27 '''
28 获取某一列的数据
29 :param sheetName:
30 :param colNo:
31 :return: 列表
32 '''
33 sh = self.wb[sheetName]
34 colValueList = []
35 for x in range(2, sh.max_row +1):
36 value = sh.cell(x, colNo).value
37 colValueList.append(value)
38 return colValueList
39
40 def getCellOfValue(self, sheetName, rowNo, colNo):
41 '''
42 获取某一个单元格的数据
43 :param sheetName:
44 :param rowNo:
45 :param colNo:
46 :return: 字符串
47 '''
48 sh = self.wb[sheetName]
49 value = sh.cell(rowNo, colNo).value
50 return value
51 def writeCell(self, sheetName, rowNo, colNo, value):
52 '''
53 向某个单元格写入数据
54 :param rowNo: 行号
55 :param colNo: 列号
56 :param value:
57 :return: 无
58 '''
59 sh = self.wb[sheetName]
60 sh.cell(rowNo, colNo).value = value
61 self.wb.save(excelPath)
62 def writeCurrentTime(self, sheetName, rowNo, colNo):
63 '''
64 向某个单元格写入当前时间
65 :return:
66 '''
67 sh = self.wb[sheetName]
68 Time = datetime.now()
69 currentTime = Time.strftime('%Y:%m:%d %H:%M:%S')
70 sh.cell(rowNo, colNo).value = currentTime
71 self.wb.save(excelPath)
72
73 def writeTestResult(self, sheetName, rowNo, result, errorInfo = None, errorPic = None):
74 ParseExcel().writeCurrentTime(sheetName, rowNo, testStep_testRunTime)
75 ParseExcel().writeCell(sheetName, rowNo, testStep_testResult, result)
76 if errorInfo and errorInfo:
77 ParseExcel().writeCell(sheetName, rowNo, testStep_testErrorInfo, errorInfo)
78 ParseExcel().writeCell(sheetName, rowNo, testStep_testErrorPic, errorPic)
79 else:
80 ParseExcel().writeCell(sheetName, rowNo, testStep_testErrorInfo, '')
81 ParseExcel().writeCell(sheetName, rowNo, testStep_testErrorPic, '')
82 if __name__=='__main__':
83 p = ParseExcel()
84 print(p.getRowValue('126account',2))
85 print(p.getColumnValue('126account',3))
86 print(p.getCellOfValue('126account', 2, 3))
7.新建Log.py文件,用来记录代码运行日志
1 import logging2 import time3 from config.VarConfig import *4 5 class Logger(object):6 '''7 封装的日志模块8 '''9 def __init__(self, logger, CmdLevel=logging.INFO, FileLevel=logging.INFO):
10 """
11
12 :param logger:
13 :param CmdLevel:
14 :param FileLevel:
15 """
16 try:
17 self.logger = logging.getLogger(logger)
18 self.logger.setLevel(logging.DEBUG) # 设置日志输出的默认级别
19 # 日志输出格式
20 fmt = logging.Formatter('%(asctime)s - %(filename)s:[%(lineno)s] - [%(levelname)s] - %(message)s')
21 # 日志文件名称
22 # self.LogFileName = os.path.join(conf.log_path, "{0}.log.txt".format(time.strftime("%Y-%m-%d")))# %H_%M_%S
23 currTime = time.strftime("%Y-%m-%d")
24 self.LogFileName = logPath+currTime+'.txt'
25 # 设置控制台输出
26 # sh = logging.StreamHandler()
27 # sh.setFormatter(fmt)
28 # sh.setLevel(CmdLevel)# 日志级别
29
30 # 设置文件输出
31 fh = logging.FileHandler(self.LogFileName)
32 fh.setFormatter(fmt)
33 fh.setLevel(FileLevel)# 日志级别
34
35 # self.logger.addHandler(sh)
36 self.logger.addHandler(fh)
37 except Exception as e:
38 raise e
39
40 if __name__ == '__main__':
41 logger = Logger("fox",CmdLevel=logging.DEBUG, FileLevel=logging.DEBUG)
42 logger.logger.debug("debug")
43 logger.logger.log(logging.ERROR,'%(module)s %(info)s',{'module':'log日志','info':'error'}) #ERROR,log日志 error
3.2业务操作功能模块
新建action文件夹,主要存储页面的各种操作,如点击操作,输入操作等
1.文件夹下新建PageAction.py文件
1 from util.ObjectMap import *2 from util.ClipboardUtil import Clipboard3 from util.KeyBoardUtil import KeyBoardKeys4 from util.WaitUntil import WaitUnit5 from util.DirAndTime import *6 from selenium import webdriver7 8 driver = None9 waitUtil = None10 # 打开浏览器11 def openBrowser(browser):12 global driver, waitUtil13 try:14 if browser.lower() =='ie':15 driver = webdriver.Ie(executable_path=iePath)16 elif browser.lower() == 'chrome':17 driver = webdriver.Chrome(executable_path=chromePath)18 else:19 # driver = webdriver.Firefox(executable_path=fireFox)20 driver = webdriver.Firefox()21 except Exception as e:22 raise e23 else:24 waitUtil = WaitUnit(driver) # driver 创建之后, 创建等待类实例对象25 26 # 浏览器窗口最大化27 def maximize_browser():28 try:29 driver.maximize_window()30 except Exception as e:31 raise e32 # 加载网址33 def loadUrl(url):34 try:35 driver.get(url)36 except Exception as e:37 raise e38 39 # 强制等待40 def sleep(sleepSeconds):41 try:42 import time43 time.sleep(sleepSeconds)44 except Exception as e:45 raise e46 # 清除输入框的内容47 def clear(by, locator):48 try:49 getElement(driver, by, locator).clear()50 except Exception as e:51 raise e52 # 输入框中输入内容53 def inputValue(by, locator, value):54 try:55 element = getElement(driver, by, locator)56 # element.click()57 element.send_keys(value)58 except Exception as e:59 raise e60 # 点击操作61 def clickBtn(by, locator):62 try:63 getElement(driver, by, locator).click()64 except Exception as e:65 raise e66 # 断言页面的title67 def assertTitle(titleStr):68 try:69 assert titleStr in driver.title, "%s not found in title!" % titleStr70 except AssertionError as e:71 raise AssertionError(e)72 except Exception as e:73 raise e74 75 # 断言目标字符串是否包含在页面源码中76 def assert_string_in_page_source(assertString):77 try:78 assert assertString in driver.page_source, "%s not found in page source!" % assertString79 except AssertionError as e:80 raise AssertionError(e)81 except Exception as e:82 raise e83 84 # 获取当前页面的title85 def getTitle():86 try:87 return driver.title88 except Exception as e:89 raise e90 91 # 获取页面源码92 def getPageSource():93 try:94 return driver.page_source95 except Exception as e:96 raise e97 # 切换到frame里面98 def switchToFrame(by, locator):99 try:
100 driver.switch_to.frame(getElement(driver, by, locator))
101 except Exception as e:
102 raise e
103
104 # 跳到默认的frame
105 def switchToDefault():
106 try:
107 driver.switch_to.default_content()
108 except Exception as e:
109 raise e
110
111 # 模拟ctrl+v键
112 def ctrlV(value):
113 try:
114 Clipboard.setText(value)
115 sleep(2)
116 KeyBoardKeys.twoKeys('ctrl', 'v')
117 except Exception as e:
118 raise e
119
120 # 模拟tab键
121 def tabKey():
122 try:
123 KeyBoardKeys.oneKey('tab')
124 except Exception as e:
125 raise e
126
127 # 模拟enter键
128 def enterKey():
129 try:
130 KeyBoardKeys.oneKey('enter')
131 except Exception as e:
132 raise e
133
134 # 屏幕截图
135 def saveScreenShot():
136 pictureName = DirAndTime.CreatePicturePath() +'\\'+DirAndTime.getCurrentTime() + '.png'
137 try:
138 driver.get_screenshot_as_file(pictureName)
139 except Exception as e:
140 raise e
141 else:
142 return pictureName
143
144 def waitPresenceOfElementLocated(by, locator):
145 '''
146 显示等待页面元素出现在DOM中,单并不一定可见
147 :param by:
148 :param locator:
149 :return:
150 '''
151 waitUtil.presenceOfElementLocated(by, locator)
152
153 def waitFrameToBeAvailableAndSwitchToIt(by, locator):
154 '''
155 检查frame是否存在,存在就切换到frame中
156 :param by:
157 :param locator:
158 :return:
159 '''
160 waitUtil.frameToBeAvailableAndSwtichToIt(by, locator)
161
162 def waitVisibiltyOfElementLocated(by, locator):
163 '''
164 显示等待页面元素出现在DOM中,并且可见
165 :param by:
166 :param locator:
167 :return:
168 '''
169 waitUtil.visibiltyOfElementLocated(by, locator)
170
171 # 关闭浏览器
172 def quitBroswer():
173 try:
174 driver.quit()
175 except Exception as e:
176 raise e
177 if __name__=='__main__':
178 openBrowser('firefox')
179 loadUrl('http://www.baidu.com')
180 # inputValue('id', 'kw','python')
181 # clear('id', 'kw')
182 # inputValue('id', 'kw', 'python')
183 # clickBtn('id', 'su')
184 # sleep(3)
185 # title = getTitle()
186 # print(title)
187 # assertTitle('python')
188 # assert_string_in_page_source('python')
189 ctrlV('python')
四、项目数据文件设计
我们既然要实现关键字驱动的测试,无疑是通过关键字数据文件来控制代码的执行
新建testData文件夹,并新建126mailSend.xlsx文件。文件内容包括3个sheet页,分别为测试用例,登录,发送邮件
测试用例页
登录页
发送邮件页
注意:表格中的关键字 需要和PageAction.py中的方法名字保持一致
五、项目配置模块
新建config目录,并新建VarConfig.py文件记录全局的目录及excel文件部分信息
1 # 存储全局的变量2 import os3 4 # 项目根目录5 projectPath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))6 # 截图目录7 exceptionPath = projectPath +r'\exceptionpictures'8 9 # 驱动存放路径, 需要自己根据自己电脑的驱动为止修改
10 iePath = ''
11 chromePath = ''
12 fireFox = ''
13
14 # excel文件存放路径
15 excelPath = projectPath + r'\testData\126mailSend.xlsx'
16 # loh文件存放路径
17 logPath = projectPath + '\\log\\'
18 # 测试用例部分列对应的列号
19 testCase_testCaseName = 2
20 testCase_testStepName = 4
21 testCase_testIsExecute = 5
22 testCase_testRunEndTime = 6
23 testCase_testResult = 7
24
25 # 用例步骤对应的列号
26 testStep_testNum = 1
27 testStep_testStepDescribe = 2
28 testStep_keyWord = 3
29 testStep_elementBy = 4
30 testStep_elementLocator = 5
31 testStep_operateValue = 6
32 testStep_testRunTime = 7
33 testStep_testResult = 8
34 testStep_testErrorInfo = 9
35 testStep_testErrorPic = 10
36
37
38 if __name__=='__main__':
39
40 print(projectPath)
41 print(exceptionPath)
六、测试用例编写
前期所有的准备都已经完成,接下来我们开始编写测试用例
新建testCases文件夹,并新建Test126SendMailWithAttachment.py编写用例
1 from util.ParseExcel import ParseExcel2 from config.VarConfig import *3 from action.PageAction import *4 import traceback5 from util.log import Logger6 import logging7 8 log = Logger(__name__, CmdLevel=logging.INFO, FileLevel=logging.INFO)9 p = ParseExcel()
10 sheetName = p.wb.sheetnames# 获取到excel的所有sheet名称
11
12 def Test126MailSendWithAtt():
13 try:
14 testCasePassNum = 0
15
16 requiredCase = 0
17 isExecuteColumnValues = p.getColumnValue(sheetName[0], testCase_testIsExecute)
18 # print(columnValues)
19 for index, value in enumerate(isExecuteColumnValues):
20 # print(index, value)
21 # 获取对应的步骤sheet名称
22 stepSheetName = p.getCellOfValue(sheetName[0],index+2, testCase_testStepName)
23 # print(stepSheetName)
24 if value.strip().lower() == 'y':
25 requiredCase += 1
26 testStepPassNum = 0
27 print('开始执行测试用例"{}"'.format(stepSheetName))
28 log.logger.info('开始执行测试用例"{}"'.format(stepSheetName))
29 # 如果用例被标记为执行y,切换到对应的sheet页
30 # 获取对应的sheet表中的总步骤数,关键字,定位方式,定位表达式,操作值
31 # 步骤总数
32 values = p.getColumnValue(stepSheetName, testStep_testNum) # 第一列数据
33 stepNum = len(values)
34 print(stepNum)
35 for step in range(2, stepNum+2):
36 rawValue = p.getRowValue(stepSheetName, step)
37 # 执行步骤名称
38 stepName = rawValue[testStep_testStepDescribe -2]
39 # 关键字
40 keyWord = rawValue[testStep_keyWord - 2]
41 # 定位方式
42 by = rawValue[testStep_elementBy - 2]
43 # 定位表达式
44 locator = rawValue[testStep_elementLocator - 2]
45 # 操作值
46 operateValue = rawValue[testStep_operateValue - 2]
47
48 if keyWord and by and locator and operateValue:
49 func = keyWord + '(' +'"' +by +'"'+ ',' +'"'+locator+ '"'+',' +'"'+ operateValue + '"'+')'
50 elif keyWord and by and locator and operateValue is None:
51 func = keyWord + '(' +'"' +by +'"'+ ',' +'"'+locator+ '"'+')'
52
53 elif keyWord and operateValue and type(operateValue) == str and by is None and locator is None:
54 func = keyWord + '(' +'"' + operateValue + '"' + ')'
55
56 elif keyWord and operateValue and type(operateValue) == int and by is None and locator is None:
57 func = keyWord + '(' + str(operateValue) +')'
58
59 else:
60 func = keyWord + '('+')'
61
62 try:
63 # 执行测试步骤
64 eval(func)
65 except Exception:
66 # 截图
67 picPath = saveScreenShot()
68 # 写回测试结果
69 errorInfo = traceback.format_exc()
70 p.writeTestResult(stepSheetName, step, 'Failed', errorInfo, picPath)
71 print('步骤"{}"执行失败'.format(stepName))
72 log.logger.info('步骤"{}"执行失败'.format(stepName))
73 raise
74 else:
75 print('步骤"{}"执行通过'.format(stepName))
76 log.logger.info('步骤"{}"执行通过'.format(stepName))
77 # 标记测试步骤为pass
78 p.writeTestResult(stepSheetName, step, 'Pass')
79 testStepPassNum += 1
80 # print('通过用例步数数:',testStepPassNum)
81 if testStepPassNum == stepNum:
82 # 标记测试用例sheet页的执行结果为pass
83 p.writeCell(sheetName[0], index+2, testCase_testResult, 'Pass')
84 testCasePassNum += 1
85 else:
86 p.writeCell(sheetName[0], index + 2, testCase_testResult, 'Failed')
87 print('共{}条用例,{}条需要被执行,本次执行通过{}条'.format(len(isExecuteColumnValues), requiredCase, testCasePassNum))
88 log.logger.info('共{}条用例,{}条需要被执行,本次执行通过{}条'.format(len(isExecuteColumnValues), requiredCase, testCasePassNum))
89 except Exception as e:
90 print(traceback.format_exc(e))
91 log.logger.info(traceback.format_exc(e))
92
93 if __name__=='__main__':
94 Test126MailSendWithAtt()
七、加载用例
项目主目录下直接新建RunTest.py,用例运行测试用例
1 if __name__=='__main__':
2 from testCases.Test126SendMailWithAttachment import Test126MailSendWithAtt
3 Test126MailSendWithAtt()
八、项目总结
1.使用外部测试数据文件,使用Excel管理测试用例的集合和每个测试用例的所有测试步骤 ,实现一个文件中完成测试用例的维护
2.每个测试用例的测试结果在一个文件中查看和统计
3.通过定义关键字,操作元素的定位方式及定位表达式和操作值就可以实现每个测试用例步 骤的执行,可以更加灵活地实现自动化测试的需求
4.实现定位表达式和测试代码的分离,实现定位表达式直接在测试数据文件中进行维护。
5.框架提供日志功能,方便调试和监控自动化测试程序的执行
6.基于关键字测试框架,即使不懂开发技术的测试人员也可以实施自动化测试,便于在整个 测试团队中推广和使用自动化测试技术,降低自动化测试实施的技术门槛
7.基于关键字的方式,可以进行任意关键字的扩展,以满足更加复杂项目的自动化测试需求
九、运行框架
1.运行环境需要安装了python3.x+selenium2.x;第三方模块openpyxl,pypiwin32,
win32api, win32con
2.本地已配置chrome/firefox/ie浏览器及对应版本驱动
3.需要修改Excel文件中对应的用户名和密码
4.直接运行RunTest.py文件即可执行整个框架
最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
软件测试面试小程序
被百万人刷爆的软件测试题库!!!谁用谁知道!!!全网最全面试刷题小程序,手机就可以刷题,地铁上公交上,卷起来!
涵盖以下这些面试题板块:
1、软件测试基础理论 ,2、web,app,接口功能测试 ,3、网络 ,4、数据库 ,5、linux
6、web,app,接口自动化 ,7、性能测试 ,8、编程基础,9、hr面试题 ,10、开放性测试题,11、安全测试,12、计算机基础
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!
相关文章:

如何搭建关键字驱动自动化测试框架?
前言 那么这篇文章我们将了解关键字驱动测试又是如何驱动自动化测试完成整个测试过程的。关键字驱动框架是一种功能自动化测试框架,它也被称为表格驱动测试或者基于动作字的测试。关键字驱动的框架的基本工作是将测试用例分成四个不同的部分。首先是测试步骤&#x…...

WPF实战项目十二(API篇):配置AutoMapper
1、新建类库WPFProjectShared,在类库下新建文件夹Dtos,新建BaseDto.cs,继承INotifyPropertyChanged,实现通知更新。 public class BaseDto : INotifyPropertyChanged{public int Id { get; set; }public event PropertyChangedEv…...

Linux 内核模块加载过程之重定位
文章目录 一、内核模块符号解析1.1 内核模块重定位函数调用1.1.1 struct load_info info1.1.2 copy_module_from_user 1.2 simplify_symbols1.2.1 simplify_symbols1.2.2 resolve_symbol_wait1.2.3 resolve_symbol1.2.4 find_symbol 二、 apply_relocations2.1 apply_relocatio…...

Flink流批一体计算(19):PyFlink DataStream API之State
目录 keyed state Keyed DataStream 使用 Keyed State 实现了一个简单的计数窗口 状态有效期 (TTL) 过期数据的清理 全量快照时进行清理 增量数据清理 在 RocksDB 压缩时清理 Operator State算子状态 Broadcast State广播状态 keyed state Keyed DataStream 使用 k…...

adb shell获取安卓设备电量ROM内存帧率等信息
adb shell获取安卓设备电量ROM内存帧率等信息 adb shell指令获取Android设备的运行状态,如电池信息(包含电量百分比,电池状态,电池温度,电池电压,充放电电流),CPU占比,内…...

springboot服务端接口外网远程调试,并实现HTTP服务监听
文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 windows系统2.1.2 linux系统 2.2 创建隧道映射本地端口2.3 测试公网地址 3. 固定公网地址3.1 保留一个二级子域名3.2 配置二级子域名3.2 测试使用固定公网地址…...

代码随想录算法训练营之JAVA|第四十二天|70. 爬楼梯
今天是第 天刷leetcode,立个flag,打卡60天,如果做不到,完成一件评论区点赞最高的挑战。 算法挑战链接 70. 爬楼梯https://leetcode.cn/problems/climbing-stairs/ 第一想法 这是一个动态规划的入门题目,在看完完全背…...

【uniapp】 实现公共弹窗的封装以及调用
图例:红框区域为 “ 内容区域 ” 一、组件 <!-- 弹窗组件 --> <template> <view class"add_popup" v-if"person.isShowPopup"><view class"popup_cont" :style"{width:props.width&&props.width&…...

DevOps系列文章之 Python基础
列表 Python中的列表类似于C语言中的数组的概念,列表由内部的元素组成,元素可以是任何对象 Python中的列表是可变的 简单的理解就是:被初始化的列表,可以通过列表的API接口对列表的元素进行增删改查 1、定义列表 1.可以将列表当成…...

代码随想录第五十天
代码随想录第五十天 Leetcode 123. 买卖股票的最佳时机 IIILeetcode 188. 买卖股票的最佳时机 IV Leetcode 123. 买卖股票的最佳时机 III 题目链接: 买卖股票的最佳时机 III 自己的思路:想不到!!!!高维dp数组!&#x…...

redis缓存雪崩、穿透、击穿解决方案
redis缓存雪崩、穿透、击穿解决方案 背景缓存雪崩缓存击穿缓存穿透总结背景 关于缓存异常,我们常见的有三个问题:缓存雪崩、缓存击穿、缓存穿透。这三个问题一旦发生,会导致大量请求直接落到数据库层面。如果请求的并发量很大,会影响数据库的运行,严重的会导致数据库宕机…...

基于HarmonyOS ArkUI实现七夕壁纸轮播
七夕情人节,为了Ta,你打算用什么方式表达爱?是包包、鲜花、美酒、巧克力,还是一封充满爱意的短信?作为程序员,以代码之名,表达爱。本节将演示如何在基于HarmonyOS ArkUI的SwiperController、Ima…...

FusionAD:用于自动驾驶预测和规划任务的多模态融合
论文背景 自动驾驶(AD)任务通常分为感知、预测和规划。在传统范式中,AD中的每个学习模块分别使用自己的主干,独立地学习任务。 以前,基于端到端学习的方法通常基于透视视图相机和激光雷达信息直接输出控制命令或轨迹…...

C# 序列化json数据,datatabel转对象
datatabel直接转对象 转对象逻辑 1.将datatabel转为json格式 2.将json格式的内容转化为模型data_model的list对象 JsonConvert.DeserializeObject<List<data_model>>(JsonConvert.SerializeObject(dt))...

axios引入的详细讲解
1.安装axios:npm install axios,等待安装完毕即可 2.引用axios:在需要使用的页面中引用 import axios from axios 即可 axios请求的时候有两种方式:一种是get请求,另一种是post请求 get请求: axios({…...

16- flask-bootstrap模板的使用
Flask 中支持 flask-bootstrap模板 和 bootstrap-flask模板 # 不使用: bootstrap-flask # pip install bootstrap-flask1.3.1 # 支持bootstrap 4 # pip install flask-bootstrap # 支持bootstrap3# 中文文档:https://flask-bootstrap-zh.readthedocs.io/zh/latest/ # 样式文档…...

机器学习-神经网络(西瓜书)
神经网络 5.1 神经元模型 在生物神经网络中,神经元之间相互连接,当一个神经元受到的外界刺激足够大时,就会产生兴奋(称为"激活"),并将剩余的"刺激"向相邻的神经元传导。 神经元模型…...

Apache StreamPark系列教程第二篇——项目打包和开发
一、项目打包 项目依赖maven、jdk8.0、前端(node、npm) //下载代码 git clone//maven打包相关内容 mvn -N io.takari:maven:wrapper //前端打包相关内容 curl -sL https://rpm.nodesource.com/setup_16.x | bash - yum -y install nodejs npm -v npm install -g pnpm默认是h2…...

Visual Studio 2022的MFC框架——WinMain函数
我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下Visual Studio 2022下开发工具的MFC框架知识。 大家还记得创建Win32应用程序是怎么弄的吗? Win32应用程序的建立到运行是有一个个关系分明的步骤的: 1.进入W…...

9. 解谜游戏
目录 题目 Description Input Notes 思路 暴力方法 递归法 注意事项 C代码(递归法) 关于DFS 题目 Description 小张是一个密室逃脱爱好者,在密室逃脱的游戏中,你需要解开一系列谜题最终拿到出门的密码。现在小张需要打…...

fastjson利用templatesImpl链
fastjson1.2.24 环境: pom.xml: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLoc…...

OpenCV 开启O3优化
opencv默认没有开启O3优化选项,需要进行手动设置,下面是一种优化方法: 方法一 在 /opencv-4.5.5/cmake/OpenCVCompilerOptions.cmake 中的第 269 行做出以下修改: # 修改前 set(OPENCV_EXTRA_FLAGS_RELEASE "${OPENCV_EXT…...

css background实现四角边框
2023.8.27今天我学习了如何使用css制作一个四角边框,效果如下: .style{background: linear-gradient(#33cdfa, #33cdfa) left top,linear-gradient(#33cdfa, #33cdfa) left top,linear-gradient(#33cdfa, #33cdfa) right top,linear-gradient(#33cdfa, #…...

摆动序列【贪心算法】
摆动序列 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 class Solution {public int wiggleMaxLength(int…...

【Terraform学习】使用 Terraform创建 S3 存储桶事件(Terraform-AWS最佳实战学习)
本站以分享各种运维经验和运维所需要的技能为主 《python》:python零基础入门学习 《shell》:shell学习 《terraform》持续更新中:terraform_Aws学习零基础入门到最佳实战 《k8》暂未更新 《docker学习》暂未更新 《ceph学习》ceph日常问题解…...

自定义String字符串工具类 StringUtils.java
自定义String字符串工具类 StringUtils.java 简介 自定义String字符串工具类 api 是否为空 checkEmpty(String str);目标字符串是目标数组中的一个 checkContains(String str, String[] target);限制最大长度 checkMaxLength(String str, Long l);是否纯数字的字符串 check…...

mongTemplate实现group分组查询aggregation
MongoService封装 <T> List<T> group(Class<T> clazz, Aggregation aggregation,String documentName); MongoServiceImpl实现类 Overridepublic <T> List<T> group(Class<T> clazz, Aggregation aggregation,String documentName) {//…...

防御网络攻击风险的4个步骤
如今,人们正在做大量工作来保护 IT 系统免受网络犯罪的侵害。令人担忧的是,对于运营技术系统来说,情况却并非如此,运营技术系统用于运行从工厂到石油管道再到发电厂的所有业务。 组织应该强化其网络安全策略,因为针对…...

相机SD卡数据丢失如何恢复?
出门在外,相机是人们记录生活点滴的重要工具,是旅游的最佳玩伴。人们每到一个地方,都喜欢用相机来见证自己来过的痕迹,拍好的照片都会被放到相机卡里,但在使用相机时,有时我们会意外删除了重要的照片或视频…...

Java小记-矩阵转置
对于给定的一个二维矩阵,请转置后进行输出。 输入描述 对于一个n*m的矩阵,输入有n行,每行是m个以空格分隔的数字。 输出描述 n*m矩阵的转置矩阵。输出m行,每行是n个空格分隔的数据。 import java.io.*; import java.util.*;pub…...