From 67bd83d12ebe65ee8ca0222110661442a58a056b Mon Sep 17 00:00:00 2001 From: Charles Machalow Date: Thu, 17 Sep 2020 13:36:46 -0700 Subject: [PATCH] Post-process html to allow teardown to be included in html output --- CHANGES.rst | 4 ++ pytest_html/plugin.py | 77 ++++++++++++++++++++++++++++++++----- testing/test_pytest_html.py | 56 +++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 9 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d3dc3604..813439ed 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -15,6 +15,10 @@ Release Notes * Thanks to `@Zac-HD `_ for the fix +* Post process HTML generation to allow teardown to appear in the HTML output. (`#131 `_) + + * Thanks to `@iwanb `_ for reporting and `@csm10495 `_ for the fix + **2.1.1 (2020-03-18)** * Fix issue with funcargs causing failures. (`#282 `_) diff --git a/pytest_html/plugin.py b/pytest_html/plugin.py index dd45d576..2776c250 100644 --- a/pytest_html/plugin.py +++ b/pytest_html/plugin.py @@ -11,6 +11,7 @@ import warnings from base64 import b64decode from base64 import b64encode +from collections import defaultdict from collections import OrderedDict from functools import lru_cache from html import escape @@ -148,6 +149,7 @@ def __init__(self, logfile, config): self.rerun = 0 if has_rerun else None self.self_contained = config.getoption("self_contained_html") self.config = config + self.reports = defaultdict(list) class TestResult: def __init__(self, outcome, report, logfile, config): @@ -279,7 +281,12 @@ def append_extra_html(self, extra, extra_index, test_index): def append_log_html(self, report, additional_html): log = html.div(class_="log") if report.longrepr: - for line in report.longreprtext.splitlines(): + # longreprtext is only filled out on failure by pytest + # otherwise will be None. + # Use full_text if longreprtext is None-ish + # we added full_text elsewhere in this file. + text = report.longreprtext or report.full_text + for line in text.splitlines(): separator = line.startswith("_ " * 10) if separator: log.append(line[:80]) @@ -620,15 +627,66 @@ def _save_report(self, report_content): with open(style_path, "w", encoding="utf-8") as f: f.write(self.style_css) + def _post_process_reports(self): + for test_name, test_reports in self.reports.items(): + outcome = "passed" + wasxfail = False + failure_when = None + full_text = "" + extras = [] + duration = 0.0 + + # in theory the last one should have all logs so we just go + # through them all to figure out the outcome, xfail, duration, + # extras, and when it swapped from pass + for test_report in test_reports: + full_text += test_report.longreprtext + extras.extend(getattr(test_report, "extra", [])) + duration += getattr(test_report, "duration", 0.0) + + if ( + test_report.outcome not in ("passed", "rerun") + and outcome == "passed" + ): + outcome = test_report.outcome + failure_when = test_report.when + + if hasattr(test_report, "wasxfail"): + wasxfail = True + + if test_report.outcome == "rerun": + self.append_other(test_report) + + # the following test_report. = settings come at the end of us + # looping through all test_reports that make up a single + # case. + + # outcome on the right comes from the outcome of the various + # test_reports that make up this test case + # we are just carrying it over to the final report. + test_report.outcome = outcome + test_report.when = "call" + test_report.nodeid = test_name + test_report.longrepr = full_text + test_report.extra = extras + test_report.duration = duration + + if wasxfail: + test_report.wasxfail = True + + if test_report.outcome == "passed": + self.append_passed(test_report) + elif test_report.outcome == "skipped": + self.append_skipped(test_report) + elif test_report.outcome == "failed": + test_report.when = failure_when + self.append_failed(test_report) + + # we don't append other here since the only case supported + # for append_other is rerun, which is handled in the loop above + def pytest_runtest_logreport(self, report): - if report.passed: - self.append_passed(report) - elif report.failed: - self.append_failed(report) - elif report.skipped: - self.append_skipped(report) - else: - self.append_other(report) + self.reports[report.nodeid].append(report) def pytest_collectreport(self, report): if report.failed: @@ -638,6 +696,7 @@ def pytest_sessionstart(self, session): self.suite_start_time = time.time() def pytest_sessionfinish(self, session): + self._post_process_reports() report_content = self._generate_report(session) self._save_report(report_content) diff --git a/testing/test_pytest_html.py b/testing/test_pytest_html.py index c36c2d27..0e814910 100644 --- a/testing/test_pytest_html.py +++ b/testing/test_pytest_html.py @@ -980,3 +980,59 @@ def pytest_html_report_title(report): result, html = run(testdir) assert result.ret == 0 assert len(re.findall(content_report_title, html)) == 1 + + def test_setup_and_teardown_in_html(self, testdir): + testdir.makepyfile( + """ + import pytest + @pytest.fixture(scope="function") + def setupAndTeardown(): + print ("this is setup") + yield + print ("this is teardown") + + def test_setup_and_teardown(setupAndTeardown): + print ("this is the test case") + """ + ) + result, html = run(testdir) + assert result.ret == 0 + assert_results(html, tests=1, passed=1) + assert "this is setup" in html + assert "this is teardown" in html + assert "this is the test case" in html + + def test_setup_failures_are_errors(self, testdir): + testdir.makepyfile( + """ + import pytest + @pytest.fixture(scope="function") + def setup(): + assert 0, "failure!" + + def test_setup(setup): + print ("this is the test case") + """ + ) + result, html = run(testdir) + assert result.ret == 1 + assert_results(html, tests=0, passed=0, errors=1) + assert "this is the test case" not in html + + def test_teardown_failures_are_errors(self, testdir): + testdir.makepyfile( + """ + import pytest + @pytest.fixture(scope="function") + def teardown(): + yield + assert 0, "failure!" + + def test_setup(teardown): + print ("this is the test case") + """ + ) + result, html = run(testdir) + assert result.ret == 1 + assert_results(html, tests=0, passed=0, errors=1) + assert "this is the test case" in html