From f31caab723b52f61c7d8964ceb362bde5bf36c23 Mon Sep 17 00:00:00 2001 From: roqia salah Date: Wed, 12 Aug 2026 20:05:37 -0400 Subject: [PATCH] fix(terminal): show teardown phase for xfail reports Co-authored-by: ChatGPT --- AUTHORS | 1 + changelog/6997.bugfix.rst | 2 ++ src/_pytest/terminal.py | 7 +++++- testing/test_skipping.py | 50 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 changelog/6997.bugfix.rst diff --git a/AUTHORS b/AUTHORS index d43b4132478..0eaeb4d6423 100644 --- a/AUTHORS +++ b/AUTHORS @@ -420,6 +420,7 @@ Roland Puntaier Romain Dorgueil Roman Bolshakov Ronny Pfannschmidt +Roqia Salah Ross Lawley Ruaridh Williamson Russel Winder diff --git a/changelog/6997.bugfix.rst b/changelog/6997.bugfix.rst new file mode 100644 index 00000000000..2364d541fde --- /dev/null +++ b/changelog/6997.bugfix.rst @@ -0,0 +1,2 @@ +XFAIL reports from teardown phases now include the phase in the short test +summary. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 852153b9215..e9aa36e9421 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -1328,7 +1328,12 @@ def show_xfailed(lines: list[str]) -> None: ) markup_word = self._tw.markup(verbose_word, **verbose_markup) nodeid = _get_node_id_with_markup(self._tw, self.config, rep) - line = f"{markup_word} {nodeid}" + + if rep.when == "call": + line = f"{markup_word} {nodeid}" + else: + line = f"{markup_word} at {rep.when} of {nodeid}" + reason = rep.wasxfail if reason: line += " - " + str(reason) diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 5bb641aed3c..41d3d4ae01d 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -766,6 +766,56 @@ def test_func(): result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 xfail*"]) + def test_xfail_call_and_teardown_reports_show_phase( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + test_case=""" + import pytest + + @pytest.fixture + def my_fix(): + yield + raise Exception("teardown") + + @pytest.mark.xfail(reason="Some reason") + def test_func(my_fix): + raise Exception("call") + """ + ) + + result = pytester.runpytest("-rx") + + result.stdout.fnmatch_lines( + [ + "*XFAIL*test_case.py::test_func*", + "*XFAIL at teardown of*test_case.py::test_func*", + ] + ) + + def test_xfail_setup_report_shows_phase(self, pytester: Pytester) -> None: + pytester.makepyfile( + test_case=""" + import pytest + + @pytest.fixture + def my_fix(): + raise Exception("setup") + + @pytest.mark.xfail(reason="Some reason") + def test_func(my_fix): + pass + """ + ) + + result = pytester.runpytest("-rx") + + result.stdout.fnmatch_lines( + [ + "*XFAIL at setup of*test_case.py::test_func*", + ] + ) + class TestSkip: def test_skip_class(self, pytester: Pytester) -> None: