Skip to content

Commit 67bd83d

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

File tree

3 files changed

+128
-9
lines changed

3 files changed

+128
-9
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: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import warnings
1212
from base64 import b64decode
1313
from base64 import b64encode
14+
from collections import defaultdict
1415
from collections import OrderedDict
1516
from functools import lru_cache
1617
from html import escape
@@ -148,6 +149,7 @@ def __init__(self, logfile, config):
148149
self.rerun = 0 if has_rerun else None
149150
self.self_contained = config.getoption("self_contained_html")
150151
self.config = config
152+
self.reports = defaultdict(list)
151153

152154
class TestResult:
153155
def __init__(self, outcome, report, logfile, config):
@@ -279,7 +281,12 @@ def append_extra_html(self, extra, extra_index, test_index):
279281
def append_log_html(self, report, additional_html):
280282
log = html.div(class_="log")
281283
if report.longrepr:
282-
for line in report.longreprtext.splitlines():
284+
# longreprtext is only filled out on failure by pytest
285+
# otherwise will be None.
286+
# Use full_text if longreprtext is None-ish
287+
# we added full_text elsewhere in this file.
288+
text = report.longreprtext or report.full_text
289+
for line in text.splitlines():
283290
separator = line.startswith("_ " * 10)
284291
if separator:
285292
log.append(line[:80])
@@ -620,15 +627,66 @@ def _save_report(self, report_content):
620627
with open(style_path, "w", encoding="utf-8") as f:
621628
f.write(self.style_css)
622629

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

633691
def pytest_collectreport(self, report):
634692
if report.failed:
@@ -638,6 +696,7 @@ def pytest_sessionstart(self, session):
638696
self.suite_start_time = time.time()
639697

640698
def pytest_sessionfinish(self, session):
699+
self._post_process_reports()
641700
report_content = self._generate_report(session)
642701
self._save_report(report_content)
643702

testing/test_pytest_html.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,3 +980,59 @@ def pytest_html_report_title(report):
980980
result, html = run(testdir)
981981
assert result.ret == 0
982982
assert len(re.findall(content_report_title, html)) == 1
983+
984+
def test_setup_and_teardown_in_html(self, testdir):
985+
testdir.makepyfile(
986+
"""
987+
import pytest
988+
@pytest.fixture(scope="function")
989+
def setupAndTeardown():
990+
print ("this is setup")
991+
yield
992+
print ("this is teardown")
993+
994+
def test_setup_and_teardown(setupAndTeardown):
995+
print ("this is the test case")
996+
"""
997+
)
998+
result, html = run(testdir)
999+
assert result.ret == 0
1000+
assert_results(html, tests=1, passed=1)
1001+
assert "this is setup" in html
1002+
assert "this is teardown" in html
1003+
assert "this is the test case" in html
1004+
1005+
def test_setup_failures_are_errors(self, testdir):
1006+
testdir.makepyfile(
1007+
"""
1008+
import pytest
1009+
@pytest.fixture(scope="function")
1010+
def setup():
1011+
assert 0, "failure!"
1012+
1013+
def test_setup(setup):
1014+
print ("this is the test case")
1015+
"""
1016+
)
1017+
result, html = run(testdir)
1018+
assert result.ret == 1
1019+
assert_results(html, tests=0, passed=0, errors=1)
1020+
assert "this is the test case" not in html
1021+
1022+
def test_teardown_failures_are_errors(self, testdir):
1023+
testdir.makepyfile(
1024+
"""
1025+
import pytest
1026+
@pytest.fixture(scope="function")
1027+
def teardown():
1028+
yield
1029+
assert 0, "failure!"
1030+
1031+
def test_setup(teardown):
1032+
print ("this is the test case")
1033+
"""
1034+
)
1035+
result, html = run(testdir)
1036+
assert result.ret == 1
1037+
assert_results(html, tests=0, passed=0, errors=1)
1038+
assert "this is the test case" in html

0 commit comments

Comments
 (0)