提交 6c258a0a authored 作者: 陈泽健's avatar 陈泽健

兰州中石化项目输出议题申报流程的JSON数据,调试自动化运行。

上级 7a9d4d6f
/预定系统/log
/预定系统/reports/
/预定系统/reports/imgs/
/预定系统/reports/imgs/
\ No newline at end of file
# 一、环境运行
- python3.10
- 需要手动安装库:pip install hytest
- 若切换电脑环境后需要输入指令: 将本地库导入到外部库中。
- # 找到 site-packages 路径
python -c "import site; print(site.getsitepackages())"
- # 复制你的库到其中
cp -r hytest /path/to/site-packages/
# 二、项目说明
- 模块以文件夹形式
......
from .common import signal,GSTORE,INFO,STEP,CHECK_POINT,LOG_IMG,SELENIUM_LOG_SCREEN
import os, sys
sys.path.append(os.getcwd())
\ No newline at end of file
supportedLang = ['zh','en']
class l:
LANGS = {
'zh' : 0,
'en' : 1,
}
n = None # 当前使用的语言编号
import sys
if '--lang' in sys.argv:
try:
idx = sys.argv.index('--lang')
lang = sys.argv[idx+1]
if lang in supportedLang:
l.n = l.LANGS[lang]
except:...
if l.n is None:
import locale
if 'zh_CN' in locale.getdefaultlocale():
l.n = l.LANGS['zh']
else :
l.n = l.LANGS['en']
LANG_TABLE = {
'测试报告' : ['测试报告','Test Report'],
'指定测试报告标题' : ['指定测试报告标题','set test report title'],
}
# 返回当前使用语言字符串
def ls(lookupStr):
return LANG_TABLE[lookupStr][l.n]
class Settings:
auto_open_report = True
report_title = '' # 命令行参数会设置,并且有缺省值
report_url_prefix = '' # 命令行参数会设置,并且有缺省值
\ No newline at end of file
from .utils.signal import signal
from .utils.runner import Runner
from datetime import datetime
from .cfg import l
class _GlobalStore:
def __getitem__(self, key, default=None):
if hasattr(self, key):
return getattr(self, key)
else:
return default
def __setitem__(self,key,value):
setattr(self, key, value )
get = __getitem__
# used for storing global shared data
GSTORE = _GlobalStore()
def INFO(info):
"""
print information in log and report.
This will not show in terminal window.
Parameters
----------
info : object to print
"""
signal.info(f'{info}')
def STEP(stepNo:int,desc:str):
"""
print information about test steps in log and report .
This will not show in terminal window.
Parameters
----------
stepNo : step number
desc : description about this step
"""
signal.step(stepNo,desc)
def CHECK_POINT(desc:str, condition, failStop=True, failLogScreenWebDriver = None):
"""
check point of testing.
pass or fail of this check point depends on argument condition is true or false.
it will print information about check point in log and report.
Parameters
----------
desc : check point description, like check what.
condition : usually it's a bool expression, like `a==b`,
so actually, after evaluating the expression, it's a result bool object passed in .
failStop : switch for whether continue this test cases when the condition is false
failLogScreenWebDriver : Selenium web driver object,
when you want a screenshot image of browser in test report if current check point fail.
"""
if condition:
signal.checkpoint_pass(desc)
else:
signal.checkpoint_fail(desc)
# 如果需要截屏
if failLogScreenWebDriver is not None:
SELENIUM_LOG_SCREEN(failLogScreenWebDriver)
# 记录下当前执行结果为失败
Runner.curRunningCase.execRet='fail'
Runner.curRunningCase.error=('检查点不通过','checkpoint failed')[l.n]
Runner.curRunningCase.stacktrace="\n"*3+('具体错误看测试步骤检查点','see checkpoint of case for details')[l.n]
# 如果失败停止,中止此测试用例
if failStop:
raise AssertionError()
def LOG_IMG(imgPath: str, width: str = None):
"""
add image in test report
Parameters
----------
imgPath: the path of image
width: display width of image in html, like 50% / 800px / 30em
"""
signal.log_img(imgPath, width)
def SELENIUM_LOG_SCREEN(driver, width: str = None, module_name: str = None, function_name: str = None, stepname: str = None):
"""
add screenshot image of browser into test report when using Selenium
在日志中加入selenium控制的 浏览器截屏图片
Parameters
----------
driver: selenium webdriver
width: display width of image in html, like 50% / 800px / 30em
module_name: 传入模块名称,截图会在log/imgs下创建对应目录进行存放。
function_name: 传入功能名称,截图文件放在指定目录下。
stepname: 传入步骤名称,截图以步骤名称+时间戳进行命名。
"""
# 当未指定模块名称、功能名称和步骤名称时的处理逻辑
if module_name == None and function_name == None and stepname == None:
# 使用当前时间作为文件名,确保唯一性
filename = datetime.now().strftime('%Y%m%d%H%M%S%f')
# 定义文件路径和相对于log的路径
filepath = f'log/imgs/{filename}.png'
filepath_relative_to_log = f'imgs/{filename}.png'
# 获取并保存截图
driver.get_screenshot_as_file(filepath)
# 使用signal记录截图到日志中,并指定宽度
signal.log_img(filepath_relative_to_log, width)
else:
# 当指定了模块名称、功能名称和步骤名称时的处理逻辑
filename = str(stepname)
print(filename)
# 定义文件路径和相对于log的路径,根据模块和功能名称创建目录
filepath = f'reports/imgs/{module_name}/{function_name}/{filename}.png'
filepath_relative_to_log = f'imgs/{module_name}/{function_name}/{filename}.png'
# 获取并保存截图
driver.get_screenshot_as_file(filepath)
# 使用signal记录截图到日志中,并指定宽度
signal.log_img(filepath_relative_to_log, width)
\ No newline at end of file
version= '0.8.12'
\ No newline at end of file
import re, os, traceback
from .cfg import l, Settings
import argparse
from .product import version
def tagExpressionGen(argstr):
tagRules = []
for part in argstr:
# 有单引号,是表达式
if "'" in part:
rule = re.sub(r"'.+?'", lambda m: f'tagmatch({m.group(0)})', part)
tagRules.append(f'({rule})')
# 是简单标签名
else:
rule = f"tagmatch('{part}')"
tagRules.append(f'{rule}')
return ' or '.join(tagRules)
def run():
parser = argparse.ArgumentParser()
parser.add_argument('--version', action='version', version=f'hytest v{version}',
help=("显示版本号", 'display hytest version')[l.n])
parser.add_argument('--lang', choices=['zh', 'en'],
help=("设置工具语言", 'set language')[l.n])
parser.add_argument('--new', metavar='project_dir',
help=("创建新项目目录", "create a project folder")[l.n])
parser.add_argument("case_dir", nargs='?', default='cases',
help=("用例根目录", "")[l.n])
parser.add_argument("--loglevel", metavar='Level_Number', type=int, default=3,
help=
("日志级别 0,1,2,3,4,5(数字越大,日志越详细)", "log level 0,1,2,3,4,5(bigger for more info)")[
l.n])
parser.add_argument('--auto_open_report', choices=['yes', 'no'], default='yes',
help=("测试结束不自动打开报告", "don't open report automatically after testing")[l.n])
parser.add_argument("--report_title", metavar='Report_Title',
default=['测试报告', 'Test Report'][l.n],
help=['指定测试报告标题', 'set test report title'][l.n])
parser.add_argument("--report_url_prefix", metavar='Url_Prefix',
default='',
help=['测试报告URL前缀', 'test report URL prefix'][l.n])
parser.add_argument("--test", metavar='Case_Name', action='append', default=[],
help=("用例名过滤,支持通配符", "filter by case name")[l.n])
parser.add_argument("--suite", metavar='Suite_Name', action='append', default=[],
help=("套件名过滤,支持通配符", "filter by suite name")[l.n])
parser.add_argument("--tag", metavar='Tag_Expression', action='append', default=[],
help=("标签名过滤,支持通配符", "filter by tag name")[l.n])
parser.add_argument("--tagnot", metavar='Tag_Expression', action='append', default=[],
help=("标签名反向过滤,支持通配符", "reverse filter by tag name")[l.n])
parser.add_argument("-A", "--argfile", metavar='Argument_File',
type=argparse.FileType('r', encoding='utf8'),
help=("使用参数文件", "use argument file")[l.n])
args = parser.parse_args()
# 有参数放在文件中,必须首先处理
if args.argfile:
fileArgs = [para for para in args.argfile.read().replace('\n', ' ').split() if para]
print(fileArgs)
args = parser.parse_args(fileArgs, args)
# 看命令行中是否设置了语言
if args.lang:
l.n = l.LANGS[args.lang]
# 报告标题
Settings.report_title = args.report_title
# 测试结束后,是否自动打开测试报告
Settings.auto_open_report = True if args.auto_open_report == 'yes' else False
# 测试结束后,要显示的测试报告的url前缀,比如: run.bat --report_url_prefix http://127.0.0.1
# 可以本机启动http服务,比如:python -m http.server 80 --directory log
# 方便 jenkins上查看
Settings.report_url_prefix = args.report_url_prefix
# 创建项目目录
if args.new:
projDir = args.new
if os.path.exists(projDir):
print(f'{projDir} already exists!')
exit(2)
os.makedirs(f'{projDir}/cases')
with open(f'{projDir}/cases/case1.py', 'w', encoding='utf8') as f:
caseContent = [
'''class c1:
name = '用例名称 - 0001'
# 测试用例步骤
def teststeps(self):
ret = 1
''',
'''class c1:
name = 'test case name - 0001'
# test case steps
def teststeps(self):...''',
][l.n]
f.write(caseContent)
exit()
if not os.path.exists(args.case_dir):
print(
f' {args.case_dir} {("目录不存在,工作目录为:", "folder not exists, workding dir is:")[l.n]} {os.getcwd()}')
exit(2) # '2' stands for no test cases to run
if not os.path.isdir(args.case_dir):
print(f' {args.case_dir} {("不是目录,工作目录为:", "is not a folder, workding dir is:")[l.n]} {os.getcwd()}')
exit(2) # '2' stands for no test cases to run
# 同时执行log里面的初始化日志模块,注册signal的代码
from .utils.log import LogLevel
from .utils.runner import Collector, Runner
LogLevel.level = args.loglevel
# print('loglevel',LogLevel.level)
# --tag "'冒烟测试' and 'UITest' or (not '快速' and 'fast')" --tag 白月 --tag 黑羽
tag_include_expr = tagExpressionGen(args.tag)
tag_exclude_expr = tagExpressionGen(args.tagnot)
# print(tag_include_expr)
# print(tag_exclude_expr)
print(f'''
* * * * * * * * * * * * * * * * * *
* hytest {version} www.byhy.net *
* * * * * * * * * * * * * * * * * *
'''
)
os.makedirs('log/imgs', exist_ok=True)
try:
Collector.run(
casedir=args.case_dir,
suitename_filters=args.suite,
casename_filters=args.test,
tag_include_expr=tag_include_expr,
tag_exclude_expr=tag_exclude_expr,
)
except:
print(traceback.format_exc())
print(('\n\n!! 搜集用例时发现代码错误,异常终止 !!\n\n',
'\n\n!! Collect Test Cases Exception Aborted !!\n\n')[l.n])
exit(3)
# 0 表示执行成功 , 1 表示有错误 , 2 表示没有可以执行的用例
result = Runner.run()
# keep 10 report files at most
ReportFileNumber = 10
import glob
reportFiles = glob.glob('./log/report_*.html')
fileNum = len(reportFiles)
if fileNum >= ReportFileNumber:
reportFiles.sort()
for rf in reportFiles[:fileNum - ReportFileNumber]:
try:
os.remove(rf)
except:
...
return result
if __name__ == '__main__':
exit(run())
\ No newline at end of file
此差异已折叠。
body {
font-family: consolas, Verdana, sans-serif;
font-size: 1.2em;
color: #696e71;
display: grid;
grid-template-columns: 1fr 5rem;
}
.main_section {
width: 90%;
margin: 0 auto;
}
#float_menu{
position:fixed;
top:0;
right:0;
text-align: center;
}
#float_menu .menu-item {
cursor: pointer;
padding: .5em;
margin: .5em 0;
color: #c08580;
background-color: #f8f0ef;
font-size: 1.2em;
}
.result{
display: flex;
}
.result_table{
border-collapse: collapse;
border: 1px solid #f0e0e5;
width: 30em;
text-align: center;
font-size: 1.0em;
}
.result_table td{
border: 1px solid #f0e0e5;
padding: .3em;
}
.result_barchart{
width: 30em;
margin: 0 5em 0 5em;
}
.barchar_item{
margin: 2.5rem 0;
}
.barchart_barbox {
margin: 0.5em 0;
width: 100%;
background-color: #fff;
border: 1px solid #86c2dd;
border-radius: .2em;
}
.barchart_bar {
text-align: right;
height: 1.2rem;
}
.h3_button {
margin: 1.5em;
cursor: pointer;
color: #03a9f4;
}
.info
{
white-space:pre-wrap;
margin: .8em 1.5em;
}
.error-info
{
color: #a64747
}
.suite_dir {
margin: 1em .2em;
padding: .3em;
/* background-color: #dfeff6; */
border: 1px solid #bcd8e4;
}
.suite_file {
margin: 1em .2em;
padding: .3em;
border: 1px solid #bcd8e4;
}
.case {
margin: 1em .2em;
/* padding: .3em; */
border: 1px solid #e7d4d4;
}
.case_class_path{
margin: 0em 1em;
}
.folder_header {
padding: .2em .7em;
background-color: #fffaf9;
cursor: pointer;
}
.setup{
margin: .2em;
/* padding: .3em; */
/* border: 1px solid #e7d4d4; */
}
.teardown{
margin: .2em;
/* padding: .3em;*/
/* border: 1px solid #e7d4d4; */
}
.test_steps{
margin: .2em;
padding: .3em;
/* border: 1px solid #e7d4d4; */
}
.label {
display: inline-block;
padding: .1em .5em;
font-size: .88em;
letter-spacing: 1px;
white-space: nowrap;
color: #0d6ebc;
border-radius: .2em;
min-width: 5em;
margin-right: 2em;
font-family: consolas;
}
/* .suite_setup .label{
color: #219e26 ;
}
.suite_teardown .label{
color: #219e26;
} */
/* .case.pass .casename{
color: #329132 ;
} */
.case.pass .caselabel{
color: white;
background-color: #3b9e3f;
}
/* .case.fail .casename{
color: #a64747;
} */
.case.fail .caselabel{
color: white;
background-color: #a64747;
}
/* .case.abort .casename{
color: #953ab7;
} */
.case.abort .caselabel{
color: white;
background-color: #9c27b0;
}
.case_step {
margin: .8em;
}
.checkpoint_pass {
margin: .8em;
}
.checkpoint_fail {
margin: .8em;
}
.case_step .tag{
color: #2196f3;;
margin: .3em 1em .3em 0;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_pass .tag{
color: #009806;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.checkpoint_fail .tag{
color: #9c2020;
margin:.3em 1em .3em .5em;
padding: .1em .3em;
font-size: .92em;
}
.screenshot {
border: 1px solid #86c2dd;
}
.executetime {
float: right;
}
/* 模态框内容 */
.modal-content {
margin: auto;
display: block;
width: 95%;
max-width: 700px;
max-height: 80vh; /* 设置最大高度为视口高度的80% */
object-fit: contain; /* 保持图片的宽高比 */
zoom: 3;
}
/* 模态框 */
.modal {
display: none; /* 隐藏 */
position: fixed; /* 固定位置 */
z-index: 1; /* 坐在顶部 */
padding-top: 40px; /* 在图片上方添加一些内边距 */
left: 0;
top: 0;
width: 100%; /* 宽度 */
height: 100%; /* 高度 */
overflow: auto; /* 启用滚动 */
background-color: rgb(0,0,0); /* 背景颜色 */
background-color: rgba(0,0,0,0.9); /* 黑色背景半透明 */
}
/* 关闭按钮 */
.close {
position: absolute; /* 定义元素的定位方式为绝对定位 */
top: 10px; /* 距离最近的已定位祖先元素顶部15像素 */
right: 30px; /* 距离最近的已定位祖先元素右侧35像素 */
color: #f1f1f1; /* 文本颜色为浅灰色 */
font-size: 15px; /* 字体大小为40像素 */
font-weight: bold; /* 字体加粗 */
transition: 0.3s; /* 过渡效果,0.3秒内完成 */
}
.close:hover,
.close:focus {
color: #bbb;
text-decoration: none;
cursor: pointer;
}
var FOLDER_ALL_CASES = false; // 是否为精简模式的标记
var ERROR_INFOS = []; // 错误信息列表
var current_error_idx = -1;
// 页面加载后执行的函数
window.addEventListener("load", function(){
// 所有 .folder_header 添加点击事件处理
let folderHeaderEles = document.querySelectorAll(".folder_header");
folderHeaderEles.forEach(function(ele) {
ele.addEventListener("click", function(event) {
let fb = event.target.closest('.folder_header').nextElementSibling;
fb.style.display = fb.style.display === 'none' ? 'block' : 'none';
});
});
// 找到所有的错误信息对象
ERROR_INFOS = document.querySelectorAll(".error-info");
// 获取所有图片元素
let images = document.querySelectorAll('.modal-image');
// 获取模态框元素
let modal = document.getElementById("imageModal");
// 获取模态框中的图片元素
let modalImg = document.getElementById("img01");
// 获取关闭按钮元素
let span = document.getElementsByClassName("close")[0];
// 为每个图片添加点击事件监听器
images.forEach(function(img) {
img.addEventListener("click", function() {
modal.style.display = "block"; // 显示模态框
modalImg.src = this.src; // 设置模态框中的图片为点击的图片
});
});
// 当点击关闭按钮时,隐藏模态框
span.onclick = function() {
modal.style.display = "none";
};
// 当点击模态框外区域时,隐藏模态框
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
};
});
function toggle_folder_all_cases(){
let eles = document.querySelectorAll(".folder_body");
FOLDER_ALL_CASES = !FOLDER_ALL_CASES;
document.getElementById('display_mode').innerHTML = FOLDER_ALL_CASES ? "Detail" : "Summary";
for (const ele of eles){
ele.style.display = FOLDER_ALL_CASES ? "none" : "block";
}
}
function previous_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx -= 1;
if (current_error_idx < 0)
current_error_idx = 0;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
function next_error(){
// 查找错误必须是详细模式
if (FOLDER_ALL_CASES)
toggle_folder_all_cases()
current_error_idx += 1;
if (current_error_idx > ERROR_INFOS.length - 1)
current_error_idx = ERROR_INFOS.length - 1;
let error = ERROR_INFOS[current_error_idx];
error.scrollIntoView({behavior: "smooth", block: "center", inline: "start"});
}
此差异已折叠。
class Signal:
_clients = []
_curMethodName = None
def register(self, client):
if isinstance(client,list):
self._clients += client
else:
self._clients.append(client)
def _broadcast(self,*arg,**kargs):
for logger in self._clients:
method = getattr(logger,self._curMethodName,None)
if method:
method(*arg,**kargs)
def __getattr__(self, attr):
self._curMethodName = attr
return self._broadcast
signal = Signal()
......@@ -76,11 +76,11 @@ def browser_init(login_type):
# chromedriver下载地址:https://googlechromelabs.github.io/chrome-for-testing/
# 自动化运行服务器的chromedriver路径:
# 拯救者电脑
service = Service(r'C:\Users\29194\AppData\Local\Programs\Python\Python310\Scripts\chromedriver.exe')
# service = Service(r'C:\Users\29194\AppData\Local\Programs\Python\Python310\Scripts\chromedriver.exe')
# EDY电脑
# service = Service(r'C:\Program Files\Python310\Scripts\chromedriver.exe')
# 云电脑
# service = Service(r'E:\Python\Scripts\chromedriver.exe')
service = Service(r'E:\Python\Scripts\chromedriver.exe')
# 尝试创建WebDriver实例并执行初始化操作
try:
# 创建WebDriver实例
......
......@@ -311,4 +311,6 @@
89. 2025-06-03:
- 兰州中石化项目输出会议申报模块的JSON数据,调试自动化运行,补充会议申报模块的JSON数据。
90. 2025-06-05:
- 兰州中石化项目输出角色权限组的部分JSON数据,调试自动化运行。排查展厅自动化失败问题,处理chrome版本升级后,chromedriver版本没对上问题。
\ No newline at end of file
- 兰州中石化项目输出角色权限组的部分JSON数据,调试自动化运行。排查展厅自动化失败问题,处理chrome版本升级后,chromedriver版本没对上问题。
91. 2025-06-10:
- 兰州中石化项目输出议题申报流程的JSON数据,调试自动化运行。
\ No newline at end of file
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论