At July 26 A.

This commit is contained in:
zhaoyafan 2022-07-26 23:47:41 +08:00
parent c5ba8dc479
commit eace83ef37
4 changed files with 301 additions and 336 deletions

View File

@ -26,7 +26,7 @@ HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
# Use an external stylesheet.
# See the Template_mixin class for more customizable options
runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
runner.STYLES_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
# run the test
runner.run(my_test_suite)
@ -62,7 +62,7 @@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import os, re, sys, io, time, math, datetime, unittest, logging
import os, re, sys, io, time, datetime, json, unittest, logging
from xml.sax import saxutils
_global_dict = {}
@ -132,7 +132,7 @@ class Template_mixin(object):
|<html> |
| <head> |
| |
| STYLESHEET |
| STYLES |
| +----------------+ |
| | | |
| +----------------+ |
@ -141,7 +141,7 @@ class Template_mixin(object):
| |
| <body> |
| |
| HEADING |
| HEADER |
| +----------------+ |
| | | |
| +----------------+ |
@ -151,7 +151,7 @@ class Template_mixin(object):
| | | |
| +----------------+ |
| |
| ENDING |
| FOOTER |
| +----------------+ |
| | | |
| +----------------+ |
@ -173,7 +173,7 @@ class Template_mixin(object):
# ------------------------------------------------------------------------
# 网页模板开始
# 网页模板,变量列表 title, generator, stylesheet, heading, report, ending
# 网页模板,变量列表 title, generator, styles, header, report, footer
HTML_TMPL = r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
@ -181,12 +181,12 @@ class Template_mixin(object):
<meta name="generator" content="%(generator)s"/>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://www.fanscloud.net/res/bootstrap/3.0.3/css/bootstrap.min.css">
<script src="https://www.fanscloud.net/res/jquery/2.0.0/jquery.min.js"></script>
<script src="https://www.fanscloud.net/res/bootstrap/3.0.3/js/bootstrap.min.js"></script>
<script src="https://img.hcharts.cn/highcharts/highcharts.js"></script>
<script src="https://img.hcharts.cn/highcharts/modules/exporting.js"></script>
%(stylesheet)s
%(styles)s
</head>
<body >
<script language="javascript" type="text/javascript">
@ -209,7 +209,7 @@ class Template_mixin(object):
p_attribute.eq(4).addClass("failCollection");
p_attribute.eq(5).addClass("errorCollection");
// 打开截图放大点击任何位置可以关闭图片 -- Gelomen
// 打开截图放大点击任何位置可以关闭图片
$(".screenshot").click(function(){
var img = $(this).attr("img");
$('.pic_show img').attr('src', img);
@ -227,39 +227,33 @@ class Template_mixin(object):
$('.pic_show').fadeOut(200)
});
var resize_action = function(){
// 改变窗口大小时自动改变图表边距
var browserWidth = $(window).width();
var margin_left = browserWidth/2 - 450;
if(margin_left <= 240){
$("#container").css("margin", "auto");
var margin_left = browserWidth - 360 - 450 - 550 - 40;
if(margin_left <= 0){
$("#container").css("width", "100%%");
$("#container_extend").css("width", "100%%");
$("#testinfo").css("width", "100%%");
$("#container").css("margin-left", "0px");
}else {
$("#container").css("margin-left", margin_left + "px");
$("#container").css("width", "450px");
$("#container_extend").css("width", "550px");
$("#testinfo").css("width", "25%%");
$("#container").css("margin-left", (margin_left - 1) + "px");
}
}
resize_action();
$(window).resize(function(){
// 改变窗口大小时自动改变图片与顶部的距离 -- Gelomen
// 改变窗口大小时自动改变图片与顶部的距离
var browserHeight = $(window).height();
var pic_boxHeight = $(".pic_box").height();
var top = (browserHeight - pic_boxHeight)/2;
$('.pic_box').css("margin-top", top + "px");
// 改变窗口大小时自动改变饼图的边距 -- Gelomen
var browserWidth = $(window).width();
var margin_left = browserWidth/2 - 450;
if(margin_left <= 0){
$("#container").css("margin", "auto");
$("#container").css("float", "left");
$("#container").css("width", "100%%");
$("#testinfo").css("width", "100%%");
}else {
$("#container").css("margin-left", margin_left + "px");
$("#container").css("float", "right");
$("#container").css("width", "450px");
$("#testinfo").css("width", "30%%");
}
resize_action();
});
// 距离顶部超过浏览器窗口一屏时回到顶部按钮才出现 -- Gelomen
// 超过浏览器高度时回到顶部按钮出现
$(window).scroll(function(){
var browserHeight = $(window).height();
var top = $(window).scrollTop();
@ -269,12 +263,59 @@ class Template_mixin(object):
$("#toTop").css("display", "none")
}
})
// 增加回到顶部过程的动画以看上去不会那么生硬 -- Gelomen
// 增加回到顶部过程动画
$("#toTop").click(function() {
$("html,body").animate({"scrollTop":0}, 700)
$("html,body").animate({"scrollTop":0}, 500)
})
// 增加饼状图 -- Gelomen
// 增加条形图
$('#container_extend').highcharts({
chart: {
type: 'bar'
},
credits: {
enabled: false
},
navigation: {
buttonOptions: {
enabled: false
}
},
title: {
text: '用例集合情况'
},
xAxis: {
categories: %(casesets)s
},
yAxis: {
min: 0,
title: {
text: '用例数量'
},
reversedStacks: false
},
legend: {
reversed: false
},
plotOptions: {
series: {
stacking: 'normal'
}
},
series: [{
name: '通过',
color: '#64BB64',
data: %(casesets_passed)s
}, {
name: '失败',
color: '#F16D7E',
data: %(casesets_failed)s
}, {
name: '错误',
color: '#FDC68C',
data: %(casesets_errors)s
}]
})
// 增加饼状图
$('#container').highcharts({
chart: {
plotBackgroundColor: null,
@ -325,7 +366,7 @@ class Template_mixin(object):
innerSize: '80%%',
name: '比例',
data: [
['通过', %(Pass)s], ['失败', %(fail)s], ['错误', %(error)s]
['通过', %(passed)s], ['失败', %(failed)s], ['错误', %(errors)s]
]
}]
}, function(c) {
@ -355,8 +396,8 @@ output_list = Array();
/*level 调整增加只显示通过用例的分类 --Findyou / 修复筛选显示bug --Gelomen
0:Summary //all hiddenRow
1:Failed //pt&et hiddenRow, ft none
2:Pass //pt none, ft&et hiddenRow
3:Error //pt&ft hiddenRow, et none
2:Passed //pt none, ft&et hiddenRow
3:Errors //pt&ft hiddenRow, et none
4:All //all none
*/
function showCase(level) {
@ -405,9 +446,8 @@ function showCase(level) {
}
}
//加入详细切换文字变化 --Findyou
//加入详细切换文字变化
detail_class=document.getElementsByClassName('detail');
//console.log(detail_class.length)
if (level == 4) {
for (var i = 0; i < detail_class.length; i++){
detail_class[i].innerHTML="收起"
@ -424,7 +464,7 @@ function showClassDetail(cid, count) {
var id_list = Array(count);
var toHide = 1;
for (var i = 0; i < count; i++) {
//ID修改 下划线 -Findyou
//ID修改.为_
tid0 = 't' + cid.substr(1) + '_' + (i+1);
tid = 'f' + tid0;
tr = document.getElementById(tid);
@ -462,9 +502,9 @@ function html_escape(s) {
return s;
}
</script>
%(heading)s
%(header)s
%(report)s
%(ending)s
%(footer)s
<div style='width:auto;height:24px;line-height:24px;border:1px solid #e3e3e3;text-align:center'><a href='#' style='color:#505050'></a></div>
</body>
</html>
@ -478,7 +518,7 @@ function html_escape(s) {
# alternatively use a <link> for external style sheet, e.g.
# <link rel="stylesheet" href="$url" type="text/css">
STYLESHEET_TMPL = """
STYLES_TMPL = """
<style type="text/css" media="screen">
body { font-family: Microsoft YaHei;padding: 20px; font-size: 100%; }
table { font-size: 100%; }
@ -486,8 +526,8 @@ function html_escape(s) {
vertical-align: middle;
}
/* -- heading ---------------------------------------------------------------------- */
.heading .description, .attribute {
/* -- header ---------------------------------------------------------------------- */
.header .description, .attribute {
clear: both;
}
@ -543,20 +583,29 @@ function html_escape(s) {
}
.pic_box img{
max-width: 100%;
max-height: 100%;
width: auto;
height: 100%;
height: auto;
-moz-box-shadow: 0px 0px 20px 0px #000;
-webkit-box-shadow: 0px 0px 20px 0px #000;
box-shadow: 0px 0px 20px 0px #000;
}
/* --- 饼状图div样式 -- Gelomen --- */
/* --- 饼状图样式 */
#container {
max-width: 100%;
width: 450px;
height: 300px;
float: right;
height: 350px;
float: left;
}
#container_extend {
max-width: 100%;
width: 550px;
height: 400px;
float: left;
}
/* -- report ------------------------------------------------------------------------ */
#total_row { font-weight: bold; }
@ -575,25 +624,26 @@ function html_escape(s) {
# ------------------------------------------------------------------------
# 头部信息开始
# 添加显示截图和统计图div变量列表 title, parameters, description
HEADING_TMPL = """
HEADER_TMPL = """
<div class='pic_looper'></div>
<div class='pic_show'>
<div class='pic_box'>
<img src=''/>
</div>
</div>
<div class='heading'>
<div id="testinfo" style="max-width:515px ;width: auto; float: left;">
<h1 style="font-family: Microsoft YaHei">%(title)s</h1>
<div class='header'>
<div id="testinfo" style="max-width:360px ;width: auto; float: left;">
<h1 style="margin: 5px 0px 10px 0px; font-family: Microsoft YaHei;">%(title)s</h1>
%(parameters)s
<p class='description'>%(description)s</p>
</div>
<div id="container"></div>
<div id="container_extend"></div>
</div>
"""
# 测试信息模板,变量列表 name, value
HEADING_ATTRIBUTE_TMPL = """
HEADER_ATTRIBUTE_TMPL = """
<p class='attribute'><strong>%(name)s : </strong> %(value)s</p>
"""
# 头部信息结束
@ -601,15 +651,15 @@ function html_escape(s) {
# ------------------------------------------------------------------------
# 报告模板开始
# 变量列表 test_list, count, Pass, fail, error ,passrate
# 变量列表 test_list, counts, passed, failed, errors ,passrate
REPORT_TMPL = """
<div style="width: auto; clear: both;">
<p id='show_detail_line'>
<a class="btn btn-primary" href='javascript:showCase(0)'>概要 %(passrate)s</a>
<a class="btn btn-success" href='javascript:showCase(2)'>通过 %(Pass)s</a>
<a class="btn btn-danger" href='javascript:showCase(1)'>失败 %(fail)s</a>
<a class="btn btn-warning" href='javascript:showCase(3)'>错误 %(error)s</a>
<a class="btn btn-info" href='javascript:showCase(4)'>全部 %(count)s</a>
<a class="btn btn-success" href='javascript:showCase(2)'>通过 %(passed)s</a>
<a class="btn btn-danger" href='javascript:showCase(1)'>失败 %(failed)s</a>
<a class="btn btn-warning" href='javascript:showCase(3)'>错误 %(errors)s</a>
<a class="btn btn-info" href='javascript:showCase(4)'>全部 %(counts)s</a>
</p>
</div>
<table id='result_table' class="table table-condensed table-bordered table-hover">
@ -621,7 +671,7 @@ function html_escape(s) {
<col align='right' />
<col align='right' />
<col align='right' />
<col align='right' style="width: 200px;"/>
<col align='right' style="width: 120px;"/>
</colgroup>
<tr id='header_row' class="text-center success" style="font-weight: bold;font-size: 14px;">
<td>测试用例</td>
@ -636,27 +686,27 @@ function html_escape(s) {
%(test_list)s
<tr id='total_row' class="text-center active">
<td colspan='2'>总计</td>
<td>%(count)s</td>
<td>%(Pass)s</td>
<td>%(fail)s</td>
<td>%(error)s</td>
<td>%(counts)s</td>
<td>%(passed)s</td>
<td>%(failed)s</td>
<td>%(errors)s</td>
<td>%(time_usage)s</td>
<td>通过%(passrate)s</td>
</tr>
</table>
"""
# 变量列表 style, desc, count, Pass, fail, error, cid
# 变量列表 style, desc, counts, passed, failed, errors, cid
REPORT_CLASS_TMPL = """
<tr class='%(style)s warning'>
<td>%(name)s</td>
<td>%(doc)s</td>
<td class="text-center">%(count)s</td>
<td class="text-center">%(Pass)s</td>
<td class="text-center">%(fail)s</td>
<td class="text-center">%(error)s</td>
<td>%(docs)s</td>
<td class="text-center">%(counts)s</td>
<td class="text-center">%(passed)s</td>
<td class="text-center">%(failed)s</td>
<td class="text-center">%(errors)s</td>
<td class="text-center">%(time_usage)s</td>
<td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(count)s)" class="detail" id='%(cid)s'>查看全部</a></td>
<td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(counts)s)" class="detail" id='%(cid)s'>查看全部</a></td>
</tr>
"""
@ -664,16 +714,19 @@ function html_escape(s) {
REPORT_TEST_WITH_OUTPUT_TMPL_1 = """
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s' style="vertical-align: middle"><div class='testcase'>%(name)s</div></td>
<td style="vertical-align: middle">%(doc)s</td>
<td style="vertical-align: middle">%(docs)s</td>
<td colspan='5' align='center'>
<button id='btn_%(tid)s' type="button" class="btn btn-xs" data-toggle="collapse" data-target='#div_%(tid)s,#div_%(tid)s_screenshot'>%(status)s</button>
<div id='div_%(tid)s' class="collapse in">
<pre style="text-align:left;font-size:12px;color:#e52000">
%(script)s
</pre>
<pre style="text-align:left;font-size:12px;color:#e52000">%(script)s</pre>
</div>
</td>
<td class="text-center" style="vertical-align: middle">
<div style='width:auto;height:auto;border:1px solid #b5b5b5;color:#202020font-weight:bold;text-align:center'>截图信息</div>
<div id='div_%(tid)s_screenshot' style="text-align: left" class="collapse in">
%(screenshot)s
</div>
</td>
<td class="text-center" style="vertical-align: middle"><div id='div_%(tid)s_screenshot' class="collapse in">浏览器<div style="color: brown;">%(browser)s</div></br>截图%(screenshot)s</div></td>
</tr>
"""
@ -681,7 +734,7 @@ function html_escape(s) {
REPORT_TEST_WITH_OUTPUT_TMPL_0 = """
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s' style="vertical-align: middle"><div class='testcase'>%(name)s</div></td>
<td style="vertical-align: middle">%(doc)s</td>
<td style="vertical-align: middle">%(docs)s</td>
<td colspan='5' align='center'>
<button id='btn_%(tid)s' type="button" class="btn btn-xs" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
<div id='div_%(tid)s' class="collapse in">
@ -696,7 +749,7 @@ function html_escape(s) {
REPORT_TEST_NO_OUTPUT_TMPL = """
<tr id='%(tid)s' class='%(Class)s'>
<td class='%(style)s' style="vertical-align: middle"><div class='testcase'>%(name)s</div></td>
<td style="vertical-align: left">%(doc)s</td>
<td style="vertical-align: left">%(docs)s</td>
<td colspan='5' align='center'><button type="button" class="btn btn-xs">%(status)s</button></td>
<td class='%(style)s' style="vertical-align: middle"></td>
</tr>
@ -706,8 +759,8 @@ function html_escape(s) {
REPORT_TEST_OUTPUT_TMPL = '%(id)s:' + "\n" + '%(output)s'
# 返回顶部按钮
ENDING_TMPL = """
<div id='ending'>&nbsp;</div>
FOOTER_TMPL = """
<div id='footer'>&nbsp;</div>
<div id="toTop" style="position:fixed;right:50px; bottom:30px; width:20px; height:20px;cursor:pointer; display: none">
<a>
<span class="glyphicon glyphicon-eject" style = "font-size:28px;color:#b0b0b0" aria-hidden="true">
@ -719,6 +772,14 @@ function html_escape(s) {
# ------------------------------------------------------------------------
def _findMark(mark='', text=''):
return re.findall('\[' + mark + '](.*?)\[/' + mark + ']' + '*?', text)
def _makeMark(mark='', cont=''):
return '[' + mark + ']' + cont +'[/' + mark + ']'
def _Color(fc=0, bc=0, bo=0, text=''):
if "PYCHARM" not in os.environ.keys():
return text
@ -906,10 +967,10 @@ class _TestResult(unittest.TestResult):
class HTMLTestRunner(Template_mixin):
# 新增 need_screenshot 参数,-1为无需截图否则需要截图
def __init__(self, stream=sys.stdout, verbosity=2, title=None, description=None, tester=None):
# 新增 errormsg 参数,-1为无需截图否则需要截图
def __init__(self, stream=None, verbosity=2, title=None, description=None, tester=None):
self.passrate = None
self.need_screenshot = 0
self.errormsg = None
self.stream = stream
self.verbosity = verbosity
self.runstime = None
@ -1004,10 +1065,8 @@ class HTMLTestRunner(Template_mixin):
rmap[cls] = []
classes.append(cls)
rmap[cls].append((n, t, o, e, s))
r = [(cls, rmap[cls]) for cls in classes]
return r
return [(cls, rmap[cls]) for cls in classes]
# 替换测试结果status为通过率 --Findyou
def getReportAttributes(self, result):
"""
Return report attributes as a list of (name, value).
@ -1017,7 +1076,9 @@ class HTMLTestRunner(Template_mixin):
duration = time.strftime('%H:%M:%S', time.gmtime((self.runetime - self.runstime)))
status = ''.join([
'总共 %s' % (result.passed_count + result.failed_count + result.errors_count),
'通过 %s' % result.passed_count, '失败 %s' % result.failed_count, '错误 %s' % result.errors_count
'通过 %s' % (result.passed_count),
'失败 %s' % (result.failed_count),
'错误 %s' % (result.errors_count)
])
if (result.passed_count + result.failed_count + result.errors_count) > 0:
self.passrate = str("%.2f%%" % (float(result.passed_count) / float(
@ -1028,12 +1089,12 @@ class HTMLTestRunner(Template_mixin):
if len(result.failedCase) > 0:
failedCase = result.failedCase
else:
failedCase = ""
failedCase = ''
if len(result.errorsCase) > 0:
errorsCase = result.errorsCase
else:
errorsCase = ""
errorsCase = ''
return [
('测试人员', self.tester),
@ -1046,149 +1107,138 @@ class HTMLTestRunner(Template_mixin):
def generateReport(self, test, result):
report_attrs = self.getReportAttributes(result)
stylesheet = self._generate_stylesheet()
heading = self._generate_heading(report_attrs)
report = self._generate_report(result)["report"]
ending = self._generate_ending()
report_count = self._generate_report(result)
output = self.HTML_TMPL % dict(
title=saxutils.escape(self.title),
generator='HTMLTestRunner',
stylesheet=stylesheet,
Pass=self._generate_report(result)["passed"],
fail=self._generate_report(result)["failed"],
error=self._generate_report(result)["errors"],
heading=heading,
report=report,
ending=ending,
styles=self._generate_styles(),
passed=report_count["passed"],
failed=report_count["failed"],
errors=report_count["errors"],
casesets=report_count["casesets"],
casesets_passed=report_count["casesets_passed"],
casesets_failed=report_count["casesets_failed"],
casesets_errors=report_count["casesets_errors"],
header=self._generate_header(report_attrs),
report=report_count["report"],
footer=self._generate_footer(),
)
self.stream.write(output.encode('utf8'))
self.stream.write(output.encode('utf-8'))
def _generate_stylesheet(self):
return self.STYLESHEET_TMPL
def _generate_styles(self):
return self.STYLES_TMPL
# 增加Tester显示 -Findyou
# 增加 失败用例合集 和 错误用例合集 的显示 -- Gelomen
def _generate_heading(self, report_attrs):
a_lines = []
def _generate_header(self, report_attrs):
line_list = []
for name, value in report_attrs:
# 如果是 失败用例 或 错误用例合集,则不进行转义 -- Gelomen
if name == "失败用例":
match name:
case '失败用例':
if value == "":
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value=value,
)
line = self.HEADER_ATTRIBUTE_TMPL % dict(name=name, value=value)
else:
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value="<a class='showDetail' data-toggle='collapse' href='#failedCaseOl' style='text-decoration: none;'>点击查看</a>"
"<ol id='failedCaseOl' class='collapse' style='float: left; font-family: Menlo,Monaco,Consolas,monospace;'>" + value + "</ol>",
line = self.HEADER_ATTRIBUTE_TMPL % dict(name=name, value="<a class='showDetail' data-toggle='collapse' href='#failedCaseOl' style='text-decoration: none;'>点击查看</a>"
"<ol id='failedCaseOl' class='collapse' style='float: left; font-family: Menlo,Monaco,Consolas,monospace;'>" + value + "</ol>"
)
elif name == "错误用例":
case '错误用例':
if value == "":
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value=value,
)
line = self.HEADER_ATTRIBUTE_TMPL % dict(name=name, value=value)
else:
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=name,
value="<a class='showDetail' data-toggle='collapse' href='#errorsCaseOl' style='text-decoration: none;'>点击查看</a>"
"<ol id='errorsCaseOl' class='collapse' style='float: left; font-family: Menlo,Monaco,Consolas,monospace;'>" + value + "</ol>",
line = self.HEADER_ATTRIBUTE_TMPL % dict(name=name, value="<a class='showDetail' data-toggle='collapse' href='#errorsCaseOl' style='text-decoration: none;'>点击查看</a>"
"<ol id='errorsCaseOl' class='collapse' style='float: left; font-family: Menlo,Monaco,Consolas,monospace;'>" + value + "</ol>"
)
else:
line = self.HEADING_ATTRIBUTE_TMPL % dict(
name=saxutils.escape(name),
value=saxutils.escape(value),
)
a_lines.append(line)
heading = self.HEADING_TMPL % dict(
case _:
line = self.HEADER_ATTRIBUTE_TMPL % dict(name=saxutils.escape(name), value=saxutils.escape(value))
line_list.append(line)
return self.HEADER_TMPL % dict(
title=saxutils.escape(self.title),
parameters=''.join(a_lines),
parameters=''.join(line_list),
description=saxutils.escape(self.description),
tester=saxutils.escape(self.tester),
)
return heading
# 生成报告 --Findyou添加注释
# 生成报告
def _generate_report(self, result):
rows = []
sortedResult = self.sortResult(result.result)
# 所有用例统计耗时初始化
sum_ns = 0
dura_caseset = 0
for cid, (cls, cls_results) in enumerate(sortedResult):
# subtotal for a class
np = nf = ne = ns = 0
for n, t, o, e, s in cls_results:
if n == 0:
# 遍历每条用例
match n:
case 0:
np += 1
elif n == 1:
case 1:
nf += 1
elif n == 2:
case 2:
ne += 1
ns += s # 把单个class用例文件里面的多个def用例每次的耗时相加
ns += s
# 单个用例集合耗时
ns = round(ns, 3)
sum_ns += ns # 把所有用例的每次耗时相加
# format class description
# if cls.__module__ == "__main__":
# name = cls.__name__
# else:
# name = "%s.%s" % (cls.__module__, cls.__name__)
name = cls.__name__
doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
# desc = doc and '%s - %s' % (name, doc) or name
dura_caseset += ns
row = self.REPORT_CLASS_TMPL % dict(
style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
name=name,
doc=doc,
count=np + nf + ne,
Pass=np,
fail=nf,
error=ne,
name=cls.__name__,
docs=cls.__doc__ and cls.__doc__.split("\n")[0] or '',
counts=np + nf + ne,
passed=np,
failed=nf,
errors=ne,
cid='c%s' % (cid + 1),
time_usage='%.3f' % (ns) + "" # 单个用例耗时
time_usage='%.3f' % (ns) + ""
)
rows.append(row)
for tid, (n, t, o, e, s) in enumerate(cls_results):
self._generate_report_test(rows, cid, tid, n, t, o, e)
sum_ns = round(sum_ns, 3)
rows.append(self._generate_report_test(rows, cid, tid, n, t, o, e))
# 全部用例集合耗时
dura_caseset = round(dura_caseset, 3)
report = self.REPORT_TMPL % dict(
test_list=''.join(rows),
count=str(result.passed_count + result.failed_count + result.errors_count),
Pass=str(result.passed_count),
fail=str(result.failed_count),
error=str(result.errors_count),
time_usage='%.3f' % (sum_ns) + "", # 所有用例耗时
passrate=self.passrate,
counts=str(result.passed_count + result.failed_count + result.errors_count),
passed=str(result.passed_count),
failed=str(result.failed_count),
errors=str(result.errors_count),
time_usage='%.3f' % (dura_caseset) + "",
passrate=self.passrate
)
# 获取 通过、失败 和 错误 的统计并return以用于饼图 -- Gelomen
casesets = list(result.casesort.keys())
casesets_passed = []
for value in casesets:
casesets_passed.append(result.casesort[value]["p"])
casesets_failed = []
for value in casesets:
casesets_failed.append(result.casesort[value]["f"])
casesets_errors = []
for value in casesets:
casesets_errors.append(result.casesort[value]["e"])
return {
"report": report,
"passed": str(result.passed_count),
"failed": str(result.failed_count),
"errors": str(result.errors_count)
"errors": str(result.errors_count),
"casesets": json.dumps(casesets, ensure_ascii=False),
"casesets_passed": json.dumps(casesets_passed, ensure_ascii=False),
"casesets_failed": json.dumps(casesets_failed, ensure_ascii=False),
"casesets_errors": json.dumps(casesets_errors, ensure_ascii=False)
}
def _generate_report_test(self, rows, cid, tid, n, t, o, e):
# e.g. 'pt1_1', 'ft1_1', 'et1_1'etc
has_output = bool(o or e)
# ID修改点为下划线,支持Bootstrap折叠展开特效 - Findyou
if n == 0:
# e.g. 'pt1_1', 'ft1_1', 'et1_1'
hasout = bool(o or e)
# ID修改点为下划线支持Bootstrap折叠展开特效
match n:
case 0:
tid_flag = 'p'
elif n == 1:
case 1:
tid_flag = 'f'
elif n == 2:
case 2:
tid_flag = 'e'
case _:
tid_flag = 'u'
tid = tid_flag + 't%s_%s' % (cid + 1, tid + 1)
name = t.id().split('.')[-1]
doc = t.shortDescription() or ""
# desc = doc and ('%s - %s' % (name, doc)) or name
# utf-8 支持中文 - Findyou
# o and e should be byte string because they are collected from stdout and stderr?
docs = t.shortDescription() or ""
# o and e should be byte string because they are collected from stdout and stderr.
if isinstance(o, str):
# TODO: some problem with 'string_escape': it escape \n and mess up formating
# uo = unicode(o.encode('string_escape'))
@ -1203,155 +1253,48 @@ class HTMLTestRunner(Template_mixin):
ue = e
else:
ue = e
script = self.REPORT_TEST_OUTPUT_TMPL % dict(id=tid, output=re.compile('\[[A-Za-z]+].*?\[/[A-Za-z]+][\r\n]').sub(
'', saxutils.escape(uo + ue)))
script = self.REPORT_TEST_OUTPUT_TMPL % dict(
id=tid,
output=saxutils.escape(uo + ue),
)
# 截图名字通过抛出异常存放在u通过截取字段获得截图名字 -- Gelomen
u = uo + ue
# 截图名称通过抛出异常在标准错误中
output = uo + ue
# 先判断是否需要截图
self.need_screenshot = u.find("errorImg[")
self.errormsg = output.find("TestError")
if self.need_screenshot == -1:
tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL_0 or self.REPORT_TEST_NO_OUTPUT_TMPL
row = tmpl % dict(
if self.errormsg == -1:
# 没有截图信息
template = hasout and self.REPORT_TEST_WITH_OUTPUT_TMPL_0 or self.REPORT_TEST_NO_OUTPUT_TMPL
row = template % dict(
tid=tid,
Class=(n == 0 and 'hiddenRow' or 'none'),
Class=n == 0 and 'hiddenRow' or 'none',
style=n == 2 and 'errorsCase' or (n == 1 and 'failedCase' or 'passedCase'),
name=name,
doc=doc,
docs=docs,
script=script,
status=self.STATUS[n],
)
else:
tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL_1 or self.REPORT_TEST_NO_OUTPUT_TMPL
screenshot_list = re.findall("errorImg\[(.*?)\]errorImg", u)
screenshot = ""
for i in screenshot_list:
screenshot += "</br><a class=\"screenshot\" href=\"javascript:void(0)\" img=\"image/" + i + "\">img_" + i + "</a>"
# screenshot = u[u.find('errorImg[') + 9:u.find(']errorImg')]
browser = u[u.find('browser[') + 8:u.find(']browser')]
row = tmpl % dict(
# 包含截图信息
template = hasout and self.REPORT_TEST_WITH_OUTPUT_TMPL_1 or self.REPORT_TEST_NO_OUTPUT_TMPL
screenshot_list = _findMark(mark='TestErrorImg', text=output)
screenshot = ''
for value in screenshot_list:
try:
bn = os.path.basename(value)
except:
bn = '点击查看'
screenshot += '<a style="font-family:Consolas,monospace;color:#e52000;text-decoration:underline;" class="screenshot" href="javascript:void(0)" img="' + value + '">' + bn + '</a></br>'
row = template % dict(
tid=tid,
Class=(n == 0 and 'hiddenRow' or 'none'),
Class=n == 0 and 'hiddenRow' or 'none',
style=n == 2 and 'errorsCase' or (n == 1 and 'failedCase' or 'passedCase'),
name=name,
doc=doc,
docs=docs,
script=script,
status=self.STATUS[n],
# 添加截图字段
screenshot=screenshot,
# 添加浏览器版本字段
browser=browser
screenshot=screenshot
)
rows.append(row)
return row
if not has_output:
return
def _generate_ending(self):
return self.ENDING_TMPL
# 集成创建文件夹、保存截图、获得截图名字等方法与HTMLTestReportCN交互从而实现嵌入截图 -- Gelomen
class ReportDirectory(object):
def __init__(self, path="../../result/"):
self.path = path
self.title = "Test Report"
def create_dir(self, title=None):
i = 1.0
if title is not None:
self.title = title
dir_path = self.path + self.title + "V" + str(round(i, 1))
# 判断文件夹是否存在,不存在则创建
while True:
is_dir = os.path.isdir(dir_path)
if is_dir:
i += 0.1
dir_path = self.path + self.title + "V" + str(round(i, 1))
else:
break
os.makedirs(dir_path)
# 测试报告路径
report_path = dir_path + "/" + self.title + "V" + str(round(i, 1)) + ".html"
# 将新建的 文件夹路径 和 报告路径 存入全局变量
GlobalMsg.set_value("dir_path", dir_path)
GlobalMsg.set_value("report_path", report_path)
@staticmethod
def get_screenshot(browser):
i = 1
# 通过全局变量获取文件夹路径
new_dir = GlobalMsg.get_value("dir_path")
img_dir = new_dir + "/image"
# 判断文件夹是否存在,不存在则创建
is_dir = os.path.isdir(img_dir)
if not is_dir:
os.makedirs(img_dir)
img_path = img_dir + "/" + str(i) + ".png"
# 有可能同个测试步骤出错截图名字一样导致覆盖文件所以名字存在则增加id
while True:
is_file = os.path.isfile(img_path)
if is_file:
i += 1
img_path = img_dir + "/" + str(i) + ".png"
else:
break
browser.get_screenshot_as_file(img_path)
img_name = str(i) + ".png"
browser_type = browser.capabilities["browserName"]
browser_version = browser.capabilities["browserVersion"]
browser_msg = browser_type + "(" + browser_version + ")"
print("errorImg[" + img_name + "]errorImg, browser[" + browser_msg + "]browser")
##############################################################################
# Facilities for running tests from the command line
##############################################################################
# Note: Reuse unittest.TestProgram to launch test. In the future we may
# build our own launcher to support more specific command line
# parameters like test title, CSS, etc.
class TestProgram(unittest.TestProgram):
"""
A variation of the unittest.TestProgram. Please refer to the base
class for command line parameters.
"""
def runTests(self):
# Pick HTMLTestRunner as the default test runner.
# base class's testRunner parameter is not useful because it means
# we have to instantiate HTMLTestRunner before we know self.verbosity.
if self.testRunner is None:
self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
unittest.TestProgram.runTests(self)
main = TestProgram
##############################################################################
# Executing this module from the command line
##############################################################################
if __name__ == "__main__":
main(module=None)
def _generate_footer(self):
return self.FOOTER_TMPL

View File

@ -0,0 +1,5 @@
import re
string = '...\n[TestErrorMsg]./index[/TestErrorMsg]\n[TestErrorMsg]./index[/TestErrorMsg]\n[TestErrorMsg]./index[/TestErrorMsg]\n456\nsddsfgsd: [WinError] 32432532\n[TestErrorMsg]./index[/TestErrorMsg]\n'
print(re.compile('\[[A-Za-z]+].*?\[/[A-Za-z]+][\r\n]').sub('', string))

1
run.py
View File

@ -45,6 +45,7 @@ case_suite = unittest.TestSuite()
case_suite.addTest(TestDemo('test_one1'))
case_suite.addTest(TestDemo('test_two22'))
case_suite.addTest(TestDemo('test_tre333'))
case_suite.addTest(TestDemo('test_fou444'))
case_suite.addTest(obj('test_demo1'))
case_suite.addTest(TestDemo('test_0123456789ABC'))

View File

@ -1,5 +1,8 @@
import time
import unittest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from Base.Class.Logger import *
from Base.Class.Http import *
log = Logger(name='test', level='DEBUG', ch={
@ -27,6 +30,19 @@ class TestDemo(unittest.TestCase):
time.sleep(0.025)
# assert a == 10
def test_fou444(self):
print('[TestErrorImg]./01.png[/TestErrorImg]')
print('[TestErrorImg]./2022072523501942.png[/TestErrorImg]')
print('[TestErrorImg]./2022072523501235.png[/TestErrorImg]')
raise Exception('Errors')
driver_option = Options()
driver_option.binary_location = 'C:/Users/zhaoyafan/AppData/Local/360Chrome/Chrome/Application/360chrome.exe'
browser = webdriver.Chrome(executable_path='D:/ChromeDriver_Win32_86.0.4240.22/chromedriver.exe',options=driver_option)
browser.maximize_window()
browser.get(url='https://www.fanscloud.net/')
# time.sleep(5)
raise Exception('Errors')
def test_0123456789ABC(self):
Request().http('GET',"http://more-md.fanscloud.net/iplookup", proxy="http://127.0.0.1:8888")
pass