From 0a8ed74b06425c9e1e10f5f9f69289c7148b9d23 Mon Sep 17 00:00:00 2001 From: Aleksandr Brodin Date: Wed, 28 Jan 2026 13:42:49 +0700 Subject: [PATCH 1/5] support subtest as step --- allure-pytest/src/listener.py | 14 +++++++ .../acceptance/step/subtest_as_step.py | 42 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/allure_pytest/acceptance/step/subtest_as_step.py diff --git a/allure-pytest/src/listener.py b/allure-pytest/src/listener.py index 21c06750..02e971b6 100644 --- a/allure-pytest/src/listener.py +++ b/allure-pytest/src/listener.py @@ -242,6 +242,20 @@ def pytest_runtest_makereport(self, item, call): if report.capstderr: self.attach_data(report.capstderr, "stderr", AttachmentType.TEXT, None) + @pytest.hookimpl(hookwrapper=True) + def pytest_runtest_logreport(self, report): + yield + if hasattr(report, "context"): + item_uuid = self._cache.get(report.nodeid) + step_uuid = uuid4() + step = TestStepResult(name=report.context.msg, start=report.start) + if report.longrepr: + status_details = StatusDetails(message=report.longrepr.reprcrash.message, trace=report.longreprtext) + else: + status_details = None + self.allure_logger.start_step(item_uuid, step_uuid, step) + self.allure_logger.stop_step(step_uuid, stop=report.stop, status=report.outcome, statusDetails=status_details) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_logfinish(self, nodeid, location): yield diff --git a/tests/allure_pytest/acceptance/step/subtest_as_step.py b/tests/allure_pytest/acceptance/step/subtest_as_step.py new file mode 100644 index 00000000..1352d11f --- /dev/null +++ b/tests/allure_pytest/acceptance/step/subtest_as_step.py @@ -0,0 +1,42 @@ +from hamcrest import assert_that +from tests.allure_pytest.pytest_runner import AllurePytestRunner + +from allure_commons_test.report import has_test_case +from allure_commons_test.result import has_step +from allure_commons_test.result import with_status +from allure_commons_test.result import with_message_contains +from allure_commons_test.result import has_status_details + + +def test_with_subtest(allure_pytest_runner: AllurePytestRunner): + """ + >>> import allure + >>> import pytest + >>> @pytest.mark.skipif("pytest.__version__[0] < '9'") + >>> def test_with_subtest(subtests): + ... with subtests.test(msg='Some failed subtest'): + ... assert False, 'Some error' + ... with allure.step('Next step'): + ... pass + """ + + allure_results = allure_pytest_runner.run_docstring() + + assert_that( + allure_results, + has_test_case( + "test_with_subtest", + has_step( + "Some failed subtest", + with_status("failed"), + has_status_details( + with_message_contains("Some error") + ) + ), + has_step( + "Next step", + with_status("passed") + ), + with_status("failed") + ) + ) From 1c592b9ce3fc63b8f90a55a227a210be2cf377a7 Mon Sep 17 00:00:00 2001 From: Aleksandr Brodin Date: Wed, 28 Jan 2026 13:58:10 +0700 Subject: [PATCH 2/5] fix ruff errors --- allure-pytest/src/listener.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/allure-pytest/src/listener.py b/allure-pytest/src/listener.py index 02e971b6..6997de86 100644 --- a/allure-pytest/src/listener.py +++ b/allure-pytest/src/listener.py @@ -250,11 +250,19 @@ def pytest_runtest_logreport(self, report): step_uuid = uuid4() step = TestStepResult(name=report.context.msg, start=report.start) if report.longrepr: - status_details = StatusDetails(message=report.longrepr.reprcrash.message, trace=report.longreprtext) + status_details = StatusDetails( + message=report.longrepr.reprcrash.message, + trace=report.longreprtext + ) else: status_details = None self.allure_logger.start_step(item_uuid, step_uuid, step) - self.allure_logger.stop_step(step_uuid, stop=report.stop, status=report.outcome, statusDetails=status_details) + self.allure_logger.stop_step( + step_uuid, + stop=report.stop, + status=report.outcome, + statusDetails=status_details + ) @pytest.hookimpl(hookwrapper=True) def pytest_runtest_logfinish(self, nodeid, location): From af7fec7749dcc10ed023070e393b5e5905ae4775 Mon Sep 17 00:00:00 2001 From: Aleksandr Brodin Date: Wed, 28 Jan 2026 16:22:28 +0700 Subject: [PATCH 3/5] use head_line as default step name if msg is empty --- allure-pytest/src/listener.py | 2 +- .../acceptance/step/subtest_as_step.py | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/allure-pytest/src/listener.py b/allure-pytest/src/listener.py index 6997de86..95a85d4a 100644 --- a/allure-pytest/src/listener.py +++ b/allure-pytest/src/listener.py @@ -248,7 +248,7 @@ def pytest_runtest_logreport(self, report): if hasattr(report, "context"): item_uuid = self._cache.get(report.nodeid) step_uuid = uuid4() - step = TestStepResult(name=report.context.msg, start=report.start) + step = TestStepResult(name=report.context.msg or report.head_line, start=report.start) if report.longrepr: status_details = StatusDetails( message=report.longrepr.reprcrash.message, diff --git a/tests/allure_pytest/acceptance/step/subtest_as_step.py b/tests/allure_pytest/acceptance/step/subtest_as_step.py index 1352d11f..fa11d061 100644 --- a/tests/allure_pytest/acceptance/step/subtest_as_step.py +++ b/tests/allure_pytest/acceptance/step/subtest_as_step.py @@ -40,3 +40,26 @@ def test_with_subtest(allure_pytest_runner: AllurePytestRunner): with_status("failed") ) ) + + +def test_with_subtest_without_msg(allure_pytest_runner: AllurePytestRunner): + """ + >>> import allure + >>> import pytest + >>> @pytest.mark.skipif("pytest.__version__[0] < '9'") + >>> def test_with_subtest_without_msg(subtests): + ... with subtests.test(): + ... pass + """ + + allure_results = allure_pytest_runner.run_docstring() + + assert_that( + allure_results, + has_test_case( + "test_with_subtest_without_msg", + has_step( + "test_with_subtest_without_msg ()", + ), + ) + ) From 9052ca9d1d03c8646bc76c49c7c25bb3cf79435a Mon Sep 17 00:00:00 2001 From: Aleksandr Brodin Date: Wed, 28 Jan 2026 16:35:10 +0700 Subject: [PATCH 4/5] fix name for file with subtests tests --- .../step/{subtest_as_step.py => subtest_as_step_test.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/allure_pytest/acceptance/step/{subtest_as_step.py => subtest_as_step_test.py} (100%) diff --git a/tests/allure_pytest/acceptance/step/subtest_as_step.py b/tests/allure_pytest/acceptance/step/subtest_as_step_test.py similarity index 100% rename from tests/allure_pytest/acceptance/step/subtest_as_step.py rename to tests/allure_pytest/acceptance/step/subtest_as_step_test.py From dc28171cd135f81df512afe91e2c905fd5312e4d Mon Sep 17 00:00:00 2001 From: Aleksandr Brodin Date: Tue, 3 Feb 2026 11:47:19 +0700 Subject: [PATCH 5/5] fix skipif tests --- tests/allure_pytest/acceptance/step/subtest_as_step_test.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/allure_pytest/acceptance/step/subtest_as_step_test.py b/tests/allure_pytest/acceptance/step/subtest_as_step_test.py index fa11d061..9c1c24fe 100644 --- a/tests/allure_pytest/acceptance/step/subtest_as_step_test.py +++ b/tests/allure_pytest/acceptance/step/subtest_as_step_test.py @@ -1,3 +1,5 @@ +import pytest + from hamcrest import assert_that from tests.allure_pytest.pytest_runner import AllurePytestRunner @@ -8,11 +10,11 @@ from allure_commons_test.result import has_status_details +@pytest.mark.skipif("pytest.version_tuple[0] < 9") def test_with_subtest(allure_pytest_runner: AllurePytestRunner): """ >>> import allure >>> import pytest - >>> @pytest.mark.skipif("pytest.__version__[0] < '9'") >>> def test_with_subtest(subtests): ... with subtests.test(msg='Some failed subtest'): ... assert False, 'Some error' @@ -42,11 +44,11 @@ def test_with_subtest(allure_pytest_runner: AllurePytestRunner): ) +@pytest.mark.skipif("pytest.version_tuple[0] < 9") def test_with_subtest_without_msg(allure_pytest_runner: AllurePytestRunner): """ >>> import allure >>> import pytest - >>> @pytest.mark.skipif("pytest.__version__[0] < '9'") >>> def test_with_subtest_without_msg(subtests): ... with subtests.test(): ... pass