diff --git a/allure-pytest/src/listener.py b/allure-pytest/src/listener.py index 11153630..73e81184 100644 --- a/allure-pytest/src/listener.py +++ b/allure-pytest/src/listener.py @@ -23,6 +23,7 @@ from allure_pytest.utils import get_status, get_status_details from allure_pytest.utils import get_outcome_status, get_outcome_status_details from allure_pytest.utils import get_pytest_report_status +from allure_pytest.utils import sync_steps_statuses from allure_pytest.utils import format_allure_link from allure_pytest.utils import get_history_id from allure_pytest.compat import getfixturedefs @@ -226,6 +227,9 @@ def pytest_runtest_makereport(self, item, call): if test_result.status == Status.PASSED: test_result.status = status test_result.statusDetails = status_details + has_failed_steps = sync_steps_statuses(test_result.steps) + if has_failed_steps and not hasattr(report, "wasxfail"): + test_result.status = Status.FAILED if report.when == 'teardown': if status in (Status.FAILED, Status.BROKEN) and test_result.status == Status.PASSED: diff --git a/allure-pytest/src/utils.py b/allure-pytest/src/utils.py index 1e07cb49..7a1058d9 100644 --- a/allure-pytest/src/utils.py +++ b/allure-pytest/src/utils.py @@ -184,6 +184,17 @@ def get_pytest_report_status(pytest_report): return status +def sync_steps_statuses(steps): + any_failed = False + for step in steps: + if step.steps and sync_steps_statuses(step.steps): + if step.status != Status.SKIPPED: + step.status = Status.FAILED + if step.status == Status.FAILED: + any_failed = True + return any_failed + + def get_history_id(full_name, parameters, original_values): return md5( full_name, diff --git a/tests/allure_pytest/acceptance/status/base_step_status_test.py b/tests/allure_pytest/acceptance/status/base_step_status_test.py index c5cb1a06..1771e884 100644 --- a/tests/allure_pytest/acceptance/status/base_step_status_test.py +++ b/tests/allure_pytest/acceptance/status/base_step_status_test.py @@ -74,6 +74,49 @@ def test_pytest_fail_in_step(allure_pytest_runner: AllurePytestRunner): ) +def test_pytest_fail_in_nested_step_with_soft_check(allure_pytest_runner: AllurePytestRunner): + """ + >>> import allure + >>> from pytest_check import check as soft_check + + >>> def test_pytest_fail_in_nested_step_with_soft_check(): + ... with allure.step("Parent step"): + ... with soft_check, allure.step("Child failed step"): + ... assert False + ... with soft_check, allure.step("Child passed step"): + ... assert True + """ + from pytest_check import check_log + + allure_results = allure_pytest_runner.run_docstring() + # Prevent failed soft check checks from triggering an 'assert False'. + check_log.clear_failures() + + assert_that( + allure_results, + has_test_case( + "test_pytest_fail_in_nested_step_with_soft_check", + with_status("failed"), + has_step( + "Parent step", + with_status("failed"), + has_step( + "Child failed step", + with_status("failed"), + has_status_details( + with_message_contains("AssertionError: assert False"), + with_trace_contains("test_pytest_fail_in_nested_step_with_soft_check") + ) + ), + has_step( + "Child passed step", + with_status("passed") + ) + ) + ) + ) + + def test_pytest_bytes_data_in_assert(allure_pytest_runner: AllurePytestRunner): """ >>> import allure