At July 26 A.
This commit is contained in:
parent
c5ba8dc479
commit
eace83ef37
|
@ -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'> </div>
|
||||
FOOTER_TMPL = """
|
||||
<div id='footer'> </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
|
||||
|
|
5
main.py
5
main.py
|
@ -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
1
run.py
|
@ -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'))
|
||||
|
||||
|
|
16
test_case.py
16
test_case.py
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue