Add hooks for modifying the test results table
This commit is contained in:
parent
a1413ed734
commit
bc2e8a3555
|
@ -3,6 +3,7 @@ Release Notes
|
|||
|
||||
**1.14.0 (unreleased)**
|
||||
|
||||
* Add hooks for modifying the test results table
|
||||
* Replace environment section with values from
|
||||
`pytest-metadata <https://pypi.python.org/pypi/pytest-metadata/>`_
|
||||
* Fix encoding for asset files
|
||||
|
|
59
README.rst
59
README.rst
|
@ -128,6 +128,65 @@ conftest.py file:
|
|||
extra.append(pytest_html.extras.html('<div>Additional HTML</div>'))
|
||||
report.extra = extra
|
||||
|
||||
Modifying the results table
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can modify the columns by implementing custom hooks for the header and
|
||||
rows. The following example :code:`conftest.py` adds a description column with
|
||||
the test function docstring, adds a sortable time column, and removes the links
|
||||
column:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datetime import datetime
|
||||
from py.xml import html
|
||||
import pytest
|
||||
|
||||
@pytest.mark.optionalhook
|
||||
def pytest_html_results_table_header(cells):
|
||||
cells.insert(2, html.th('Description'))
|
||||
cells.insert(0, html.th('Time', class_='sortable time', col='time'))
|
||||
cells.pop()
|
||||
|
||||
@pytest.mark.optionalhook
|
||||
def pytest_html_results_table_row(report, cells):
|
||||
cells.insert(2, html.td(report.description))
|
||||
cells.insert(1, html.td(datetime.utcnow(), class_='col-time'))
|
||||
cells.pop()
|
||||
|
||||
@pytest.mark.hookwrapper
|
||||
def pytest_runtest_makereport(item, call):
|
||||
outcome = yield
|
||||
report = outcome.get_result()
|
||||
report.description = str(item.function.__doc__)
|
||||
|
||||
You can also remove results by implementing the
|
||||
:code:`pytest_html_results_table_row` hook and removing all cells. The
|
||||
following example removes all passed results from the report:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.mark.optionalhook
|
||||
def pytest_html_results_table_row(report, cells):
|
||||
if report.passed:
|
||||
del cells[:]
|
||||
|
||||
The log output and additional HTML can be modified by implementing the
|
||||
:code:`pytest_html_results_html` hook. The following example replaces all
|
||||
additional HTML and log output with a notice that the log is empty:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.mark.optionalhook
|
||||
def pytest_html_results_table_html(report, data):
|
||||
if report.passed:
|
||||
del data[:]
|
||||
data.append(html.div('No log output captured.', class_='empty log'))
|
||||
|
||||
Screenshots
|
||||
-----------
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
def pytest_html_results_table_header(cells):
|
||||
""" Called after building results table header. """
|
||||
|
||||
|
||||
def pytest_html_results_table_row(report, cells):
|
||||
""" Called after building results table row. """
|
||||
|
||||
|
||||
def pytest_html_results_table_html(report, data):
|
||||
""" Called after building results table additional HTML. """
|
|
@ -37,6 +37,11 @@ else:
|
|||
from cgi import escape
|
||||
|
||||
|
||||
def pytest_addhooks(pluginmanager):
|
||||
from . import hooks
|
||||
pluginmanager.add_hookspecs(hooks)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup('terminal reporting')
|
||||
group.addoption('--html', action='store', dest='htmlpath',
|
||||
|
@ -54,10 +59,7 @@ def pytest_configure(config):
|
|||
htmlpath = config.option.htmlpath
|
||||
# prevent opening htmlpath on slave nodes (xdist)
|
||||
if htmlpath and not hasattr(config, 'slaveinput'):
|
||||
config._html = HTMLReport(htmlpath,
|
||||
config.getoption('self_contained_html'),
|
||||
config.pluginmanager
|
||||
.hasplugin('rerunfailures'))
|
||||
config._html = HTMLReport(htmlpath, config)
|
||||
config.pluginmanager.register(config._html)
|
||||
|
||||
|
||||
|
@ -78,7 +80,7 @@ def data_uri(content, mime_type='text/plain', charset='utf-8'):
|
|||
|
||||
class HTMLReport(object):
|
||||
|
||||
def __init__(self, logfile, self_contained, has_rerun):
|
||||
def __init__(self, logfile, config):
|
||||
logfile = os.path.expanduser(os.path.expandvars(logfile))
|
||||
self.logfile = os.path.abspath(logfile)
|
||||
self.test_logs = []
|
||||
|
@ -86,12 +88,14 @@ class HTMLReport(object):
|
|||
self.errors = self.failed = 0
|
||||
self.passed = self.skipped = 0
|
||||
self.xfailed = self.xpassed = 0
|
||||
has_rerun = config.pluginmanager.hasplugin('rerunfailures')
|
||||
self.rerun = 0 if has_rerun else None
|
||||
self.self_contained = self_contained
|
||||
self.self_contained = config.getoption('self_contained_html')
|
||||
self.config = config
|
||||
|
||||
class TestResult:
|
||||
|
||||
def __init__(self, outcome, report, self_contained, logfile):
|
||||
def __init__(self, outcome, report, logfile, config):
|
||||
self.test_id = report.nodeid
|
||||
if report.when != 'call':
|
||||
self.test_id = '::'.join([report.nodeid, report.when])
|
||||
|
@ -99,8 +103,10 @@ class HTMLReport(object):
|
|||
self.outcome = outcome
|
||||
self.additional_html = []
|
||||
self.links_html = []
|
||||
self.self_contained = self_contained
|
||||
self.self_contained = config.getoption('self_contained_html')
|
||||
self.logfile = logfile
|
||||
self.config = config
|
||||
self.row_table = self.row_extra = None
|
||||
|
||||
test_index = hasattr(report, 'rerun') and report.rerun + 1 or 0
|
||||
|
||||
|
@ -109,14 +115,22 @@ class HTMLReport(object):
|
|||
|
||||
self.append_log_html(report, self.additional_html)
|
||||
|
||||
self.row_table = html.tr([
|
||||
cells = [
|
||||
html.td(self.outcome, class_='col-result'),
|
||||
html.td(self.test_id, class_='col-name'),
|
||||
html.td('{0:.2f}'.format(self.time), class_='col-duration'),
|
||||
html.td(self.links_html, class_='col-links')])
|
||||
html.td(self.links_html, class_='col-links')]
|
||||
|
||||
self.row_extra = html.tr(html.td(self.additional_html,
|
||||
class_='extra', colspan='5'))
|
||||
self.config.hook.pytest_html_results_table_row(
|
||||
report=report, cells=cells)
|
||||
|
||||
self.config.hook.pytest_html_results_table_html(
|
||||
report=report, data=self.additional_html)
|
||||
|
||||
if len(cells) > 0:
|
||||
self.row_table = html.tr(cells)
|
||||
self.row_extra = html.tr(html.td(self.additional_html,
|
||||
class_='extra', colspan=len(cells)))
|
||||
|
||||
def __lt__(self, other):
|
||||
order = ('Error', 'Failed', 'Rerun', 'XFailed',
|
||||
|
@ -232,13 +246,16 @@ class HTMLReport(object):
|
|||
additional_html.append(log)
|
||||
|
||||
def _appendrow(self, outcome, report):
|
||||
result = self.TestResult(outcome, report, self.self_contained,
|
||||
self.logfile)
|
||||
index = bisect.bisect_right(self.results, result)
|
||||
self.results.insert(index, result)
|
||||
self.test_logs.insert(index, html.tbody(result.row_table,
|
||||
result.row_extra, class_=result.outcome.lower() +
|
||||
' results-table-row'))
|
||||
result = self.TestResult(outcome, report, self.logfile, self.config)
|
||||
if result.row_table is not None:
|
||||
index = bisect.bisect_right(self.results, result)
|
||||
self.results.insert(index, result)
|
||||
tbody = html.tbody(
|
||||
result.row_table,
|
||||
class_='{0} results-table-row'.format(result.outcome.lower()))
|
||||
if result.row_extra is not None:
|
||||
tbody.append(result.row_extra)
|
||||
self.test_logs.insert(index, tbody)
|
||||
|
||||
def append_passed(self, report):
|
||||
if report.when == 'call':
|
||||
|
@ -362,19 +379,20 @@ class HTMLReport(object):
|
|||
if i < len(outcomes):
|
||||
summary.append(', ')
|
||||
|
||||
cells = [
|
||||
html.th('Result',
|
||||
class_='sortable result initial-sort',
|
||||
col='result'),
|
||||
html.th('Test', class_='sortable', col='name'),
|
||||
html.th('Duration', class_='sortable numeric', col='duration'),
|
||||
html.th('Links')]
|
||||
session.config.hook.pytest_html_results_table_header(cells=cells)
|
||||
|
||||
results = [html.h2('Results'), html.table([html.thead(
|
||||
html.tr([
|
||||
html.th('Result',
|
||||
class_='sortable result initial-sort',
|
||||
col='result'),
|
||||
html.th('Test', class_='sortable', col='name'),
|
||||
html.th('Duration',
|
||||
class_='sortable numeric',
|
||||
col='duration'),
|
||||
html.th('Links')]),
|
||||
html.tr(cells),
|
||||
html.tr([
|
||||
html.th('No results found. Try to check the filters',
|
||||
colspan='5')],
|
||||
colspan=len(cells))],
|
||||
id='not-found-message', hidden='true'),
|
||||
id='results-table-head'),
|
||||
self.test_logs], id='results-table')]
|
||||
|
|
Loading…
Reference in New Issue