2023-03-13 18:49:49 +08:00
"""
这是一个用于unittest框架的TestRunner , 用它可以生成HTML测试报告 , 包含用例执行情况以
及图表 ;
使用它最简单方法是调用main方法 , 例如 :
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# import unittest
# import HTMLTestRunner
# 在这里,
# 定义你的测试代码。
# if __name__ == '__main__':
# HTMLTestRunner.main()
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
"""
import os , re , sys , io , time , datetime , platform , json , unittest , logging , shutil
from xml . sax import saxutils
class OutputRedirector ( object ) :
"""
重定向stdout和stderr以便于在测试过程中捕获输出
"""
def __init__ ( self , fp ) :
self . fp = fp
def write ( self , s ) :
self . fp . write ( s )
def writelines ( self , lines ) :
self . fp . writelines ( lines )
def flush ( self ) :
self . fp . flush ( )
stdout_redirector = OutputRedirector ( sys . stdout )
stderr_redirector = OutputRedirector ( sys . stderr )
class ReportTemplate :
"""
报告模板
"""
STATUS = {
0 : ' 通过 ' ,
1 : ' 失败 ' ,
2 : ' 错误 ' ,
}
DEFAULT_TITLE = ' 自动化测试报告 '
DEFAULT_DESCRIPTION = ' 暂无描述 '
# ------------------------------------------------------------------------
# 网页模板
# 变量列表 title, generator, styles, header, report, footer
HTML_TMPL = """
< ! DOCTYPE html PUBLIC " -//W3C//DTD XHTML 1.0 Strict//EN " " http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd " >
< html lang = " zh-CN " xmlns = " http://www.w3.org/1999/xhtml " >
< head >
< title > % ( title ) s < / title >
< 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 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 >
% ( styles ) s
< / head >
< body >
< script type = " text/javascript " >
$ ( function ( ) {
/ / 按钮颜色
$ ( " button " ) . each ( function ( ) {
let text = $ ( this ) . text ( ) ;
switch ( $ ( this ) . text ( ) ) {
case ' 通过 ' :
$ ( this ) . addClass ( " btn-success " ) ;
break ;
case ' 失败 ' :
$ ( this ) . addClass ( " btn-danger " ) ;
break ;
case ' 错误 ' :
$ ( this ) . addClass ( " btn-warning " ) ;
break ;
default :
$ ( this ) . addClass ( " btn-danger " ) ;
}
} ) ;
/ / 异常用例合集查看按钮
$ ( " .showDetail " ) . click ( function ( ) {
let expand = " 点击查看 " ;
let pickup = " 点击收起 " ;
if ( $ ( this ) . html ( ) != pickup ) { $ ( this ) . html ( pickup ) } else { $ ( this ) . html ( expand ) }
} ) ;
/ / 异常用例合集样式设置
var p_attribute = $ ( " p.attribute " ) ;
p_attribute . eq ( 4 ) . addClass ( " failedCollection " ) ;
p_attribute . eq ( 5 ) . addClass ( " errorsCollection " ) ;
/ / 打开截图点击任意位置可以关闭图片
$ ( " .screenshot " ) . click ( function ( ) {
var img = $ ( this ) . attr ( " img " ) ;
$ ( ' .pic_show img ' ) . attr ( ' src ' , img ) ;
$ ( ' .pic_looper ' ) . fadeIn ( 200 ) ;
$ ( ' .pic_show ' ) . fadeIn ( 200 ) ;
var browserHeight = $ ( window ) . height ( ) ;
var pic_boxHeight = $ ( " .pic_box " ) . height ( ) ;
var top = ( browserHeight - pic_boxHeight ) / 2 ;
$ ( ' .pic_box ' ) . css ( " margin-top " , top + " px " ) ;
} ) ;
/ / 图片效果
$ ( ' .pic_looper,.pic_show ' ) . click ( function ( ) {
$ ( ' .pic_looper ' ) . fadeOut ( 200 ) ;
$ ( ' .pic_show ' ) . fadeOut ( 200 ) ;
} ) ;
/ / 改变窗口大小时的动作
var resize_action = function ( ) {
let browserWidth = $ ( window ) . width ( ) ;
let 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 ( " width " , " 450px " ) ;
$ ( " #container_extend " ) . css ( " width " , " 550px " ) ;
$ ( " #testinfo " ) . css ( " width " , " 25 %% " ) ;
$ ( " #container " ) . css ( " margin-left " , ( margin_left - 1 - 16 ) + " px " ) ;
}
} ;
resize_action ( ) ;
/ / 改变窗口大小时自动调整图表和图片边距
$ ( window ) . resize ( function ( ) {
let browserHeight = $ ( window ) . height ( ) ;
let pic_boxHeight = $ ( " .pic_box " ) . height ( ) ;
let top = ( browserHeight - pic_boxHeight ) / 2 ;
$ ( ' .pic_box ' ) . css ( " margin-top " , top + " px " ) ;
resize_action ( ) ;
} ) ;
/ / 超过页面高度时显示回到顶部按钮
$ ( window ) . scroll ( function ( ) { if ( $ ( window ) . scrollTop ( ) > = $ ( window ) . height ( ) ) { $ ( " #toTop " ) . css ( " display " , " block " ) ; } else { $ ( " #toTop " ) . css ( " display " , " none " ) ; } } ) ;
/ / 增加回到顶部过程动画效果
$ ( " #toTop " ) . click ( function ( ) { $ ( " html,body " ) . animate ( { " scrollTop " : 0 } , 500 ) } ) ;
/ / 增加条形图
$ ( " #container_extend " ) . highcharts ( {
chart : {
type : " bar "
} ,
credits : {
enabled : ! 1
} ,
navigation : {
buttonOptions : {
enabled : ! 1
}
} ,
title : {
text : " 用例集合情况 "
} ,
xAxis : {
categories : % ( casesets ) s
} ,
yAxis : {
min : 0 ,
title : {
text : " 用例数量 "
} ,
reversedStacks : ! 1
} ,
legend : {
reversed : ! 1
} ,
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 ,
plotBorderWidth : null ,
plotShadow : ! 1 ,
spacing : [ 0 , 0 , 0 , 0 ]
} ,
credits : {
enabled : ! 1
} ,
navigation : {
buttonOptions : {
enabled : ! 1
}
} ,
title : {
floating : ! 0 ,
text : " 测试结果占比 "
} ,
tooltip : {
pointFormat : ' <text style= " font-size:10px " > {series.name} : {point.percentage:.1f} %% </text> '
} ,
plotOptions : {
pie : {
allowPointSelect : ! 0 ,
cursor : " pointer " ,
colors : [ " #64bb64 " , " #f16d7e " , " #fdc68c " ] ,
dataLabels : {
enabled : ! 0 ,
format : " <b> {point.name} </b>: {point.percentage:.1f} %% " ,
style : {
color : ( Highcharts . theme & & Highcharts . theme . contrastTextColor ) | | " black "
}
} ,
point : {
events : {
mouseOver : function ( a ) {
chart . setTitle ( {
text : a . target . name + " \t " + a . target . y + " 个 "
} ) ;
}
}
}
}
} ,
series : [ {
type : " pie " ,
innerSize : " 80 %% " ,
name : " 比例 " ,
data : [ [ " 通过 " , % ( passed ) s ] , [ " 失败 " , % ( failed ) s ] , [ " 错误 " , % ( errors ) s ] ]
} ]
} ,
function ( c ) {
var centerY = c . series [ 0 ] . center [ 1 ] ,
titleHeight = parseInt ( c . title . styles . fontSize ) ;
c . setTitle ( {
x : 0 ,
y : centerY + titleHeight / 2
} ) ;
chart = c ;
} ) ;
} ) ;
function showCase ( level ) {
/ *
0 : / / All hiddenRow .
1 : Failed / / Show Failed Only .
2 : Passed / / Show Passed Only .
3 : Errors / / Show Errors Only .
4 : All / / All Show .
* /
let trs = document . getElementsByTagName ( " tr " ) ;
for ( let i = 0 ; i < trs . length ; i + + ) {
let tr = trs [ i ] ;
let id = tr . id ;
if ( id . substr ( 0 , 2 ) == ' ft ' ) {
if ( level == 2 | | level == 0 | | level == 3 ) {
tr . className = ' hiddenRow ' ;
}
else {
tr . className = ' ' ;
$ ( " div[id^= ' div_ft ' ] " ) . attr ( " class " , " collapse " ) ;
$ ( " div[id^= ' div_et ' ] " ) . attr ( " class " , " collapse " ) ;
$ ( " div[id^= ' div_pt ' ] " ) . attr ( " class " , " collapse " ) ;
}
}
if ( id . substr ( 0 , 2 ) == ' pt ' ) {
if ( level == 1 | | level == 0 | | level == 3 ) {
tr . className = ' hiddenRow ' ;
}
else {
tr . className = ' ' ;
$ ( " div[id^= ' div_ft ' ] " ) . attr ( " class " , " collapse " ) ;
$ ( " div[id^= ' div_et ' ] " ) . attr ( " class " , " collapse " ) ;
$ ( " div[id^= ' div_pt ' ] " ) . attr ( " class " , " collapse " ) ;
}
}
if ( id . substr ( 0 , 2 ) == ' et ' ) {
if ( level == 1 | | level == 0 | | level == 2 ) {
tr . className = ' hiddenRow ' ;
}
else {
tr . className = ' ' ;
$ ( " div[id^= ' div_ft ' ] " ) . attr ( " class " , " collapse " ) ;
$ ( " div[id^= ' div_et ' ] " ) . attr ( " class " , " collapse " ) ;
$ ( " div[id^= ' div_pt ' ] " ) . attr ( " class " , " collapse " ) ;
}
}
}
let detail_class = document . getElementsByClassName ( ' detail ' ) ;
if ( level == 4 ) {
for ( let i = 0 ; i < detail_class . length ; i + + ) {
detail_class [ i ] . innerHTML = " 收起 " ;
}
}
else {
for ( let i = 0 ; i < detail_class . length ; i + + ) {
detail_class [ i ] . innerHTML = " 详细 " ;
}
}
}
function showClassDetail ( cid , count ) {
let id_list = Array ( count ) ;
let toHide = 1 ;
for ( let i = 0 ; i < count ; i + + ) {
let tid_suffix = ' t ' + cid . substr ( 1 ) + ' _ ' + ( i + 1 ) ;
let tid = ' ' ;
let tel ;
let p = [ ' p ' , ' f ' , ' e ' ] ;
for ( let j = 0 ; j < p . length ; j + + ) {
let cur = p [ j ] ;
tid = cur + tid_suffix ;
tel = document . getElementById ( tid ) ;
if ( tel ) { break }
}
id_list [ i ] = tid ;
if ( tel . className ) { toHide = 0 }
}
for ( let i = 0 ; i < count ; i + + ) {
let tid = id_list [ i ] ;
if ( toHide ) {
document . getElementById ( tid ) . className = ' hiddenRow ' ;
document . getElementById ( cid ) . innerText = " 详细 " ;
}
else {
document . getElementById ( tid ) . className = ' ' ;
document . getElementById ( cid ) . innerText = " 收起 " ;
}
}
}
function html_escape ( s ) {
return s . replace ( / & / g , ' & ' ) . replace ( / < / g , ' < ' ) . replace ( / > / g , ' > ' ) ;
}
< / script >
% ( header ) s
% ( report ) s
% ( footer ) s
< / body >
< / html >
"""
# ------------------------------------------------------------------------
# 网页样式
STYLES_TMPL = """
< style type = " text/css " media = " screen " >
body { font - family : Microsoft YaHei ; padding : 15 px 20 px 20 px 20 px ; font - size : 100 % }
table { font - size : 100 % }
. table tbody tr td { vertical - align : middle }
. attribute , . header . description { clear : both }
. errorsCollection , . failedCollection { width : auto ; float : left }
#failedCaseOl li{color:red}
#errorsCaseOl li{color:orange}
. data - img { cursor : pointer }
. pic_looper { width : 100 % ; height : 100 % ; position : fixed ; left : 0 ; top : 0 ; opacity : .6 ; background : #000;display:none;z-index:100}
. pic_show { width : 100 % ; position : fixed ; left : 0 ; top : 0 ; right : 0 ; bottom : 0 ; margin : auto ; text - align : center ; display : none ; z - index : 100 }
. pic_box { padding : 10 px ; width : 90 % ; height : 90 % ; margin : 40 px auto ; text - align : center ; overflow : hidden }
. pic_box img { max - width : 100 % ; max - height : 100 % ; width : auto ; height : auto ; - moz - box - shadow : 0 0 20 px 0 #000;-webkit-box-shadow:0 0 20px 0 #000;box-shadow:0 0 20px 0 #000}
#container{max-width:100%;width:450px;height:350px;float:left}
#container_extend{max-width:100%;width:550px;height:400px;float:left}
#total_row{font-weight:700}
. passedCase { color : #3fb83f;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:14px;font-weight:700}
. failedCase { color : #d9433f;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:14px;font-weight:700}
. errorsCase { color : #f0a02f;font-family:Menlo,Monaco,Consolas,"Courier New",monospace;font-size:14px;font-weight:700}
. hiddenRow { display : none }
. testcase { margin - left : 1 em ; word - break : break - all ; white - space : pre - wrap }
. screenshot : link { text - decoration : none ; color : #ff1493}
. screenshot : visited { text - decoration : none ; color : #ff1493}
. screenshot : hover { text - decoration : none ; color : #008b8b}
. screenshot : active { text - decoration : none ; color : #ff1493}
< / style >
"""
# ------------------------------------------------------------------------
# 报告标题、测试信息、报告描述、截图显示容器、图表容器
# 变量列表 title, parameters, description, logo, sign
HEADER_TMPL = """
< div class = " pic_looper " > < / div >
< div class = " pic_show " > < div class = " pic_box " > < img src = " " / > < / div > < / div >
< div style = " width:auto;height:auto;border:1px solid #e3e3e3;text-align:center;color:#505050;display: %(rail_hidden)s ;justify-content:center " >
< img src = " %(logo)s " style = " width:auto;height:auto;max-height:40px;padding:1px 4px 1px 4px; " / >
< div style = " display:flex;flex-direction:row;align-items:center " > < b style = " font-size:22px;color:#353535;text-align:center " > % ( sign ) s < / b > < / div >
< / div >
< 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
HEADER_ATTRIBUTE_TMPL = """
< p class = " attribute " > < strong > % ( name ) s : < / strong > % ( value ) s < / p >
"""
# ------------------------------------------------------------------------
# 报告模板
# 变量列表 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) " > 通过 % ( 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 " >
< colgroup >
< col align = " left " style = " width:300px " / >
< col align = " right " style = " width:285px " / >
< col align = " right " / >
< col align = " right " / >
< col align = " right " / >
< col align = " right " / >
< col align = " right " / >
< col align = " right " style = " width:120px " / >
< / colgroup >
< tr id = " header_row " class = " text-center success " style = " font-weight:bold;font-size:14px " >
< td > 测试用例 < / td >
< td > 说明 < / td >
< td > 总计 < / td >
< td > 通过 < / td >
< td > 失败 < / td >
< td > 错误 < / td >
< td > 耗时 < / td >
< td > 详细 < / td >
< / tr >
% ( test_list ) s
< tr id = " total_row " class = " text-center active " >
< td colspan = " 2 " > 总计 < / 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, counts, passed, failed, errors, cid
REPORT_CLASS_TMPL = """
< tr class = " %(style)s warning " >
< td > % ( name ) 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 ' , %(counts)s ) " class = " detail " id = " %(cid)s " > 查看全部 < / a > < / td >
< / tr >
"""
# ------------------------------------------------------------------------
# 失败样式(有截图列)
# 变量列表 tid, Class, style, desc, status
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 " > % ( 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-family:monospace;font-size:12px;color: %(pre_color)s " > % ( 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 >
< / tr >
"""
# ------------------------------------------------------------------------
# 失败样式(无截图列)
# 变量列表 tid, Class, style, desc, status
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 " > % ( 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 " >
< pre style = " text-align:left;font-family:monospace;font-size:12px;color: %(pre_color)s " > % ( script ) s < / pre >
< / div >
< / td >
< td class = " %(style)s " style = " vertical-align:middle " > < / td >
< / tr >
"""
# ------------------------------------------------------------------------
# 通过样式
# 变量列表 tid, Class, style, desc, status
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 " > % ( 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 >
"""
# ------------------------------------------------------------------------
# 测试输出内容
REPORT_TEST_OUTPUT_TMPL = ' %(id)s : ' + " \n " + ' %(output)s '
# ------------------------------------------------------------------------
# 页面底部、返回顶部
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 " > < / span > < / a >
< / div >
"""
# ------------------------------------------------------------------------
def _findMark ( mark = ' ' , data = ' ' ) :
return re . findall ( ' \\ [ ' + mark + ' ](.*?) \\ [/ ' + mark + ' ] ' + ' *? ' , data )
def _makeMark ( mark = ' ' , cont = ' ' ) :
return ' [ ' + mark + ' ] ' + cont + ' [/ ' + mark + ' ] '
def _Color ( fc = 0 , bc = 0 , bo = 0 , text = ' ' ) :
b = ' PYCHARM_HOSTED ' in os . environ . keys ( )
return " \033 [ " + str ( bo ) + [ ' ' , ' ; ' + str ( fc ) ] [ fc > 0 ] + [ ' ' , ' ; ' + str ( bc ) ] [ bc > 0 ] + " m " + text + " \033 [0m " if b else text
class _TestResult ( unittest . TestResult ) :
def __init__ ( self , verbosity = 1 , log = ' ' , success_log_in_report = False ) :
super ( ) . __init__ ( verbosity = verbosity )
self . verbosity = verbosity
self . log = log
self . success_log_in_report = success_log_in_report
self . fh = None
self . lh = None
self . ch = None
self . loggerStream = None
self . outputBuffer = None
self . stdout0 = None
self . stderr0 = None
self . passed_count = 0
self . failed_count = 0
self . errors_count = 0
self . stime = None
self . etime = None
self . passrate = float ( 0 )
# 分类统计数量耗时
self . casesort = { }
# 增加失败用例合集
self . failedCase = ' '
self . failedCaseList = [ ]
# 增加错误用例合集
self . errorsCase = ' '
self . errorsCaseList = [ ]
self . logger = logging . getLogger ( ' test ' )
# result is a list of result in 4 tuple:
# 1. result code (0: passed; 1: failed; 2: errors),
# 2. testcase object,
# 3. test output (byte string),
# 4. stack trace.
self . result = [ ]
def sortCount ( self , cls , res , dur = float ( 0 ) ) :
"""
分类统计
"""
s = self . casesort
if cls not in s . keys ( ) :
s [ cls ] = { ' p ' : 0 , ' f ' : 0 , ' e ' : 0 , ' d ' : 0 }
if str ( res ) . upper ( ) . startswith ( ' P ' ) :
s [ cls ] [ ' p ' ] + = 1
if str ( res ) . upper ( ) . startswith ( ' F ' ) :
s [ cls ] [ ' f ' ] + = 1
if str ( res ) . upper ( ) . startswith ( ' E ' ) :
s [ cls ] [ ' e ' ] + = 1
s [ cls ] [ ' d ' ] + = dur
return True
def startTest ( self , test ) :
"""
单条用例执行开始前的动作
"""
if self . verbosity > = 2 :
sys . stderr . write ( _Color ( fc = 39 , bc = 4 , bo = 1 , text = ' %-79s ' % ( ' %04d @ Testing... ' % ( len ( self . result ) + 1 ) ) ) + " \n " )
# 开始测试
super ( ) . startTest ( test )
# 终端日志
if self . verbosity > = 2 :
self . ch = logging . StreamHandler ( sys . stderr )
self . ch . setLevel ( logging . DEBUG )
self . ch . setFormatter ( logging . Formatter ( fmt = ' {asctime} - {levelname[0]} : {message} ' , style = ' { ' ) )
self . logger . addHandler ( self . ch )
# 报告日志
if True :
self . loggerStream = io . StringIO ( )
self . lh = logging . StreamHandler ( self . loggerStream )
self . lh . setLevel ( logging . DEBUG )
self . lh . setFormatter ( logging . Formatter ( fmt = ' {asctime} - {levelname[0]} : {message} ' , style = ' { ' ) )
self . logger . addHandler ( self . lh )
# 文件日志
if self . log :
self . fh = logging . FileHandler ( filename = self . log , mode = ' a+ ' , encoding = ' utf-8 ' )
self . fh . setLevel ( logging . DEBUG )
self . fh . setFormatter ( logging . Formatter ( fmt = ' {asctime} - {levelname[0]} : {module} . {funcName} \t {message} ' , style = ' { ' ) )
self . logger . addHandler ( self . fh )
# 用例执行过程中的输出
# Just one buffer for both stdout and stderr
self . outputBuffer = io . StringIO ( )
stdout_redirector . fp = self . outputBuffer
stderr_redirector . fp = self . outputBuffer
self . stdout0 = sys . stdout
self . stderr0 = sys . stderr
sys . stdout = stdout_redirector
sys . stderr = stderr_redirector
self . stime = round ( time . time ( ) , 3 )
def completeOutput ( self ) :
"""
单条用例执行结束后 , 添加结果前的动作 ;
添加结果需要调用的方法 ;
断开输出重定向 ;
返回日志和输出 ;
"""
self . etime = round ( time . time ( ) , 3 )
if self . stdout0 :
sys . stdout = self . stdout0
sys . stderr = self . stderr0
self . stdout0 = None
self . stderr0 = None
return [
self . loggerStream . getvalue ( ) ,
self . outputBuffer . getvalue ( )
]
def stopTest ( self , test ) :
"""
单条用例执行结束后 , 添加结果后的动作 ;
"""
super ( ) . stopTest ( test )
# 移除日志Handler
for handler in [ self . fh , self . lh , self . ch ] :
if handler :
self . logger . removeHandler ( handler )
def singleEndConsolePrint ( self , test , term_mark , term_head , term_clor , duration ) :
"""
将用例执行结果打印到终端显示
"""
self . verbosity > = 1 and sys . stderr . write ( ' %s %s %s %s . %s . %-18s \t %s %s \n ' % (
_Color ( fc = term_clor , bo = 1 , text = ' %04d ' % ( len ( self . result ) ) + ' ' + term_mark + ' ' + term_head + ' : ' ) ,
_Color ( fc = 37 , bo = 0 , text = datetime . datetime . utcfromtimestamp ( duration ) . strftime ( ' % H: % M: % S. %f ' ) [ 0 : 12 ] ) ,
_Color ( fc = 37 , bo = 0 , text = ' <= ' ) ,
_Color ( fc = 37 , bo = 0 , text = str ( test . __module__ ) . strip ( ' _ ' ) ) ,
_Color ( fc = 37 , bo = 0 , text = str ( test . __class__ . __qualname__ ) ) ,
_Color ( fc = 37 , bo = 0 , text = str ( test . __dict__ [ ' _testMethodName ' ] ) ) ,
_Color ( fc = 37 , bo = 0 , text = ' <= ' ) ,
_Color ( fc = 37 , bo = 0 , text = str ( test . __dict__ [ ' _testMethodDoc ' ] and test . __dict__ [ ' _testMethodDoc ' ] . strip ( ) . splitlines ( ) [ 0 ] . strip ( ) or ' ' ) )
) )
def addSuccess ( self , test ) :
"""
单条用例执行结束后添加成功结果动作
"""
term_mark , term_head , term_clor = ' = ' , ' Passed ' , 32
self . passed_count + = 1
super ( ) . addSuccess ( test )
output = self . completeOutput ( )
duration = round ( self . etime - self . stime , 3 )
self . sortCount ( cls = test . __class__ . __qualname__ , res = term_head , dur = duration )
# 添加测试结果
self . result . append ( ( 0 , test , [ ' ' , output [ 0 ] ] [ bool ( self . success_log_in_report ) ] + output [ 1 ] , ' ' , duration ) )
# 单条用例执行结束后在终端打印结果
self . singleEndConsolePrint ( test , term_mark , term_head , term_clor , duration )
def addError ( self , test , err ) :
"""
单条用例执行结束后添加错误结果动作
"""
term_mark , term_head , term_clor = ' ? ' , ' Errors ' , 33
self . errors_count + = 1
super ( ) . addError ( test , err )
_ , _exc_str = self . errors [ - 1 ]
output = self . completeOutput ( )
duration = round ( self . etime - self . stime , 3 )
self . sortCount ( cls = test . __class__ . __qualname__ , res = term_head , dur = duration )
# 添加测试结果
self . result . append ( ( 2 , test , output [ 0 ] + output [ 1 ] , _exc_str , duration ) )
# 用例执行结束后在终端打印结果
self . singleEndConsolePrint ( test , term_mark , term_head , term_clor , duration )
# 收集错误测试用例名称以在测试报告中显示
testcase_name = ' %s . %s . %s ' % ( str ( test . __module__ ) . strip ( ' _ ' ) , test . __class__ . __qualname__ , test . __dict__ [ ' _testMethodName ' ] )
self . errorsCase + = ' <li> %s </li> ' % testcase_name
self . errorsCaseList . append ( testcase_name )
def addFailure ( self , test , err ) :
"""
单条用例执行结束后添加失败结果动作
"""
term_mark , term_head , term_clor = ' ! ' , ' Failed ' , 31
self . failed_count + = 1
super ( ) . addFailure ( test , err )
_ , _exc_str = self . failures [ - 1 ]
output = self . completeOutput ( )
duration = round ( self . etime - self . stime , 3 )
self . sortCount ( cls = test . __class__ . __qualname__ , res = term_head , dur = duration )
# 添加测试结果
self . result . append ( ( 1 , test , output [ 0 ] + output [ 1 ] , _exc_str , duration ) )
# 用例执行结束后在终端打印结果
self . singleEndConsolePrint ( test , term_mark , term_head , term_clor , duration )
# 收集失败测试用例名称以在测试报告中显示
testcase_name = ' %s . %s . %s ' % ( str ( test . __module__ ) . strip ( ' _ ' ) , test . __class__ . __qualname__ , test . __dict__ [ ' _testMethodName ' ] )
self . failedCase + = ' <li> %s </li> ' % testcase_name
self . failedCaseList . append ( testcase_name )
class HTMLTestRunner ( ReportTemplate ) :
def __init__ (
self ,
stream = None ,
verbosity : int = 1 ,
title : str = None ,
description : str = None ,
success_log_in_report = False ,
report_home : str = None ,
report_home_latest_name : str = None ,
report : str = None ,
log : str = None ,
logo = ' ' ,
sign = ' '
) :
"""
This is HTMLTestRunner .
: param stream : HTML report write file stream .
: param verbosity :
0 : Terminal only shows the test results summary .
1 : Terminal shows the results summary and results of each case test .
2 : Terminal shows the results summary and results of each case test , and print log .
: param title : Title of report .
: param description : Description of report .
: param success_log_in_report : The testcase passed is also displayed in the HTML report .
: param report_home :
Set home directory of the report for this test ,
all the results related files will be storage inside ( including HTML report , log , images ) ,
at this time ,
other related parameters will fail .
: param report_home_latest_name : Create < latest > directory on the path where the report directory is located .
: param report : HTML report file location ( useless when stream parameter inset ) .
: param log : Log file .
: param logo : Logo of report , pass an image URL .
: param sign : Sign of report , Pass in one piece short text .
"""
if report_home :
try :
2023-03-13 20:06:31 +08:00
os . makedirs ( report_home )
2023-03-13 18:49:49 +08:00
except Exception :
raise Exception ( ' Report directory already exists. ' )
stream = report = log = None
report , log = ' %s /index.html ' % report_home , ' %s /HTMLTestRunner.log ' % report_home
self . report_home = report_home
self . result_associate_files = [ ]
self . verbosity = verbosity
self . success_log_in_report = success_log_in_report
self . report_home_latest_name = report_home_latest_name
self . stream = stream or ( report and open ( os . path . abspath ( report ) , ' wb ' ) )
self . report = None
if self . stream :
stream_name = os . path . abspath ( self . stream . name )
self . result_associate_files . append ( stream_name )
os . environ [ ' HTML_REPORT_ROOT ' ] = os . path . dirname ( stream_name )
self . report = stream_name
else :
os . environ [ ' HTML_REPORT_ROOT ' ] = ' '
self . log = log
self . log_location = log and os . path . abspath ( log )
self . log_location and self . result_associate_files . append ( self . log_location )
self . log_location and open ( self . log_location , ' wb ' ) . close ( )
self . title = self . DEFAULT_TITLE if title is None else title
self . description = self . DEFAULT_DESCRIPTION if description is None else description
self . logo = logo or ' '
self . sign = sign or ' '
self . passrate = None
self . errormsg = None
self . runstime = None
self . runetime = None
def run ( self , test ) :
sys . stderr . write ( _Color ( fc = 38 , bo = 1 , text = ' * * * * * * * * * * * * * * * * * * 开始测试 * * * * * * * * * * * * * * * * * * ' ) + ' \n ' )
self . verbosity > = 2 and sys . stderr . write ( " \n " )
self . runstime = round ( time . time ( ) , 3 )
# 获取测试结果
result = _TestResult ( verbosity = self . verbosity , log = self . log_location , success_log_in_report = self . success_log_in_report )
test ( result )
self . runetime = round ( time . time ( ) , 3 )
self . verbosity > = 2 and sys . stderr . write ( " \n " )
sys . stderr . write ( _Color ( fc = 38 , bo = 1 , text = ' * * * * * * * * * * * * * * * * * * 结束测试 * * * * * * * * * * * * * * * * * * ' ) + ' \n ' )
# 生成测试报告
self . generateReport ( test , result )
if len ( result . result ) == 0 :
raise Exception ( ' No testcases found. ' )
case_count = {
' t ' : len ( result . result ) ,
' p ' : result . passed_count ,
' f ' : result . failed_count ,
' e ' : result . errors_count
}
dura = time . strftime ( ' % H: % M: % S ' , time . gmtime ( ( self . runetime - self . runstime ) ) )
rate_passed = str ( " %.2f %% " % ( float ( case_count [ ' p ' ] / case_count [ ' t ' ] * 100 ) ) )
rate_failed = str ( " %.2f %% " % ( float ( case_count [ ' f ' ] / case_count [ ' t ' ] * 100 ) ) )
rate_errors = str ( " %.2f %% " % ( float ( case_count [ ' e ' ] / case_count [ ' t ' ] * 100 ) ) )
list_failed = result . failedCaseList
list_errors = result . errorsCaseList
casesort = result . casesort
# 终端打印结果
sys . stderr . write ( _Color ( fc = 36 , bo = 1 , text = ' 结果概要 ' ) + " \n " )
sys . stderr . write (
_Color ( fc = 37 , bo = 0 , text = ' 总共 ' + " \x20 " + str ( case_count [ " t " ] ) + " \x20 " ) +
_Color ( fc = 37 , bo = 0 , text = ' 通过 ' + " \x20 " + str ( case_count [ " p " ] ) + " \x20 " ) +
_Color ( fc = 37 , bo = 0 , text = ' 失败 ' + " \x20 " + str ( case_count [ " f " ] ) + " \x20 " ) +
_Color ( fc = 37 , bo = 0 , text = ' 错误 ' + " \x20 " + str ( case_count [ " e " ] ) + " \x20 " ) +
_Color ( fc = 37 , bo = 0 , text = ' 耗时 ' + " \x20 " + dura + " \x20 " ) +
_Color ( fc = 37 , bo = 0 , text = ' 通过率 ' + " \x20 " + rate_passed + " \x20 " ) +
_Color ( fc = 37 , bo = 0 , text = ' 失败率 ' + " \x20 " + rate_failed + " \x20 " ) +
_Color ( fc = 37 , bo = 0 , text = ' 错误率 ' + " \x20 " + rate_errors + " \x20 " ) +
" \n "
)
for value in [ [ ' 失败用例 ' , list_failed , 31 ] , [ ' 错误用例 ' , list_errors , 33 ] ] :
if len ( value [ 1 ] ) :
sys . stderr . write ( _Color ( fc = value [ 2 ] , bo = 1 , text = value [ 0 ] ) + " \n " )
for i in range ( len ( value [ 1 ] ) ) :
sys . stderr . write ( _Color ( fc = 37 , bo = 0 , text = str ( i + 1 ) + ' . ' + value [ 1 ] [ i ] ) + " \n " )
if len ( casesort . keys ( ) ) :
sys . stderr . write (
_Color ( fc = 34 , bo = 1 , text = ' %-22s ' % ( ' 用例集合 ' ) + " \t " +
' %-4s ' % ( ' 总计 ' ) + " \t " +
' %-4s ' % ( ' 通过 ' ) + " \t " +
' %-4s ' % ( ' 失败 ' ) + " \t " +
' %-4s ' % ( ' 错误 ' ) + " \t " +
' %-8s ' % ( ' 耗时 ' ) + " \t " ) + " \n " )
for key , value in casesort . items ( ) :
sys . stderr . write (
_Color ( fc = 37 , bo = 0 , text = ' %-22s ' % ( str ( key ) ) + " \t " +
' %-4s ' % ( str ( value [ " p " ] + value [ " f " ] + value [ " e " ] ) ) + " \t " +
' %-4s ' % ( str ( value [ " p " ] ) ) + " \t " +
' %-4s ' % ( str ( value [ " f " ] ) ) + " \t " +
' %-4s ' % ( str ( value [ " e " ] ) ) + " \t " +
' %-8s ' % ( ' %.3f ' % ( round ( value [ " d " ] , 3 ) ) + ' 秒 ' ) + " \t " ) + " \n " )
return result
@staticmethod
def sortResult ( result_list ) :
rmap = { }
classes = [ ]
for n , t , o , e , s in result_list :
cls = t . __class__
if cls not in rmap :
rmap [ cls ] = [ ]
classes . append ( cls )
rmap [ cls ] . append ( ( n , t , o , e , s ) )
return [ ( cls , rmap [ cls ] ) for cls in classes ]
def getReportAttributes ( self , result ) :
runstime = time . strftime ( ' % Y- % m- %d % H: % M: % S ' , time . localtime ( self . runstime ) )
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 )
] )
if ( result . passed_count + result . failed_count + result . errors_count ) > 0 :
self . passrate = str ( " %.2f %% " % ( float ( result . passed_count ) / float (
result . passed_count + result . failed_count + result . errors_count ) * 100 ) )
else :
self . passrate = ' 0.00 % '
if len ( result . failedCase ) > 0 :
failedCase = result . failedCase
else :
failedCase = ' 无 '
if len ( result . errorsCase ) > 0 :
errorsCase = result . errorsCase
else :
errorsCase = ' 无 '
uname = platform . uname ( )
return [
[ ' 开始时间 ' , runstime ] ,
[ ' 合计耗时 ' , duration ] ,
[ ' 主机名称 ' , ' %s ( %s %s ) %s ' % ( uname . node , uname . system , uname . release , uname . machine . lower ( ) ) ] ,
[ ' 测试结果 ' , ' %s ,通过率 %s ' % ( status , self . passrate ) ] ,
[ ' 失败用例 ' , failedCase ] ,
[ ' 错误用例 ' , errorsCase ] ,
]
def generateReport ( self , test , result ) :
report_attrs = self . getReportAttributes ( result )
report_count = self . _generate_report ( result )
output = self . HTML_TMPL % dict (
title = saxutils . escape ( self . title ) ,
generator = ' HTMLTestRunner ' ,
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 and self . stream . write ( output . encode ( ' utf-8 ' ) )
self . stream and self . stream . close ( )
if self . report_home and self . report_home_latest_name :
latest = ' %s / %s ' % ( os . path . dirname ( self . report_home ) , self . report_home_latest_name )
os . path . exists ( latest ) and shutil . rmtree ( latest )
shutil . copytree ( self . report_home , latest )
if self . report_home :
open ( ' %s /latest.ini ' % os . path . dirname ( self . report_home ) , mode = ' w ' ) . write ( os . path . basename ( self . report_home ) )
def _generate_styles ( self ) :
return self . STYLES_TMPL
def _generate_header ( self , report_attrs ) :
line_list = [ ]
for name , value in report_attrs :
match name :
case ' 失败用例 ' :
if value == " 无 " :
line = self . HEADER_ATTRIBUTE_TMPL % dict ( name = name , value = value )
else :
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> "
)
case ' 错误用例 ' :
if value == " 无 " :
line = self . HEADER_ATTRIBUTE_TMPL % dict ( name = name , value = value )
else :
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> "
)
case _ :
line = self . HEADER_ATTRIBUTE_TMPL % dict ( name = saxutils . escape ( str ( name ) ) , value = saxutils . escape ( str ( value ) ) )
line_list . append ( line )
return self . HEADER_TMPL % dict (
rail_hidden = ' none ' if not self . logo and not self . sign else ' flex ' ,
logo = self . logo ,
sign = self . sign ,
title = saxutils . escape ( self . title ) ,
parameters = ' ' . join ( line_list ) ,
description = saxutils . escape ( self . description )
)
def _generate_report ( self , result ) :
rows = [ ]
sortedResult = self . sortResult ( result . result )
dura_caseset = 0
for cid , ( cls , cls_results ) in enumerate ( sortedResult ) :
np = nf = ne = ns = 0
for n , t , o , e , s in cls_results :
# 遍历每条用例
match n :
case 0 :
np + = 1
case 1 :
nf + = 1
case 2 :
ne + = 1
ns + = s
# 单个用例集合耗时
ns = round ( ns , 3 )
dura_caseset + = ns
row = self . REPORT_CLASS_TMPL % dict (
style = ne > 0 and ' errorsClass ' or nf > 0 and ' failedClass ' or ' passedClass ' ,
name = cls . __qualname__ ,
docs = cls . __doc__ and cls . __doc__ . strip ( ) . splitlines ( ) [ 0 ] . strip ( ) or ' ' ,
counts = np + nf + ne ,
passed = np ,
failed = nf ,
errors = ne ,
cid = ' c %s ' % ( cid + 1 ) ,
time_usage = ' %.3f 秒 ' % ns
)
rows . append ( row )
for tid , ( n , t , o , e , s ) in enumerate ( cls_results ) :
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 ) ,
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
)
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 ) ,
" 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 ) :
hasout = bool ( o or e )
match n :
case 0 :
tid_flag = ' p '
pre_clor = ' #119611 '
case 1 :
tid_flag = ' f '
pre_clor = ' #e52000 '
case 2 :
tid_flag = ' e '
pre_clor = ' #e54f00 '
case _ :
tid_flag = ' u '
pre_clor = ' #808080 '
# ID修改点为下划线;
# 支持Bootstrap折叠展开特效;
# 例如:'pt1_1', 'ft1_1', 'et1_1'
tid = tid_flag + ' t %s _ %s ' % ( cid + 1 , tid + 1 )
name = t . id ( ) . split ( ' . ' ) [ - 1 ]
docs = t . shortDescription ( ) or ' '
# o and e should be byte string because they are collected from stdout and stderr.
if isinstance ( o , str ) :
# some problem with 'string_escape': it escape \n and mess up formating
# uo = unicode(o.encode('string_escape'))
# uo = o.decode('latin-1')
uo = o
else :
uo = o
if isinstance ( e , str ) :
# some problem with 'string_escape': it escape \n and mess up formating
# ue = unicode(e.encode('string_escape'))
# ue = e.decode('latin-1')
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 ) ) )
# 截图名称通过抛出异常在
# 标准错误当中,
# 判断是否包含截图信息
# 实际检测输出当中是否包含report-screenshot关键字;
output = uo + ue
self . errormsg = output . find ( ' report-screenshot ' )
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 ' ,
style = n == 2 and ' errorsCase ' or ( n == 1 and ' failedCase ' or ' passedCase ' ) ,
name = name ,
docs = docs ,
script = script ,
status = self . STATUS [ n ] ,
pre_color = pre_clor
)
else :
# 包含截图信息
template = hasout and self . REPORT_TEST_WITH_OUTPUT_TMPL_1 or self . REPORT_TEST_NO_OUTPUT_TMPL
screenshot_list = _findMark ( mark = ' report-screenshot ' , data = output )
screenshot = ' '
for image in screenshot_list :
bn = os . path . basename ( image )
abs_image = os . path . abspath ( image )
self . result_associate_files . append ( abs_image )
# 移动图片位置到报告所在目录
try :
self . report and shutil . move ( abs_image , ' %s / %s ' % ( os . path . dirname ( self . report ) , bn ) )
except Exception :
pass
screenshot + = ' <a style= " font-family:Consolas,monospace;color:#e52000;text-decoration:underline " ' \
' class= " screenshot " href= " javascript:void(0) " img= " ' + ' ./ %s ' % bn + ' " > ' + bn + ' </a></br> '
row = template % dict (
tid = tid ,
Class = n == 0 and ' hiddenRow ' or ' none ' ,
style = n == 2 and ' errorsCase ' or ( n == 1 and ' failedCase ' or ' passedCase ' ) ,
name = name ,
docs = docs ,
script = script ,
status = self . STATUS [ n ] ,
pre_color = pre_clor ,
screenshot = screenshot
)
return row
def _generate_footer ( self ) :
return self . FOOTER_TMPL