diff --git a/src/pytest_html/basereport.py b/src/pytest_html/basereport.py index 8d0e8de7..df6bc3f2 100644 --- a/src/pytest_html/basereport.py +++ b/src/pytest_html/basereport.py @@ -248,15 +248,17 @@ def _process_logs(report): log = [] if report.longreprtext: log.append(report.longreprtext.replace("<", "<").replace(">", ">") + "\n") - for section in report.sections: - header, content = section - log.append(f"{' ' + header + ' ':-^80}\n{content}") - - # weird formatting related to logs - if "log" in header: - log.append("") - if "call" in header: + # Don't add captured output to reruns + if report.outcome != "rerun": + for section in report.sections: + header, content = section + log.append(f"{' ' + header + ' ':-^80}\n{content}") + + # weird formatting related to logs + if "log" in header: log.append("") + if "call" in header: + log.append("") if not log: log.append("No log output captured.") return log diff --git a/src/pytest_html/report_data.py b/src/pytest_html/report_data.py index d797c7ca..8e6278de 100644 --- a/src/pytest_html/report_data.py +++ b/src/pytest_html/report_data.py @@ -67,7 +67,7 @@ def set_data(self, key, value): def add_test(self, test_data, report, logs): # regardless of pass or fail we must add teardown logging to "call" if report.when == "teardown": - self.update_test_log(report) + self.append_teardown_log(report) # passed "setup" and "teardown" are not added to the html if report.when == "call" or ( @@ -79,12 +79,13 @@ def add_test(self, test_data, report, logs): return False - def update_test_log(self, report): + def append_teardown_log(self, report): log = [] - for test in self._data["tests"][report.nodeid]: - if test["testId"] == report.nodeid and "log" in test: - for section in report.sections: - header, content = section - if "teardown" in header: - log.append(f"{' ' + header + ' ':-^80}\n{content}") - test["log"] += _handle_ansi("\n".join(log)) + if self._data["tests"][report.nodeid]: + # Last index is "call" + test = self._data["tests"][report.nodeid][-1] + for section in report.sections: + header, content = section + if "teardown" in header: + log.append(f"{' ' + header + ' ':-^80}\n{content}") + test["log"] += _handle_ansi("\n".join(log)) diff --git a/testing/test_integration.py b/testing/test_integration.py index a6e4b0e6..58f5d3a3 100644 --- a/testing/test_integration.py +++ b/testing/test_integration.py @@ -759,25 +759,29 @@ def log_cli(self, pytester): @pytest.fixture def test_file(self): - return """ - import pytest - import logging - @pytest.fixture - def setup(): - logging.info("this is setup") - {setup} - yield - logging.info("this is teardown") - {teardown} + def formatter(assertion, setup="", teardown="", flaky=""): + return f""" + import pytest + import logging + @pytest.fixture + def setup(): + logging.info("this is setup") + {setup} + yield + logging.info("this is teardown") + {teardown} + + {flaky} + def test_logging(setup): + logging.info("this is test") + assert {assertion} + """ - def test_logging(setup): - logging.info("this is test") - assert {assertion} - """ + return formatter @pytest.mark.usefixtures("log_cli") def test_all_pass(self, test_file, pytester): - pytester.makepyfile(test_file.format(setup="", teardown="", assertion=True)) + pytester.makepyfile(test_file(assertion=True)) page = run(pytester) assert_results(page, passed=1) @@ -787,9 +791,7 @@ def test_all_pass(self, test_file, pytester): @pytest.mark.usefixtures("log_cli") def test_setup_error(self, test_file, pytester): - pytester.makepyfile( - test_file.format(setup="error", teardown="", assertion=True) - ) + pytester.makepyfile(test_file(assertion=True, setup="error")) page = run(pytester) assert_results(page, error=1) @@ -800,7 +802,7 @@ def test_setup_error(self, test_file, pytester): @pytest.mark.usefixtures("log_cli") def test_test_fails(self, test_file, pytester): - pytester.makepyfile(test_file.format(setup="", teardown="", assertion=False)) + pytester.makepyfile(test_file(assertion=False)) page = run(pytester) assert_results(page, failed=1) @@ -813,9 +815,7 @@ def test_test_fails(self, test_file, pytester): "assertion, result", [(True, {"passed": 1}), (False, {"failed": 1})] ) def test_teardown_error(self, test_file, pytester, assertion, result): - pytester.makepyfile( - test_file.format(setup="", teardown="error", assertion=assertion) - ) + pytester.makepyfile(test_file(assertion=assertion, teardown="error")) page = run(pytester) assert_results(page, error=1, **result) @@ -825,7 +825,7 @@ def test_teardown_error(self, test_file, pytester, assertion, result): assert_that(log).matches(self.LOG_LINE_REGEX.format(when)) def test_no_log(self, test_file, pytester): - pytester.makepyfile(test_file.format(setup="", teardown="", assertion=True)) + pytester.makepyfile(test_file(assertion=True)) page = run(pytester) assert_results(page, passed=1) @@ -834,6 +834,18 @@ def test_no_log(self, test_file, pytester): for when in ["setup", "test", "teardown"]: assert_that(log).does_not_match(self.LOG_LINE_REGEX.format(when)) + @pytest.mark.usefixtures("log_cli") + def test_rerun(self, test_file, pytester): + pytester.makepyfile( + test_file(assertion=False, flaky="@pytest.mark.flaky(reruns=2)") + ) + page = run(pytester, query_params={"visible": "failed"}) + assert_results(page, failed=1, rerun=2) + + log = get_log(page) + assert_that(log.count("Captured log setup")).is_equal_to(3) + assert_that(log.count("Captured log teardown")).is_equal_to(5) + class TestCollapsedQueryParam: @pytest.fixture