Skip to content

Commit 3e6d07e

Browse files
committed
Post-process html to allow teardown to be included in html output
1 parent c5a8263 commit 3e6d07e

File tree

3 files changed

+131
-10
lines changed

3 files changed

+131
-10
lines changed

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Release Notes
1515

1616
* Thanks to `@Zac-HD <https://github.com/Zac-HD>`_ for the fix
1717

18+
* Post process HTML generation to allow teardown to appear in the HTML output. (`#131 <https://github.com/pytest-dev/pytest-html/issues/131>`_)
19+
20+
* Thanks to `@iwanb <https://github.com/iwanb>`_ for reporting and `@csm10495 <https://github.com/csm10495>`_ for the fix
21+
1822
**2.1.1 (2020-03-18)**
1923

2024
* Fix issue with funcargs causing failures. (`#282 <https://github.com/pytest-dev/pytest-html/issues/282>`_)

pytest_html/plugin.py

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
import re
1010
import time
1111
import warnings
12+
1213
from base64 import b64decode
1314
from base64 import b64encode
15+
from collections import defaultdict
1416
from collections import OrderedDict
1517
from functools import lru_cache
1618
from html import escape
@@ -148,6 +150,7 @@ def __init__(self, logfile, config):
148150
self.rerun = 0 if has_rerun else None
149151
self.self_contained = config.getoption("self_contained_html")
150152
self.config = config
153+
self.reports = defaultdict(list)
151154

152155
class TestResult:
153156
def __init__(self, outcome, report, logfile, config):
@@ -279,7 +282,12 @@ def append_extra_html(self, extra, extra_index, test_index):
279282
def append_log_html(self, report, additional_html):
280283
log = html.div(class_="log")
281284
if report.longrepr:
282-
for line in report.longreprtext.splitlines():
285+
# longreprtext is only filled out on failure by pytest
286+
# otherwise will be None.
287+
# Use full_text if longreprtext is None-ish
288+
# we added full_text elsewhere in this file.
289+
text = report.longreprtext or report.full_text
290+
for line in text.splitlines():
283291
separator = line.startswith("_ " * 10)
284292
if separator:
285293
log.append(line[:80])
@@ -620,15 +628,66 @@ def _save_report(self, report_content):
620628
with open(style_path, "w", encoding="utf-8") as f:
621629
f.write(self.style_css)
622630

631+
def _post_process_reports(self):
632+
for test_name, test_reports in self.reports.items():
633+
outcome = "passed"
634+
wasxfail = False
635+
failure_when = None
636+
full_text = ""
637+
extras = []
638+
duration = 0.0
639+
640+
# in theory the last one should have all logs so we just go
641+
# through them all to figure out the outcome, xfail, duration,
642+
# extras, and when it swapped from pass
643+
for test_report in test_reports:
644+
full_text += test_report.longreprtext
645+
extras.extend(getattr(test_report, "extra", []))
646+
duration += getattr(test_report, "duration", 0.0)
647+
648+
if (
649+
test_report.outcome not in ("passed", "rerun")
650+
and outcome == "passed"
651+
):
652+
outcome = test_report.outcome
653+
failure_when = test_report.when
654+
655+
if hasattr(test_report, "wasxfail"):
656+
wasxfail = True
657+
658+
if test_report.outcome == "rerun":
659+
self.append_other(test_report)
660+
661+
# the following test_report.<X> = settings come at the end of us
662+
# looping through all test_reports that make up a single
663+
# case.
664+
665+
# outcome on the right comes from the outcome of the various
666+
# test_reports that make up this test case
667+
# we are just carrying it over to the final report.
668+
test_report.outcome = outcome
669+
test_report.when = "call"
670+
test_report.nodeid = test_name
671+
test_report.longrepr = full_text
672+
test_report.extra = extras
673+
test_report.duration = duration
674+
675+
if wasxfail:
676+
test_report.wasxfail = True
677+
678+
if test_report.outcome == "passed":
679+
self.append_passed(test_report)
680+
elif test_report.outcome == "skipped":
681+
self.append_skipped(test_report)
682+
elif test_report.outcome == "failed":
683+
test_report.when = failure_when
684+
self.append_failed(test_report)
685+
686+
# we don't append other here since the only case supported
687+
# for append_other is rerun, which is handled in the loop above
688+
623689
def pytest_runtest_logreport(self, report):
624-
if report.passed:
625-
self.append_passed(report)
626-
elif report.failed:
627-
self.append_failed(report)
628-
elif report.skipped:
629-
self.append_skipped(report)
630-
else:
631-
self.append_other(report)
690+
self.reports[report.nodeid].append(report)
632691

633692
def pytest_collectreport(self, report):
634693
if report.failed:
@@ -638,6 +697,7 @@ def pytest_sessionstart(self, session):
638697
self.suite_start_time = time.time()
639698

640699
def pytest_sessionfinish(self, session):
700+
self._post_process_reports()
641701
report_content = self._generate_report(session)
642702
self._save_report(report_content)
643703

testing/test_pytest_html.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,8 @@ def test_foo(val):
809809
assert_results(html, passed=1)
810810

811811
@pytest.mark.parametrize(
812-
"with_ansi", [True, False],
812+
"with_ansi",
813+
[True, False],
813814
)
814815
def test_ansi_color(self, testdir, mocker, with_ansi):
815816
if not with_ansi:
@@ -980,3 +981,59 @@ def pytest_html_report_title(report):
980981
result, html = run(testdir)
981982
assert result.ret == 0
982983
assert len(re.findall(content_report_title, html)) == 1
984+
985+
def test_setup_and_teardown_in_html(self, testdir):
986+
testdir.makepyfile(
987+
"""
988+
import pytest
989+
@pytest.fixture(scope="function")
990+
def setupAndTeardown():
991+
print ("this is setup")
992+
yield
993+
print ("this is teardown")
994+
995+
def test_setup_and_teardown(setupAndTeardown):
996+
print ("this is the test case")
997+
"""
998+
)
999+
result, html = run(testdir)
1000+
assert result.ret == 0
1001+
assert_results(html, tests=1, passed=1)
1002+
assert "this is setup" in html
1003+
assert "this is teardown" in html
1004+
assert "this is the test case" in html
1005+
1006+
def test_setup_failures_are_errors(self, testdir):
1007+
testdir.makepyfile(
1008+
"""
1009+
import pytest
1010+
@pytest.fixture(scope="function")
1011+
def setup():
1012+
assert 0, "failure!"
1013+
1014+
def test_setup(setup):
1015+
print ("this is the test case")
1016+
"""
1017+
)
1018+
result, html = run(testdir)
1019+
assert result.ret == 1
1020+
assert_results(html, tests=0, passed=0, errors=1)
1021+
assert "this is the test case" not in html
1022+
1023+
def test_teardown_failures_are_errors(self, testdir):
1024+
testdir.makepyfile(
1025+
"""
1026+
import pytest
1027+
@pytest.fixture(scope="function")
1028+
def teardown():
1029+
yield
1030+
assert 0, "failure!"
1031+
1032+
def test_setup(teardown):
1033+
print ("this is the test case")
1034+
"""
1035+
)
1036+
result, html = run(testdir)
1037+
assert result.ret == 1
1038+
assert_results(html, tests=0, passed=0, errors=1)
1039+
assert "this is the test case" in html

0 commit comments

Comments
 (0)