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