代码拉取完成,页面将自动刷新
import json
import os
import re
import shutil
import time
from copy import deepcopy
from typing import Final
import allure
import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.webelement import WebElement
from enums.EngineActIdEnum import EngineActIdEnum
from enums.ErrSkipMsgEnum import ErrSkipMsgEnum
from SeleniumHelper import SeleniumHelper
from faker import Faker
def tplTestFunc(self):
titleIdx = self.__class__.runner.currentTitleIdx
self.__class__.runner.currentTitleIdx += 1
self.__class__.runner.runTitle(titleIdx)
def runPyTest(dir: str, files: list[str], skipPyTest: bool, genAllureReport: bool):
reportDir = os.path.join(dir, 'report')
allureReportDir = os.path.abspath(os.path.join(dir, 'allure-report'))
if not skipPyTest:
try:
shutil.rmtree(reportDir)
except:
pass
agvs = [os.path.join(dir, iFile) for iFile in files]
agvs.extend(['-s', '-q', "--alluredir", reportDir])
pytest.main(agvs)
if genAllureReport:
allure_cmd = f"allure generate {reportDir} --clean -o {allureReportDir}" # 将报告转换成html格式文件的命令
os.system(allure_cmd)
print(f"\nallure-report dir : {allureReportDir}")
for iFile in files:
os.remove(os.path.join(dir, iFile))
print('done.')
# allure_cmd2 = f"allure open {allureReportDir}" # 将报告转换成html格式文件的命令
# os.system(allure_cmd2)
class SeleniumTestRunner:
VERSION: Final[list[int]] = [0, 0, 1]
DOC_TYPE: Final[str] = 'SeleniumTestRunner-Plan-DDT'
__argsReg: Final = re.compile(r"{{ARGS:(.*?)}}")
__varsReg: Final = re.compile(r"{{VARS:(.*?)}}")
__fakerReg: Final = re.compile(r"{{FAKER\.(.*?):?((?<=:).*?)?}}")
__keysReg: Final = re.compile(r"{{Keys\.(.*?)}}")
def __init__(self):
self.doc: dict = None
self.initSteps: list[dict] = None
self.titleLst: list[dict] = None
# self.titleRange: list = []
self.currentTitleIdx: int = 0
self.file: str = None
self.fake: Faker = None
self.screenshotOption: dict[str, int] = None
self.__seleniumHelper: SeleniumHelper = None
self.__vars: dict[str, any] = dict()
self.exitIfSetupErr: bool = False
self.skipModuleReason: str = ''
def _resetVars(self):
self.__vars.clear()
@staticmethod
def loadAll(dir: str, skipPyTest: bool, genAllureReport: bool) -> None:
files = os.listdir(dir)
jsonFiles: list[str] = []
plans: dict[str, dict] = {}
with open('TplOfTestPy.pyTpl', 'r', encoding="utf-8") as f:
tplStr: str = f.read()
pyFiles: list[str] = []
for iFileName in files:
iFileName2 = os.path.join(dir, iFileName)
if os.path.isfile(iFileName2):
d = os.path.splitext(iFileName)
if d[1].lower() == ".json":
absFile = os.path.abspath(iFileName2)
with open(absFile, 'r', encoding='utf-8') as fs:
doc: dict = json.load(fs)
if not SeleniumTestRunner.__checkDocVersion(doc):
continue
jsonFiles.append(absFile)
plans[d[0]] = doc
pyName = f'test_{d[0]}.py'
pyFiles.append(pyName)
with open(os.path.join(dir, pyName), 'w', encoding="utf-8") as f:
s = tplStr.replace('{{DOC_FILE}}', iFileName)
s = s.replace('{{DOC_PATH}}', absFile)
f.write(s)
runPyTest(dir, pyFiles, skipPyTest, genAllureReport)
return
@staticmethod
def loadFile(file: str):
with open(file, 'r', encoding='utf-8') as fs:
doc: dict = json.load(fs)
if not SeleniumTestRunner.__checkDocVersion(doc):
return False
self = SeleniumTestRunner()
self.doc = doc
self.file = file
titleLst = self.titleLst = []
plan: dict = doc['plan']
setupSteps: list = doc.get('init').get('setupSteps')
if setupSteps:
self.initSteps = []
self.exitIfSetupErr = doc.get('init').get('exitIfSetupErr')
for iStep in setupSteps:
self.initSteps.append(iStep)
features = plan['features']
for iFeature in features:
stories = iFeature['stories']
for iStory in stories:
titles: list[dict] = iStory['titles']
for iTitle in titles:
iTitle['feature'] = iFeature.get('name')
iTitle['story'] = iStory.get('name')
repeat = iTitle.get('repeat', 1)
if repeat > 1:
titleName = iTitle['name']
iRepeat = 1
while iRepeat <= repeat:
iTitle['name'] = titleName + f' {iRepeat}/{repeat}'
titleLst.append(iTitle.copy())
iRepeat += 1
else:
titleLst.append(iTitle)
# self.titleRange = [''] * len(titleLst)
self.currentTitleIdx = 0
return self
@staticmethod
def __checkDocVersion(doc: dict) -> bool:
if doc.get('docType') != SeleniumTestRunner.DOC_TYPE:
return False
docLibVer: str = doc.get('engineVersion')
if not docLibVer:
return False
docLibVerLst: list[str] = docLibVer.split('.')
if len(docLibVerLst) != 3:
return False
verIdx = 0
while verIdx < 3:
if int(docLibVerLst[verIdx]) > SeleniumTestRunner.VERSION[verIdx]:
return False
verIdx += 1
return True
def initClass(self, cls: type):
nextTestIdx: int = 0
for iTitle in self.titleLst:
setattr(cls, 'test_' + str(nextTestIdx).zfill(5), tplTestFunc)
nextTestIdx += 1
def initRunner(self):
initDict: dict = self.doc['init']
self.currentTitleIdx = 0
with allure.step("--启动参数--"):
with allure.step(f'设定浏览器类型: {initDict["browserType"]}'):
browserType = initDict["browserType"]
implicitly_wait = initDict.get('implicitly_wait')
if implicitly_wait:
with allure.step(f'设定隐性等待: {implicitly_wait}秒'):
assert True
else:
implicitly_wait = 10
with allure.step(f'设定隐性等待为默认值: {implicitly_wait}秒'):
pass
options: list[str] = initDict.get('browserOptions')
if options and len(options) > 0:
with allure.step(f'设定浏览器启动选项: {len(options)}项'):
for iOption in options:
with allure.step(iOption):
assert True
pageLoadStrategy = initDict.get('pageLoadStrategy')
fakerLocal: str = initDict.get('fakerLocal') or "zh_CN"
with allure.step(f'设定Faker本地语言: {fakerLocal}'):
self.fake = Faker(locale=fakerLocal)
ssOption: dict = initDict.get('screenshot', dict({'before': 0, 'after': 2}))
ssBefore: int = ssOption.get('before', 0)
ssAfter: int = ssOption.get('after', 2)
with allure.step(f'设定截图策略: before={ssBefore} , after={ssAfter}'):
ssOption['before'] = ssBefore
ssOption['after'] = ssAfter
self.screenshotOption = ssOption
with allure.step("启动浏览器"):
self.__seleniumHelper = SeleniumHelper(browserType, options)
if self.initSteps:
with allure.step('--启动指令--'):
for iStep in self.initSteps:
try:
self.runStep(iStep)
except Exception as e:
if self.exitIfSetupErr:
self.skipModuleReason = "Module Setup 失败"
# pytest.fail("Module Setup 失败")
def runTitle(self, titleIdx: int):
title = self.titleLst[titleIdx]
severity: str = title.get('severity')
if severity:
allure.dynamic.severity(severity)
feature = title.get('feature')
if feature:
allure.dynamic.feature(feature)
story = title.get('story')
if story:
allure.dynamic.story(story)
allure.dynamic.title(title['name'])
if title.get('description'):
allure.dynamic.description_html(title.get('description'))
if title.get('tag'):
allure.dynamic.tag(*title.get('tag'))
if self.skipModuleReason:
with allure.step(self.skipModuleReason):
pytest.skip(self.skipModuleReason)
return
steps = title.get('steps')
errSkip: int = title.get('errSkip', 0)
try:
for iStep in steps:
self.runStep(iStep)
except Exception as e:
if errSkip == 0:
pytest.fail(e.args[0])
# raise e
elif errSkip == 2:
self.skipModuleReason = e.args[0]
pytest.fail(e.args[0])
# raise e
def quit(self):
self.__seleniumHelper.quit()
def runStep(self, step: dict):
item = list(step.items())[0]
act = item[0]
value: dict = item[1]
seleniumHelper = self.__seleniumHelper
stepName: str = value["step_name"]
stepNameV = self._getSysStrValue(stepName, value, False)
options: dict = value.get('_options', {})
ssOption: dict = options.get('screenshot', self.screenshotOption)
assertError: int = options.get('assertError', False)
errSkip: int = options.get('errSkip', 0)
def _runOneStep(step_name: str, currentRepeat: int, totalRepeat: int):
# region init
stepV: dict = deepcopy(value)
for iKey in stepV.keys():
if type(stepV[iKey]) == str:
stepV[iKey] = self._getSysStrValue(stepV[iKey], stepV, iKey != 'step_name')
isSubStep = totalRepeat > 1
ssBefore: int = ssOption.get('before', 0)
ssAfter: int = ssOption.get('after', 2)
beforeSS: bytes = None
if isSubStep:
step_name = step_name + f'\t{currentRepeat}/{totalRepeat}'
def _saveScreenshot(beforeSS: bytes, before: bool, after: bool):
if ssBefore != 0 and before:
allure.attach(beforeSS, "<Step Before>", attachment_type=allure.attachment_type.PNG)
if after:
afterSS: bytes = seleniumHelper.BROSWER_screenshot()
allure.attach(afterSS, "<Step After>", attachment_type=allure.attachment_type.PNG)
if assertError:
step_name = step_name + '\t[E]'
# endregion
with allure.step(step_name):
try:
if ssBefore != 0:
beforeSS: bytes = seleniumHelper.BROSWER_screenshot()
if act == EngineActIdEnum.sub_steps:
for iSubAction in stepV.get('actions'):
self.runStep(iSubAction)
elif act == EngineActIdEnum.SYS_sleep:
time.sleep(stepV.get('time'))
pass
elif act == EngineActIdEnum.ENGINE_setVar:
varName = stepV.get("name")
# varValue = self._getSysStrValue(stepV.get('value'))
varValue = stepV.get('value')
with allure.step(f'{varName} = {varValue}'):
self.__vars[varName] = varValue
elif act == EngineActIdEnum.BROWSER_getUrl:
seleniumHelper.BROWSER_getUrl(stepV.get('url'), stepV.get('force'))
elif act == EngineActIdEnum.BROWSER_switch_to_window:
seleniumHelper.BROWSER_switch_to_window(stepV.get('idx'))
elif act == EngineActIdEnum.BROWSER_wait:
self._waitBrowser(stepV)
elif act == EngineActIdEnum.BROWSER_screenshot:
png = seleniumHelper.BROSWER_screenshot()
allure.attach(png, "窗口截图", attachment_type=allure.attachment_type.PNG)
elif act == EngineActIdEnum.BROWSER_close:
seleniumHelper.BROWSER_close()
elif act == EngineActIdEnum.BROWSER_maximize_window:
seleniumHelper.BROWSER_maximize_window()
elif act == EngineActIdEnum.BROWSER_set_window_size:
seleniumHelper.BROWSER_set_window_size(stepV.get('width', 1280), stepV.get('height', 800))
elif act == EngineActIdEnum.BROWSER_SET_implicitly_wait:
seleniumHelper.BROWSER_SET_implicitly_wait(stepV.get('time'))
elif act == EngineActIdEnum.WEB_wait:
self._waitElem(stepV)
elif act == EngineActIdEnum.WEB_assertAttr:
elem = self.findElem(stepV)
elemValue: str = elem.get_attribute(stepV.get('attribute'))
pos = elemValue.find(stepV.get('value'))
with allure.step(f":内容发现位置:{pos}"):
assert pos > -1
elif act == EngineActIdEnum.WEB_sendKeys:
elem = self.findElem(stepV)
v = stepV.get('value')
seleniumHelper.WEB_scrollIntoView(elem)
if stepV.get('clear'):
with allure.step('清空已有内容'):
elem.clear()
with allure.step(f"输入值 : {v}"):
elem.send_keys(v)
elif act == EngineActIdEnum.WEB_click:
elem = self.findElem(stepV)
seleniumHelper.WEB_scrollIntoView(elem)
with allure.step("单击元素"):
elem.click()
elif act == EngineActIdEnum.WEB_select:
selectBy: str = stepV.get('selectBy')
selectContent: str = stepV.get('selectContent')
elem = self.findElem(stepV)
with allure.step(f"点选Item by={selectBy} , content={selectContent}"):
seleniumHelper.WEB_select(elem, selectBy, selectContent)
elif act == EngineActIdEnum.WEB_switch_to_frame:
seleniumHelper.WEB_switch_to_frame(stepV.get('by'), stepV.get('path'))
pass
elif act == EngineActIdEnum.WEB_switch_to_default_content:
seleniumHelper.WEB_switch_to_default_content()
pass
else:
raise NotImplementedError()
# except NotImplementedError as e:
# allure.attach(json.dump(value), "doc", attachment_type=allure.attachment_type.JSON)
# raise e
except Exception as e:
if isinstance(e, NotImplementedError):
allure.attach(json.dump(stepV), "doc", attachment_type=allure.attachment_type.JSON)
raise e
if not assertError:
_saveScreenshot(beforeSS, ssBefore != 0, ssAfter != 0)
if errSkip == 0:
msg = ErrSkipMsgEnum.Msg100
with allure.step(msg):
# pytest.fail(str(e))
raise Exception(msg, err=e, step=stepV)
elif errSkip == 1:
msg = ErrSkipMsgEnum.Msg101
with allure.step(msg):
pass
else:
msg = ErrSkipMsgEnum.Msg102
raise Exception(msg, err=e, step=stepV)
else:
with allure.step(ErrSkipMsgEnum.Msg000):
pass
else:
if assertError:
_saveScreenshot(beforeSS, ssBefore != 0, ssAfter != 0)
if errSkip == 0:
msg = ErrSkipMsgEnum.Msg010
with allure.step(msg):
raise Exception(msg, step=stepV)
elif errSkip == 1:
msg = ErrSkipMsgEnum.Msg011
with allure.step(msg):
pass
else:
msg = ErrSkipMsgEnum.Msg012
with allure.step(msg):
raise Exception(msg, step=stepV)
else:
_saveScreenshot(beforeSS, ssBefore == 1, ssAfter == 1)
repeat = options.get('repeat', 1)
if repeat > 1:
iRepeat = 1
# with allure.step(stepNameV + f'\t重复{repeat}次'):
while iRepeat <= repeat:
_runOneStep(stepNameV, iRepeat, repeat)
iRepeat += 1
else:
_runOneStep(stepNameV, 1, 1)
def findElem(self, act: dict) -> WebElement:
by: str = act.get('by') or By.XPATH
path: str = act.get('path')
waitOptions: dict = act.get('waitOptions')
if waitOptions:
with allure.step(f"[等待]定位元素 : by={by} , path={path} , wait={waitOptions.get('method')}"):
elem = self.__seleniumHelper.WEB_waitElem(waitOptions, by, path)
else:
with allure.step(f"定位元素 : by={by} , path={path}"):
elem = self.__seleniumHelper.elemFind(by, path)
return elem
def _waitElem(self, act: dict) -> WebElement:
by: str = act.get('by') or By.XPATH
path: str = act.get('path')
waitOptions: dict = act.get('waitOptions', {"timeout": 10, "until": "until", "frequency": 0.5, })
elem = self.__seleniumHelper.WEB_waitElem(waitOptions, by, path)
return elem
def _waitBrowser(self, act: dict) -> WebElement:
method: str = act.get("method")
timeout: float = act.get("timeout") or 10.0
until: bool = act.get('until', "until") == "until"
frequency: float = act.get('frequency') or 0.5
args: list = act.get('args', [])
if method == 'current_url_changes':
method = 'url_changes'
args = [self.__seleniumHelper.BROWSER_current_url()]
elem = self.__seleniumHelper.BROWSER_wait(method, timeout, frequency, until, args)
return elem
def _getFakeValue(self, old: str) -> str:
def _fakerRepl(matched: re):
funcName = matched.groups()[0]
func = getattr(self.fake, funcName)
argsStr = matched.groups()[1]
result = None
if argsStr:
args = json.loads(argsStr)
if type(args) == list:
result = func(*args)
elif type(args) == dict:
result = func(**args)
else:
result = func()
return str(result)
return self.__class__.__fakerReg.sub(_fakerRepl, old)
def _getSysStrValue(self, old: str, node: dict = None, fake: bool = True):
result = old
def _argSub(matched: re.Match):
value = node.get(matched.groups()[0])
return str(value)
result = self.__class__.__argsReg.sub(_argSub, result)
# 替换VARS
def _varSub(matched: re.Match):
return str(self.__vars.get(matched.groups()[0]))
result = self.__class__.__varsReg.sub(_varSub, result)
# 替换键盘Keys
def _keysSub(matched: re.Match):
return getattr(Keys, matched.groups()[0])
result = self.__class__.__keysReg.sub(_keysSub, result)
# 替换FAKER
if fake:
result = self._getFakeValue(result)
return result
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。