HTMLTestRunner.main()
-To customize the report, instantiates a HTMLTestRunner object and set
-the parameters. HTMLTestRunner is a counterpart to unittest's
-TextTestRunner. E.g.
+For more customization options, instantiates a HTMLTestRunner object.
+HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
# output to a file
fp = file('my_report.html', 'wb')
runner = HTMLTestRunner.HTMLTestRunner(
stream=fp,
title='My unit test',
- report_attrs=[('Version','1.2.3')],
description='This demonstrates the report output by HTMLTestRunner.'
)
------------------------------------------------------------------------
-Copyright (c) 2004-2006, Wai Yip Tung
+Copyright (c) 2004-2007, Wai Yip Tung
All rights reserved.
Redistribution and use in source and binary forms, with or without
# URL: http://tungwaiyip.info/software/HTMLTestRunner.html
__author__ = "Wai Yip Tung"
-__version__ = "0.8.0"
+__version__ = "0.8.2"
"""
-Changes in 0.8.0
+Change History
+
+Version 0.8.2
+* Show output inline instead of popup window (Viorel Lupu).
+
+Version in 0.8.1
+* Validated XHTML (Wolfgang Borgert).
+* Added description of test classes and test cases.
+
+Version in 0.8.0
* Define Template_mixin class for customization.
* Workaround a IE 6 bug that it does not treat <script> block as CDATA.
-Changes in 0.7.1
-* Back port to Python 2.3. Thank you Frank Horowitz.
-* Fix missing scroll bars in detail log. Thank you Podi.
+Version in 0.7.1
+* Back port to Python 2.3 (Frank Horowitz).
+* Fix missing scroll bars in detail log (Podi).
"""
# TODO: color stderr
# ------------------------------------------------------------------------
-# The redirectors below is used to capture output during testing. Output
+# The redirectors below are used to capture output during testing. Output
# sent to sys.stdout and sys.stderr are automatically captured. However
# in some cases sys.stdout is already cached before HTMLTestRunner is
# invoked (e.g. calling logging.basicConfig). In order to capture those
# ------------------------------------------------------------------------
# HTML Template
- HTML_TMPL = r"""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html>
+ HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
+<!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>
<title>%(title)s</title>
- <meta name="generator" content="%(generator)s">
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta name="generator" content="%(generator)s"/>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
%(stylesheet)s
</head>
<body>
-<script>
+<script language="javascript" type="text/javascript"><!--
output_list = Array();
/* level - 0:Summary; 1:Failed; 2:All */
}
}
+
function showClassDetail(cid, count) {
var id_list = Array(count);
var toHide = 1;
for (var i = 0; i < count; i++) {
tid = id_list[i];
if (toHide) {
+ document.getElementById('div_'+tid).style.display = 'none'
document.getElementById(tid).className = 'hiddenRow';
}
else {
}
}
+
+function showTestDetail(div_id){
+ var details_div = document.getElementById(div_id)
+ var displayState = details_div.style.display
+ // alert(displayState)
+ if (displayState != 'block' ) {
+ displayState = 'block'
+ details_div.style.display = 'block'
+ }
+ else {
+ details_div.style.display = 'none'
+ }
+}
+
+
function html_escape(s) {
s = s.replace(/&/g,'&');
s = s.replace(/</g,'<');
return s;
}
+/* obsoleted by detail in <div>
function showOutput(id, name) {
var w = window.open("", //url
name,
d.write("</pre>\n");
d.close();
}
-
-</script>
+*/
+--></script>
%(heading)s
%(report)s
# <link rel="stylesheet" href="$url" type="text/css">
STYLESHEET_TMPL = """
-<style>
+<style type="text/css" media="screen">
body { font-family: verdana, arial, helvetica, sans-serif; font-size: 80%; }
table { font-size: 100%; }
pre { }
/* -- heading ---------------------------------------------------------------------- */
h1 {
+ font-size: 16pt;
+ color: gray;
}
.heading {
margin-top: 0ex;
margin-bottom: 6ex;
}
+/* -- css div popup ------------------------------------------------------------------------ */
+a.popup_link {
+}
+
+a.popup_link:hover {
+ color: red;
+}
+
+.popup_window {
+ display: none;
+ position: relative;
+ left: 0px;
+ top: 0px;
+ /*border: solid #627173 1px; */
+ padding: 10px;
+ background-color: #E6E6D6;
+ font-family: "Lucida Console", "Courier New", Courier, monospace;
+ text-align: left;
+ font-size: 8pt;
+ width: 500px;
+}
+
+}
/* -- report ------------------------------------------------------------------------ */
#show_detail_line {
margin-top: 3ex;
#result_table {
width: 80%;
border-collapse: collapse;
- border: medium solid #777;
+ border: 1px solid #777;
}
#header_row {
font-weight: bold;
background-color: #777;
}
#result_table td {
- border: thin solid #777;
+ border: 1px solid #777;
padding: 2px;
}
#total_row { font-weight: bold; }
<col align='right' />
</colgroup>
<tr id='header_row'>
- <td>Class/Test case</td>
+ <td>Test Group/Test case</td>
<td>Count</td>
<td>Pass</td>
<td>Fail</td>
</table>
""" # variables: (test_list, count, Pass, fail, error)
-
REPORT_CLASS_TMPL = r"""
<tr class='%(style)s'>
- <td>%(name)s</td>
+ <td>%(desc)s</td>
<td>%(count)s</td>
<td>%(Pass)s</td>
<td>%(fail)s</td>
<td>%(error)s</td>
<td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">Detail</a></td>
</tr>
-""" # variables: (style, name, count, Pass, fail, error, cid)
+""" # variables: (style, desc, count, Pass, fail, error, cid)
REPORT_TEST_WITH_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
- <td class='%(style)s'><div class='testcase'>%(name)s<div></td>
- <td colspan='5' align='center'><a href="javascript:showOutput('%(tid)s', '%(name)s')">%(status)s</a></td>
+ <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
+ <td colspan='5' align='center'>
+
+ <!--css div popup start-->
+ <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
+ %(status)s</a>
+
+ <div id='div_%(tid)s' class="popup_window">
+ <div style='text-align: right; color:red;cursor:pointer'>
+ <a onfocus='this.blur();' onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
+ [x]</a>
+ </div>
+ <pre>
+ %(script)s
+ </pre>
+ </div>
+ <!--css div popup end-->
+
+ </td>
</tr>
-""" # variables: (tid, Class, style, name, status)
+""" # variables: (tid, Class, style, desc, status)
REPORT_TEST_NO_OUTPUT_TMPL = r"""
<tr id='%(tid)s' class='%(Class)s'>
- <td class='%(style)s'><div class='testcase'>%(name)s<div></td>
+ <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
<td colspan='5' align='center'>%(status)s</td>
</tr>
-""" # variables: (tid, Class, style, name, status)
+""" # variables: (tid, Class, style, desc, status)
REPORT_TEST_OUTPUT_TMPL = r"""
-<script>output_list['%(id)s'] = '%(output)s';</script>
+%(id)s: %(output)s
""" # variables: (id, output)
# -------------------- The end of the Template class -------------------
-
-def jsEscapeString(s):
- """ Escape s for use as a Javascript String """
- return s.replace('\\','\\\\') \
- .replace('\r', '\\r') \
- .replace('\n', '\\n') \
- .replace('"', '\\"') \
- .replace("'", "\\'") \
- .replace("&", '\\x26') \
- .replace("<", '\\x3C') \
- .replace(">", '\\x3E')
- # Note: non-ascii unicode characters do not need to be encoded
- # Note: previously we encode < as <, etc. However IE6 fail to treat <script> block as CDATA.
-
-
TestResult = unittest.TestResult
class _TestResult(TestResult):
class HTMLTestRunner(Template_mixin):
"""
"""
- def __init__(self, stream=sys.stdout, verbosity=1, title=None, report_attrs=[], description=None):
- """
- @param stream - output stream, default to stdout
- @param verbosity
- @param title - use in title and heading
- @param report_attrs - list of (name, value) to show in report
- @param description - test description
- """
+ def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None):
self.stream = stream
self.verbosity = verbosity
if title is None:
self.description = self.DEFAULT_DESCRIPTION
else:
self.description = description
- self.report_attrs = report_attrs
self.startTime = datetime.datetime.now()
def getReportAttributes(self, result):
"""
- Add a few system generated attributes on top of users defined.
- Override this to add other dynamic custom attributes.
-
- @return list of (name, value).
+ Return report attributes as a list of (name, value).
+ Override this to add custom attributes.
"""
startTime = str(self.startTime)[:19]
duration = str(self.stopTime - self.startTime)
status = []
- if result.success_count: status.append('Success %s' % result.success_count)
+ if result.success_count: status.append('Pass %s' % result.success_count)
if result.failure_count: status.append('Failure %s' % result.failure_count)
if result.error_count: status.append('Error %s' % result.error_count )
if status:
status = ' '.join(status)
else:
status = 'none'
-
- return [('Start Time', startTime),
- ('Duration', duration),
- ('Status', status),
- ] + self.report_attrs
+ return [
+ ('Start Time', startTime),
+ ('Duration', duration),
+ ('Status', status),
+ ]
def generateReport(self, test, result):
elif n == 1: nf += 1
else: ne += 1
+ # format class description
+ if cls.__module__ == "__main__":
+ name = cls.__name__
+ else:
+ name = "%s.%s" % (cls.__module__, cls.__name__)
+ doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
+ desc = doc and '%s: %s' % (name, doc) or name
+
row = self.REPORT_CLASS_TMPL % dict(
style = ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
- name = "%s.%s" % (cls.__module__, cls.__name__),
+ desc = desc,
count = np+nf+ne,
Pass = np,
fail = nf,
has_output = bool(o or e)
tid = (n == 0 and 'p' or 'f') + '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
tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
- row = tmpl % dict(
- tid = tid,
- Class = (n == 0 and 'hiddenRow' or ''),
- style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or ''),
- name = name,
- status = self.STATUS[n],
- )
- rows.append(row)
- if not has_output:
- return
# o and e should be byte string because they are collected from stdout and stderr?
if isinstance(o,str):
else:
ue = e
- row = self.REPORT_TEST_OUTPUT_TMPL % dict(
+ script = self.REPORT_TEST_OUTPUT_TMPL % dict(
id = tid,
- output = jsEscapeString(uo+ue),
+ output = saxutils.escape(uo+ue),
)
- rows.append(row)
+ row = tmpl % dict(
+ tid = tid,
+ Class = (n == 0 and 'hiddenRow' or 'none'),
+ style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none'),
+ desc = desc,
+ script = script,
+ status = self.STATUS[n],
+ )
+ rows.append(row)
+ if not has_output:
+ return
def _generate_ending(self):
return self.ENDING_TMPL