diff --git a/src/pytest_bdd/plugin.py b/src/pytest_bdd/plugin.py index 7a229b19..a19fef9e 100644 --- a/src/pytest_bdd/plugin.py +++ b/src/pytest_bdd/plugin.py @@ -128,6 +128,17 @@ def pytest_bdd_after_step( reporting.after_step(request, feature, scenario, step, step_func, step_func_args) +@pytest.hookimpl(tryfirst=True) +def pytest_bdd_step_func_lookup_error( + request: FixtureRequest, + feature: Feature, + scenario: Scenario, + step: Step, + exception: Exception, +) -> None: + reporting.step_func_lookup_error(request, feature, scenario, step, exception) + + def pytest_cmdline_main(config: Config) -> int | None: return generation.cmdline_main(config) diff --git a/src/pytest_bdd/reporting.py b/src/pytest_bdd/reporting.py index f3af9900..a21ea308 100644 --- a/src/pytest_bdd/reporting.py +++ b/src/pytest_bdd/reporting.py @@ -226,6 +226,24 @@ def step_error( scenario_reports_registry[request.node].fail() +def step_func_lookup_error( + request: FixtureRequest, + feature: Feature, + scenario: Scenario, + step: Step, + exception: Exception, +) -> None: + """Record the step with the missing definition as failed. + + Without this, a scenario whose first step has no definition would leave + no step reports behind, and reporters (e.g. the cucumber json report) + would silently drop the whole scenario. + """ + scenario_report = scenario_reports_registry[request.node] + scenario_report.add_step_report(StepReport(step=step)) + scenario_report.fail() + + def before_step( request: FixtureRequest, feature: Feature, diff --git a/tests/feature/test_cucumber_json.py b/tests/feature/test_cucumber_json.py index 7ddc3680..ff950090 100644 --- a/tests/feature/test_cucumber_json.py +++ b/tests/feature/test_cucumber_json.py @@ -239,6 +239,56 @@ def test_passing_outline(): assert jsonobject == expected +def test_missing_first_step_definition(pytester): + """Test that a scenario whose first step is not implemented is still reported (see issue #721).""" + pytester.makefile( + ".feature", + test=textwrap.dedent( + """ + Feature: Missing step definitions + Scenario: First step missing + Given a missing first step + When a present second step + Then a present third step + """ + ), + ) + pytester.makepyfile( + textwrap.dedent( + """ + from pytest_bdd import scenario, then, when + + @when('a present second step') + def _(): + pass + + @then('a present third step') + def _(): + pass + + @scenario('test.feature', 'First step missing') + def test_scenario(): + pass + """ + ) + ) + result, jsonobject = runandparse(pytester) + result.assert_outcomes(failed=1) + + elements = jsonobject[0]["elements"] + assert len(elements) == 1 + assert elements[0]["name"] == "First step missing" + + steps = elements[0]["steps"] + assert [step["name"] for step in steps] == [ + "a missing first step", + "a present second step", + "a present third step", + ] + assert steps[0]["result"]["status"] == "failed" + assert "Step definition is not found" in steps[0]["result"]["error_message"] + + def test_pytest_fail_in_step_body(pytester): """Test that pytest.fail() in a step body is captured as failed in the JSON output.""" pytester.makefile(