目录
项目目录结构
action目录
config目录
exceptionpictures目录
log目录
testCases目录
testData目录
util目录
总结
之前写过一篇Java版的关键字驱动,现在来写一篇Python版本的,网上好多教程都是虎头蛇尾的不完整~
说下思路,这边没有用Python反射去获取方法名去执行关键字,而是通过Python内置函数eval()来拼接字符串组成关键字然后一并执行,这个用法比较妙!Python eval()内置函数具体用法可自行百度。
项目目录结构
先看下目录结构:
action目录
action目录主要存放关键字模块:PageAction.py,封装了一些基本的web操作方法,如点击、输入、查找等,具体代码如下:
from seleniumkeyword.util.ObjectMap import *
from seleniumkeyword.util.ClipboardUtil import Clipboard
from seleniumkeyword.util.KeyBoardUtil import KeyBoardKeys
from seleniumkeyword.util.WaitUntil import WaitUnit
from seleniumkeyword.util.DirAndTime import *
from selenium import webdriver
driver = None
waitUtil = None
# 打开浏览器
def openBrowser(browser):
global driver, waitUtil
try:
if browser.lower() == 'ie':
driver = webdriver.Ie(executable_path=iePath)
elif browser.lower() == 'chrome':
driver = webdriver.Chrome(executable_path=chromePath)
else:
# driver = webdriver.Firefox(executable_path=fireFox)
driver = webdriver.Firefox()
except Exception as e:
raise e
else:
waitUtil = WaitUnit(driver) # driver 创建之后, 创建等待类实例对象
# 浏览器窗口最大化
def maximize_browser():
try:
driver.maximize_window()
except Exception as e:
raise e
# 加载网址
def loadUrl(url):
try:
driver.get(url)
except Exception as e:
raise e
# 强制等待
def sleep(sleepSeconds):
try:
import time
time.sleep(sleepSeconds)
except Exception as e:
raise e
# 清除输入框的内容
def clear(by, locator):
try:
getElement(driver, by, locator).clear()
except Exception as e:
raise e
# 输入框中输入内容
def inputValue(by, locator, value):
try:
element = getElement(driver, by, locator)
# element.click()
element.send_keys(value)
except Exception as e:
raise e
# 点击操作
def clickBtn(by, locator):
try:
getElement(driver, by, locator).click()
except Exception as e:
raise e
# 断言页面的title
def assertTitle(titleStr):
try:
assert titleStr in driver.title, "%s not found in title!" % titleStr
except AssertionError as e:
raise AssertionError(e)
except Exception as e:
raise e
# 断言目标字符串是否包含在页面源码中
def assert_string_in_page_source(assertString):
try:
assert assertString in driver.page_source, "%s not found in page source!" % assertString
except AssertionError as e:
raise AssertionError(e)
except Exception as e:
raise e
# 获取当前页面的title
def getTitle():
try:
return driver.title
except Exception as e:
raise e
# 获取页面源码
def getPageSource():
try:
return driver.page_source
except Exception as e:
raise e
# 切换到frame里面
def switchToFrame(by, locator):
try:
driver.switch_to.frame(getElement(driver, by, locator))
except Exception as e:
raise e
# 跳到默认的frame
def switchToDefault():
try:
driver.switch_to.default_content()
except Exception as e:
raise e
# 模拟ctrl+v键
def ctrlV(value):
try:
Clipboard.setText(value)
sleep(2)
KeyBoardKeys.twoKeys('ctrl', 'v')
except Exception as e:
raise e
# 模拟tab键
def tabKey():
try:
KeyBoardKeys.oneKey('tab')
except Exception as e:
raise e
# 模拟enter键
def enterKey():
try:
KeyBoardKeys.oneKey('enter')
except Exception as e:
raise e
# 屏幕截图
def saveScreenShot():
pictureName = DirAndTime.CreatePicturePath() + '\\' + DirAndTime.getCurrentTime() + '.png'
try:
driver.get_screenshot_as_file(pictureName)
except Exception as e:
raise e
else:
return pictureName
def waitPresenceOfElementLocated(by, locator):
'''
显示等待页面元素出现在DOM中,单并不一定可见
:param by:
:param locator:
:return:
'''
waitUtil.presenceOfElementLocated(by, locator)
def waitFrameToBeAvailableAndSwitchToIt(by, locator):
'''
检查frame是否存在,存在就切换到frame中
:param by:
:param locator:
:return:
'''
waitUtil.frameToBeAvailableAndSwtichToIt(by, locator)
def waitVisibiltyOfElementLocated(by, locator):
'''
显示等待页面元素出现在DOM中,并且可见
:param by:
:param locator:
:return:
'''
waitUtil.visibiltyOfElementLocated(by, locator)
# 关闭浏览器
def quitBroswer():
try:
driver.quit()
except Exception as e:
raise e
if __name__ == '__main__':
openBrowser('firefox')
loadUrl('http://www.baidu.com')
# inputValue('id', 'kw','python')
# clear('id', 'kw')
# inputValue('id', 'kw', 'python')
# clickBtn('id', 'su')
# sleep(3)
# title = getTitle()
# print(title)
# assertTitle('python')
# assert_string_in_page_source('python')
ctrlV('python')
config目录
主要是一些配置 参数和常量,VarConfig.py,如果用的selenium 4和webdriver manager的话可以不用定义驱动路径,代码运行会自动安装对应的驱动程序。
具体代码如下:
# 存储全局的变量
import os
# 项目根目录
projectPath = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 截图目录
exceptionPath = projectPath + r'\exceptionpictures'
# 驱动存放路径, 需要自己根据自己电脑的驱动为止修改
iePath = ''
chromePath = 'C:\\Users\\Maven\\.wdm\\drivers\\chromedriver\\win32\\109.0.5414.74\\chromedriver.exe'
fireFox = ''
# excel文件存放路径
excelPath = projectPath + r'\testData\keywords.xlsx'
# loh文件存放路径
logPath = projectPath + '\\log\\'
# 测试用例部分列对应的列号
testCase_testCaseName = 2
testCase_testStepName = 4
testCase_testIsExecute = 5
testCase_testRunEndTime = 6
testCase_testResult = 7
# 用例步骤对应的列号
testStep_testNum = 1
testStep_testStepDescribe = 2
testStep_keyWord = 3
testStep_elementBy = 4
testStep_elementLocator = 5
testStep_operateValue = 6
testStep_testRunTime = 7
testStep_testResult = 8
testStep_testErrorInfo = 9
testStep_testErrorPic = 10
if __name__ == '__main__':
print(projectPath)
print(exceptionPath)
exceptionpictures目录
主要存放用例异常时的截图
log目录
存放执行日志
testCases目录
存放测试用例,用例示例代码如下:
from seleniumkeyword.util.ParseExcel import ParseExcel
from seleniumkeyword.config.VarConfig import *
from seleniumkeyword.action.PageAction import *
import traceback
from seleniumkeyword.util.Log import Logger
import logging
log = Logger(__name__, CmdLevel=logging.INFO, FileLevel=logging.INFO)
p = ParseExcel()
sheetName = p.wb.sheetnames # 获取到excel的所有sheet名称
def TestKeywords():
try:
testCasePassNum = 0
requiredCase = 0
isExecuteColumnValues = p.getColumnValue(sheetName[0], testCase_testIsExecute)
print(isExecuteColumnValues)
print(len(isExecuteColumnValues))
for index, value in enumerate(isExecuteColumnValues):
print(index, value)
# 获取对应的步骤sheet名称
stepSheetName = p.getCellOfValue(sheetName[0], index + 2, testCase_testStepName)
# print(stepSheetName)
if value is not None and value.strip().lower() == 'y':
requiredCase += 1
testStepPassNum = 0
print('开始执行测试用例"{}"'.format(stepSheetName))
log.logger.info('开始执行测试用例"{}"'.format(stepSheetName))
# 如果用例被标记为执行y,切换到对应的sheet页
# 获取对应的sheet表中的总步骤数,关键字,定位方式,定位表达式,操作值
# 步骤总数
values = p.getColumnValue(stepSheetName, testStep_testNum) # 第一列数据
stepNum = len(values)
print(stepNum)
for step in range(2, stepNum + 2):
rawValue = p.getRowValue(stepSheetName, step)
# 执行步骤名称
stepName = rawValue[testStep_testStepDescribe - 2]
# 关键字
keyWord = rawValue[testStep_keyWord - 2]
# 定位方式
by = rawValue[testStep_elementBy - 2]
# 定位表达式
locator = rawValue[testStep_elementLocator - 2]
# 操作值
operateValue = rawValue[testStep_operateValue - 2]
if keyWord and by and locator and operateValue:
func = keyWord + '(' + '"' + by + '"' + ',' + '"' + locator + '"' + ',' + '"' + operateValue + '"' + ')'
elif keyWord and by and locator and operateValue is None:
func = keyWord + '(' + '"' + by + '"' + ',' + '"' + locator + '"' + ')'
elif keyWord and operateValue and type(operateValue) == str and by is None and locator is None:
func = keyWord + '(' + '"' + operateValue + '"' + ')'
elif keyWord and operateValue and type(operateValue) == int and by is None and locator is None:
func = keyWord + '(' + str(operateValue) + ')'
else:
func = keyWord + '(' + ')'
try:
# 执行测试步骤
eval(func)
except Exception:
# 截图
picPath = saveScreenShot()
# 写回测试结果
errorInfo = traceback.format_exc()
p.writeTestResult(stepSheetName, step, 'Failed', errorInfo, picPath)
print('步骤"{}"执行失败'.format(stepName))
log.logger.info('步骤"{}"执行失败'.format(stepName))
else:
print('步骤"{}"执行通过'.format(stepName))
log.logger.info('步骤"{}"执行通过'.format(stepName))
# 标记测试步骤为pass
p.writeTestResult(stepSheetName, step, 'Pass')
testStepPassNum += 1
# print('通过用例步数数:',testStepPassNum)
if testStepPassNum == stepNum:
# 标记测试用例sheet页的执行结果为pass
p.writeTestCaseResult(sheetName[0], index + 2, 'Pass')
testCasePassNum += 1
else:
p.writeTestCaseResult(sheetName[0], index + 2, 'Failed')
print('共{}条用例,{}条需要被执行,本次执行通过{}条'.format(len(isExecuteColumnValues), requiredCase, testCasePassNum))
log.logger.info('共{}条用例,{}条需要被执行,本次执行通过{}条'.format(len(isExecuteColumnValues), requiredCase, testCasePassNum))
except Exception as e:
print(traceback.format_exc(e))
log.logger.info(traceback.format_exc(e))
if __name__ == '__main__':
TestKeywords()
testData目录
存放Excel的关键字用例:keywords.xlsx,主要分两块,一块是用例的汇总信息见表一(表名:测试用例),一个是用例的具体执行信息,见表二(表名:百度搜索)、表三(表名:登录):
序号 | 用例名称 | 用例描述 | 步骤名 | 是否执行 | 执行结束时间 | 结果 |
1 | 百度搜索 | 百度搜索 | 百度搜索 | y | 2023:04:18 22:45:58 | Pass |
2 | 登录126邮箱 | 使用无效的账号登录126邮箱 | 登录 | y | 2023:04:18 22:48:38 | Failed |
根据表一即“测试用例”表是否执行列取值为y则执行步骤列对应表二表三中的关键字,注意这个要一一对应不然会报错!
util目录
封装一些常用的操作,如读写Excel,时间操作,等待操作,查找操作等,具体类如下:
ClipboardUtil.py,封装复制粘贴方法
import win32clipboard as w
import win32con
class Clipboard(object):
@staticmethod
def getText():
'''
获取剪切板的内容
:return:
'''
try:
# 打开剪切板
w.OpenClipboard()
# 读取数据
value = w.GetClipboardData(win32con.CF_TEXT)
# 关闭剪切板
w.CloseClipboard()
except Exception as e:
raise e
else:
return value
@staticmethod
def setText(value):
'''
设置剪切板内容
:return:
'''
try:
w.OpenClipboard() # 打开剪切板
w.EmptyClipboard() # 清空剪切板
w.SetClipboardData(win32con.CF_UNICODETEXT, value) # 设置内容
w.CloseClipboard() # 关闭
except Exception as e:
raise e
if __name__ == '__main__':
from selenium import webdriver
value = 'python'
driver = webdriver.Firefox()
driver.get('http://www.baidu.com')
query = driver.find_element_by_id('kw')
Clipboard.setText(value)
clValue = Clipboard.getText()
query.send_keys(clValue.decode('utf-8'))
DirAndTime.py,文件和时间的一些封装
from datetime import datetime, date
from seleniumkeyword.config.VarConfig import *
import os
class DirAndTime(object):
@staticmethod
def getCurrentDate():
'''
获取当前日期
:return:
'''
try:
currentDate = date.today()
except Exception as e:
raise e
else:
return str(currentDate)
@staticmethod
def getCurrentTime():
'''
获取当前时间
:return:
'''
try:
Time = datetime.now()
currentTime = Time.strftime('%H_%M_%S')
except Exception as e:
raise e
else:
return currentTime
@staticmethod
def CreatePicturePath():
'''
创建图片存放路径路径
:return:
'''
try:
picturePath = os.path.join(exceptionPath, DirAndTime.getCurrentDate())
if not os.path.exists(picturePath):
os.makedirs(picturePath) # 生成多级目录
except Exception as e:
raise e
else:
return picturePath
if __name__ == '__main__':
print(DirAndTime.getCurrentDate())
print(DirAndTime.getCurrentTime())
print(DirAndTime.CreatePicturePath())
KeyBoardUtil.py封装一些键盘鼠标操作
import win32api
import win32con
class KeyBoardKeys(object):
'''
模拟键盘
'''
# 键盘编码
vk_code = {
'enter': 0x0D,
'tab': 0x09,
'ctrl': 0x11,
'v': 0x56
}
@staticmethod
def keyDown(keyName):
'''
模拟按下键
:param keyName:
:return:
'''
try:
win32api.keybd_event(KeyBoardKeys.vk_code[keyName], 0, 0, 0)
except Exception as e:
raise e
@staticmethod
def keyUp(keyName):
'''
释放键
:param keyName:
:return:
'''
try:
win32api.keybd_event(KeyBoardKeys.vk_code[keyName], 0, win32con.KEYEVENTF_KEYUP, 0)
except Exception as e:
raise e
@staticmethod
def oneKey(key):
'''
模拟当个按键
:param key:
:return:
'''
try:
KeyBoardKeys.keyDown(key)
KeyBoardKeys.keyUp(key)
except Exception as e:
raise e
@staticmethod
def twoKeys(key1, key2):
'''
模拟组合按键
:param key1:
:param key2:
:return:
'''
try:
KeyBoardKeys.keyDown(key1)
KeyBoardKeys.keyDown(key2)
KeyBoardKeys.keyUp(key1)
KeyBoardKeys.keyUp(key2)
except Exception as e:
raise e
if __name__ == '__main__':
from selenium import webdriver
driver = webdriver.Firefox()
driver.get('http://www.baidu.com')
driver.find_element_by_id('kw').send_keys('python')
KeyBoardKeys.oneKey('enter')
Log.py:封装日志类
import logging
import time
from seleniumkeyword.config.VarConfig import *
class Logger(object):
'''
封装的日志模块
'''
def __init__(self, logger, CmdLevel=logging.INFO, FileLevel=logging.INFO):
"""
:param logger:
:param CmdLevel:
:param FileLevel:
"""
try:
self.logger = logging.getLogger(logger)
self.logger.setLevel(logging.DEBUG) # 设置日志输出的默认级别
# 日志输出格式
fmt = logging.Formatter('%(asctime)s - %(filename)s:[%(lineno)s] - [%(levelname)s] - %(message)s')
# 日志文件名称
# self.LogFileName = os.path.join(conf.log_path, "{0}.log.txt".format(time.strftime("%Y-%m-%d")))# %H_%M_%S
currTime = time.strftime("%Y-%m-%d")
self.LogFileName = logPath + currTime + '.txt'
# 设置控制台输出
# sh = logging.StreamHandler()
# sh.setFormatter(fmt)
# sh.setLevel(CmdLevel)# 日志级别
# 设置文件输出
fh = logging.FileHandler(self.LogFileName)
fh.setFormatter(fmt)
fh.setLevel(FileLevel) # 日志级别
# self.logger.addHandler(sh)
self.logger.addHandler(fh)
except Exception as e:
raise e
if __name__ == '__main__':
logger = Logger("fox", CmdLevel=logging.DEBUG, FileLevel=logging.DEBUG)
logger.logger.debug("debug")
logger.logger.log(logging.ERROR, '%(module)s %(info)s', {'module': 'log日志', 'info': 'error'}) # ERROR,log日志 error
ObjectMap.py:封装selenium查找方法
from selenium.webdriver.support.wait import WebDriverWait
def getElement(driver, by, locator):
'''
查找单一元素
:param driver:
:param by:
:param locator:
:return: 元素对象
'''
try:
element = WebDriverWait(driver, 30).until(lambda x: x.find_element(by, locator))
except Exception as e:
raise e
else:
return element
def getElements(driver, by, locator):
'''
获取一组元素
:param driver:
:param by:
:param locator:
:return: 一组元素对象
'''
try:
elements = WebDriverWait(driver, 30).until(lambda x: x.find_element(by, locator))
except Exception as e:
raise e
else:
return elements
if __name__ == "__main__":
from selenium import webdriver
import time
driver = webdriver.Firefox()
driver.get('https://mail.126.com')
time.sleep(5)
driver.switch_to.frame(getElement(driver, 'xpath', "//div[@id='loginDiv']/iframe"))
username = getElement(driver, 'xpath', "//input[@name='email']")
username.send_keys('linuxxiaochao')
driver.switch_to.default_content()
driver.quit()
ParseExcel.py:Excel解析、创建
from openpyxl import load_workbook
from openpyxl.styles import colors
from openpyxl.styles import Font, Color
from seleniumkeyword.config.VarConfig import *
from datetime import datetime, date
class ParseExcel(object):
'''
解析excel文件的封装
'''
def __init__(self):
# 加载excel文件到内存
self.wb = load_workbook(excelPath)
def getRowValue(self, sheetName, rawNo):
'''
获取某一行的数据
:param sheetName:
:param rawNo:
:return: 列表
'''
sh = self.wb[sheetName]
rowValueList = []
for y in range(2, sh.max_column + 1):
value = sh.cell(rawNo, y).value
rowValueList.append(value)
return rowValueList
def getColumnValue(self, sheetName, colNo):
'''
获取某一列的数据
:param sheetName:
:param colNo:
:return: 列表
'''
sh = self.wb[sheetName]
colValueList = []
print('待执行用例最大行数'+str(sh.max_row-1))
for x in range(2, sh.max_row+1):
value = sh.cell(x, colNo).value
colValueList.append(value)
return colValueList
def getCellOfValue(self, sheetName, rowNo, colNo):
'''
获取某一个单元格的数据
:param sheetName:
:param rowNo:
:param colNo:
:return: 字符串
'''
sh = self.wb[sheetName]
value = sh.cell(rowNo, colNo).value
return value
def writeCell(self, sheetName, rowNo, colNo, value):
'''
向某个单元格写入数据
:param rowNo: 行号
:param colNo: 列号
:param value:
:return: 无
'''
sh = self.wb[sheetName]
sh.cell(rowNo, colNo).value = value
if value=='Pass':
ft = Font(color="00008000")
else:
ft = Font(color="00FF0000")
sh.cell(rowNo, colNo).font = ft
self.wb.save(excelPath)
def writeCurrentTime(self, sheetName, rowNo, colNo):
'''
向某个单元格写入当前时间
:return:
'''
sh = self.wb[sheetName]
ft = Font(color="00000000")
sh.cell(rowNo, colNo).font = ft
Time = datetime.now()
currentTime = Time.strftime('%Y:%m:%d %H:%M:%S')
sh.cell(rowNo, colNo).value = currentTime
self.wb.save(excelPath)
def writeTestResult(self, sheetName, rowNo, result, errorInfo=None, errorPic=None):
self.writeCurrentTime(sheetName, rowNo, testStep_testRunTime)
self.writeCell(sheetName, rowNo, testStep_testResult, result)
if errorInfo and errorInfo:
self.writeCell(sheetName, rowNo, testStep_testErrorInfo, errorInfo)
self.writeCell(sheetName, rowNo, testStep_testErrorPic, errorPic)
else:
self.writeCell(sheetName, rowNo, testStep_testErrorInfo, '')
self.writeCell(sheetName, rowNo, testStep_testErrorPic, '')
def writeTestCaseResult(self, sheetName, rowNo, result):
self.writeCurrentTime(sheetName, rowNo, testCase_testRunEndTime)
self.writeCell(sheetName, rowNo, testCase_testResult, result)
if __name__ == '__main__':
p = ParseExcel()
print(p.getRowValue('126account', 2))
print(p.getColumnValue('126account', 3))
print(p.getCellOfValue('126account', 2, 3))
WaitUntil.py:selenium 等待方法的封装
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from seleniumkeyword.util.ObjectMap import *
class WaitUnit(object):
def __init__(self, driver):
self.byDic = {
'id': By.ID,
'name': By.NAME,
'class_name': By.CLASS_NAME,
'xpath': By.XPATH,
'link_text': By.LINK_TEXT
}
self.driver = driver
self.wait = WebDriverWait(self.driver, 50)
def presenceOfElementLocated(self, by, locator):
'''
显示等待某个元素出现在dom中,不一定可见,存在返回元素对象
:param by:
:param locator:
:return:
'''
try:
if by.lower() in self.byDic:
self.wait.until(EC.presence_of_element_located((self.byDic[by.lower()], locator)))
else:
raise TypeError('未找到定位方式,请确保定位方式正确')
except Exception as e:
raise e
def frameToBeAvailableAndSwtichToIt(self, by, locator):
'''
检查frame是否存在,存在就切换到frame中
:param by:
:param locator:
:return:
'''
try:
if by.lower() in self.byDic:
self.wait.until(EC.frame_to_be_available_and_switch_to_it((self.byDic[by.lower()], locator)))
else:
raise TypeError('未找到定位方式,请确保定位方式正确')
except Exception as e:
raise e
def visibiltyOfElementLocated(self, by, locator):
'''
显示等待页面元素出现在dom中, 并且可见, 存在则返回该元素对象
:param by:
:param locator:
:return:
'''
try:
if by.lower() in self.byDic:
self.wait.until(EC.visibility_of_element_located((self.byDic[by.lower()], locator)))
else:
raise TypeError('未找到定位方式,请确保定位方式正确')
except Exception as e:
raise e
if __name__ == '__main__':
from selenium import webdriver
driver = webdriver.Firefox()
driver.get('https://mail.126.com')
wait = WaitUnit(driver)
wait.frameToBeAvailableAndSwtichToIt('xpath', "//div[@id='loginDiv']/iframe")
wait.visibiltyOfElementLocated('xpath', "//input[@name='email']")
uname = getElement(driver, 'xpath', "//input[@name='email']")
uname.send_keys('python')
driver.quit()
RunTest.py:测试运行类
from seleniumkeyword.testCases.TestKeyWords import TestKeyWords
if __name__ == '__main__':
TestKeyWords()
测试执行如下:
总结
实际情况下这些关键字太细使用过程中如果场景复杂要填的表格项会非常多,因此需要封装一些常用的关键字比如登录,退出登录等操作,这里给个思路,因为Python eval 函数是拼接字符串执行的,因此其实定位方法和操作值里填一个参数或者多个参数都是一样的,关键在于怎么去把多个参数分隔成想要的数据。如通常登录至少包含3个查找元素:用户输入、密码输入、登录,那么在填写登录关键字的时候就可以这样填:
序号 | 测试步骤描述 | 关键字 | 操作 | 操作元素的定位表达式 | 操作值 |
1 | 登录 | login | id,id,xpath | loc1,loc2,loc | value1,value2 |
然后封装关键字的时候注意分隔参数这样就能实现复杂的关键字封装~