From f717d96c54919931504c82436de57d770d70f883 Mon Sep 17 00:00:00 2001 From: Willem Adnet Date: Sat, 9 May 2026 11:29:13 +0200 Subject: [PATCH 1/3] terminal: add --name-only option to suppress tracebacks Add a new CLI argument --name-only that displays only the test name or collection error headline when a test fails. This is useful for quickly identifying failures in large test suites without the noise of full tracebacks or capture sections. - Register --name-only in pytest_addoption. - Modify summary_failures_combined and summary_errors to honor the flag. - Add regression tests in testing/test_name_only.py. --- changelog/14443.feature.rst | 1 + src/_pytest/terminal.py | 26 +++++++++--- testing/test_name_only.py | 81 +++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 changelog/14443.feature.rst create mode 100644 testing/test_name_only.py diff --git a/changelog/14443.feature.rst b/changelog/14443.feature.rst new file mode 100644 index 00000000000..bfdf1cfc153 --- /dev/null +++ b/changelog/14443.feature.rst @@ -0,0 +1 @@ +Added new CLI argument ``--name-only`` to only display the name of the test when it fails, suppressing tracebacks and other detailed information. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index b9a65ff191e..79f3a6d12ce 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -247,6 +247,13 @@ def pytest_addoption(parser: Parser) -> None: help="Controls how captured stdout/stderr/log is shown on failed tests. " "Default: all.", ) + group.addoption( + "--name-only", + action="store_true", + default=False, + dest="name_only", + help="Only display the name of the test when it fails.", + ) group.addoption( "--fulltrace", "--full-trace", @@ -1212,14 +1219,18 @@ def summary_failures_combined( if style == "line": for rep in reports: line = self._getcrashline(rep) - self._outrep_summary(rep) + if not self.config.option.name_only: + self._outrep_summary(rep) self.write_line(line) else: for rep in reports: msg = self._getfailureheadline(rep) - self.write_sep("_", msg, red=True, bold=True) - self._outrep_summary(rep) - self._handle_teardown_sections(rep.nodeid) + if self.config.option.name_only: + self._tw.line(msg, red=True, bold=True) + else: + self.write_sep("_", msg, red=True, bold=True) + self._outrep_summary(rep) + self._handle_teardown_sections(rep.nodeid) def summary_errors(self) -> None: if self.config.option.tbstyle != "no": @@ -1233,8 +1244,11 @@ def summary_errors(self) -> None: msg = "ERROR collecting " + msg else: msg = f"ERROR at {rep.when} of {msg}" - self.write_sep("_", msg, red=True, bold=True) - self._outrep_summary(rep) + if self.config.option.name_only: + self._tw.line(msg, red=True, bold=True) + else: + self.write_sep("_", msg, red=True, bold=True) + self._outrep_summary(rep) def _outrep_summary(self, rep: BaseReport) -> None: rep.toterminal(self._tw) diff --git a/testing/test_name_only.py b/testing/test_name_only.py new file mode 100644 index 00000000000..26b642c6353 --- /dev/null +++ b/testing/test_name_only.py @@ -0,0 +1,81 @@ +import pytest + +class TestNameOnly: + def test_name_only_failures(self, pytester: pytest.Pytester) -> None: + pytester.makepyfile( + """ + def test_fail1(): + assert False + def test_fail2(): + assert False + def test_pass(): + assert True + """ + ) + result = pytester.runpytest("--name-only") + result.stdout.fnmatch_lines([ + "=* FAILURES *=", + "test_fail1", + "test_fail2", + "=* short test summary info *=", + ]) + + output = result.stdout.str() + failures_part = output.split("FAILURES")[1].split("short test summary info")[0] + assert "test_fail1" in failures_part + assert "test_fail2" in failures_part + assert "assert False" not in failures_part + assert "E assert False" not in failures_part + + def test_name_only_errors(self, pytester: pytest.Pytester) -> None: + p = pytester.makepyfile(test_error=""" + import pytest + @pytest.fixture + def bad_fixture(): + raise RuntimeError("error in fixture") + def test_error(bad_fixture): + pass + """) + result = pytester.runpytest(p, "--name-only") + result.stdout.fnmatch_lines([ + "=* ERRORS *=", + "ERROR at setup of test_error", + "=* short test summary info *=", + "ERROR test_error.py::test_error - RuntimeError: error in fixture", + ]) + output = result.stdout.str() + errors_part = output.split("ERRORS")[1].split("short test summary info")[0] + assert "ERROR at setup of test_error" in errors_part + assert "RuntimeError: error in fixture" not in errors_part + + def test_name_only_collection_error(self, pytester: pytest.Pytester) -> None: + pytester.makepyfile( + """ + def test_syntax(): + assert + """ + ) + result = pytester.runpytest("--name-only") + result.stdout.fnmatch_lines([ + "=* ERRORS *=", + "ERROR collecting test_name_only_collection_error.py", + "=* short test summary info *=", + "ERROR test_name_only_collection_error.py", + ]) + output = result.stdout.str() + errors_part = output.split("ERRORS")[1].split("short test summary info")[0] + assert "ERROR collecting test_name_only_collection_error.py" in errors_part + assert "SyntaxError" not in errors_part + + def test_name_only_with_tb_line(self, pytester: pytest.Pytester) -> None: + pytester.makepyfile( + """ + def test_fail(): + assert False + """ + ) + result = pytester.runpytest("--name-only", "--tb=line") + result.stdout.fnmatch_lines([ + "=* FAILURES *=", + "*test_name_only_with_tb_line.py*: assert False", + ]) From 98c06dc09d3f5a1a936c6b172d63f1fa94fb238f Mon Sep 17 00:00:00 2001 From: Willem Adnet Date: Sat, 9 May 2026 11:33:36 +0200 Subject: [PATCH 2/3] docs: add --name-only to reference and update AUTHORS Co-authored-by: Gemini CLI --- AUTHORS | 1 + doc/en/reference/reference.rst | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/AUTHORS b/AUTHORS index f3cf1b0facb..c1b0117df64 100644 --- a/AUTHORS +++ b/AUTHORS @@ -503,6 +503,7 @@ Warren Markham Wei Lin Wil Cooley Will Riley +Willem Adnet William Lee Wim Glenn Wouter van Ackooy diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index a69aa2c7887..6e527eb84ba 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -3074,6 +3074,10 @@ Output and Reporting * ``log``: show captured logging * ``all`` (default): show all captured output +.. option:: --name-only + + Only display the name of the test when it fails, suppressing tracebacks and other detailed information. + .. option:: --color=WHEN Color terminal output: From b15932f1c620f7ca1b7bd12d1b31f6384e291384 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 09:40:57 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/test_name_only.py | 63 +++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/testing/test_name_only.py b/testing/test_name_only.py index 26b642c6353..11a32ed880c 100644 --- a/testing/test_name_only.py +++ b/testing/test_name_only.py @@ -1,5 +1,8 @@ +from __future__ import annotations + import pytest + class TestNameOnly: def test_name_only_failures(self, pytester: pytest.Pytester) -> None: pytester.makepyfile( @@ -13,13 +16,15 @@ def test_pass(): """ ) result = pytester.runpytest("--name-only") - result.stdout.fnmatch_lines([ - "=* FAILURES *=", - "test_fail1", - "test_fail2", - "=* short test summary info *=", - ]) - + result.stdout.fnmatch_lines( + [ + "=* FAILURES *=", + "test_fail1", + "test_fail2", + "=* short test summary info *=", + ] + ) + output = result.stdout.str() failures_part = output.split("FAILURES")[1].split("short test summary info")[0] assert "test_fail1" in failures_part @@ -28,21 +33,25 @@ def test_pass(): assert "E assert False" not in failures_part def test_name_only_errors(self, pytester: pytest.Pytester) -> None: - p = pytester.makepyfile(test_error=""" + p = pytester.makepyfile( + test_error=""" import pytest @pytest.fixture def bad_fixture(): raise RuntimeError("error in fixture") def test_error(bad_fixture): pass - """) + """ + ) result = pytester.runpytest(p, "--name-only") - result.stdout.fnmatch_lines([ - "=* ERRORS *=", - "ERROR at setup of test_error", - "=* short test summary info *=", - "ERROR test_error.py::test_error - RuntimeError: error in fixture", - ]) + result.stdout.fnmatch_lines( + [ + "=* ERRORS *=", + "ERROR at setup of test_error", + "=* short test summary info *=", + "ERROR test_error.py::test_error - RuntimeError: error in fixture", + ] + ) output = result.stdout.str() errors_part = output.split("ERRORS")[1].split("short test summary info")[0] assert "ERROR at setup of test_error" in errors_part @@ -56,12 +65,14 @@ def test_syntax(): """ ) result = pytester.runpytest("--name-only") - result.stdout.fnmatch_lines([ - "=* ERRORS *=", - "ERROR collecting test_name_only_collection_error.py", - "=* short test summary info *=", - "ERROR test_name_only_collection_error.py", - ]) + result.stdout.fnmatch_lines( + [ + "=* ERRORS *=", + "ERROR collecting test_name_only_collection_error.py", + "=* short test summary info *=", + "ERROR test_name_only_collection_error.py", + ] + ) output = result.stdout.str() errors_part = output.split("ERRORS")[1].split("short test summary info")[0] assert "ERROR collecting test_name_only_collection_error.py" in errors_part @@ -75,7 +86,9 @@ def test_fail(): """ ) result = pytester.runpytest("--name-only", "--tb=line") - result.stdout.fnmatch_lines([ - "=* FAILURES *=", - "*test_name_only_with_tb_line.py*: assert False", - ]) + result.stdout.fnmatch_lines( + [ + "=* FAILURES *=", + "*test_name_only_with_tb_line.py*: assert False", + ] + )