diff --git a/CHANGES.rst b/CHANGES.rst index 3b94132cb..6f26a8ced 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -30,6 +30,7 @@ Removed Fixed +++++ * Made type annotations stronger and removed most of the ``typing.Any`` usages and ``# type: ignore`` annotations. `#658 `_ +* Mark steps that raise pytest outcome exceptions as failed in cucumber JSON output. `#770 `_ Security ++++++++ diff --git a/src/pytest_bdd/hooks.py b/src/pytest_bdd/hooks.py index 48e1cedbc..6f5996a2e 100644 --- a/src/pytest_bdd/hooks.py +++ b/src/pytest_bdd/hooks.py @@ -53,7 +53,7 @@ def pytest_bdd_step_error( step: Step, step_func: Callable[..., object], step_func_args: dict[str, object], - exception: Exception, + exception: BaseException, ) -> object: """Called when step function failed to execute.""" diff --git a/src/pytest_bdd/plugin.py b/src/pytest_bdd/plugin.py index a9f4cff91..3104521fa 100644 --- a/src/pytest_bdd/plugin.py +++ b/src/pytest_bdd/plugin.py @@ -101,7 +101,7 @@ def pytest_bdd_step_error( step: Step, step_func: Callable[..., object], step_func_args: dict[str, object], - exception: Exception, + exception: BaseException, ) -> None: reporting.step_error(request, feature, scenario, step, step_func, step_func_args, exception) diff --git a/src/pytest_bdd/reporting.py b/src/pytest_bdd/reporting.py index 4d5c626b6..7d816f605 100644 --- a/src/pytest_bdd/reporting.py +++ b/src/pytest_bdd/reporting.py @@ -219,7 +219,7 @@ def step_error( step: Step, step_func: Callable[..., object], step_func_args: dict[str, object], - exception: Exception, + exception: BaseException, ) -> None: """Finalize the step report as failed.""" scenario_reports_registry[request.node].fail() diff --git a/src/pytest_bdd/scenario.py b/src/pytest_bdd/scenario.py index 531190617..33684fa5f 100644 --- a/src/pytest_bdd/scenario.py +++ b/src/pytest_bdd/scenario.py @@ -24,6 +24,7 @@ import pytest from _pytest.fixtures import FixtureDef, FixtureManager, FixtureRequest, call_fixture_func +from _pytest.outcomes import TEST_OUTCOME from . import exceptions from .compat import getfixturedefs, inject_fixture @@ -247,7 +248,7 @@ def _execute_step_function( # so that we can allow "yield" statements in it return_value = call_fixture_func(fixturefunc=context.step_func, request=request, kwargs=kwargs) - except Exception as exception: + except TEST_OUTCOME as exception: request.config.hook.pytest_bdd_step_error(exception=exception, **kw) raise diff --git a/tests/feature/test_cucumber_json.py b/tests/feature/test_cucumber_json.py index 6e6239c41..86be60aa3 100644 --- a/tests/feature/test_cucumber_json.py +++ b/tests/feature/test_cucumber_json.py @@ -237,3 +237,40 @@ def test_passing_outline(): ] assert jsonobject == expected + + +def test_pytest_outcome_exception_marks_step_failed(pytester): + """Test pytest outcome exceptions are reflected in cucumber json.""" + pytester.makefile( + ".feature", + test=textwrap.dedent( + """ + Feature: Outcome exceptions + + Scenario: Failing outcome + When a pytest outcome failure is raised + """ + ), + ) + pytester.makepyfile( + textwrap.dedent( + """ + import pytest + from pytest_bdd import scenario, when + + @scenario("test.feature", "Failing outcome") + def test_failing_outcome(): + pass + + @when("a pytest outcome failure is raised") + def _(): + pytest.fail("timeout-style failure") + """ + ) + ) + + result, jsonobject = runandparse(pytester) + result.assert_outcomes(failed=1) + [scenario] = jsonobject[0]["elements"] + [step] = scenario["steps"] + assert step["result"]["status"] == "failed"