Cucumber.Runtime (Cucumber v1.0.0)

View Source

Runtime execution of cucumber steps.

This module handles the step execution lifecycle within a running test:

  1. Each step's text is matched against the step registry to find a definition
  2. Cucumber Expressions are used to extract typed parameters from step text
  3. The step function is invoked with context containing extracted args, optional datatable, and optional docstring
  4. Step return values are processed to update the shared context:
    • :ok keeps context unchanged
    • A map is merged into context
    • A keyword list is converted to a map and merged
    • {:ok, data} unwraps and merges
    • {:error, reason} fails the step
    • :pending / {:pending, message} marks the step pending: remaining steps are skipped, after hooks run, and the scenario fails with Cucumber.PendingStepError
    • :skipped / {:skipped, reason} skips the rest of the scenario: remaining steps don't run, after hooks run, and the scenario does not fail (a one-line notice is printed instead)
  5. On failure, enhanced error messages are generated with step history, file locations, and formatted assertion details

Summary

Functions

Executes a step with the given context and step registry.

Runs a complete scenario inside the test process: before hooks, background steps, scenario steps, and after hooks.

Functions

execute_step(context, step, step_registry, parameter_types \\ %{})

@spec execute_step(map(), Gherkin.Step.t(), map(), Cucumber.Expression.custom_types()) ::
  map() | {:halted, :pending | :skipped, String.t() | nil, map()}

Executes a step with the given context and step registry.

parameter_types carries custom parameter type definitions (see Cucumber.ParameterTypes) used when matching cucumber expressions.

Returns the updated context, or {:halted, status, reason, context} when the step signaled :pending or :skipped — the caller owns what happens to the rest of the scenario.

Step hooks do not run through this entry point; they require the scenario runner's hook list (see run_scenario/3).

run_scenario(exunit_context, scenario, runtime_data)

@spec run_scenario(map(), map(), map()) :: map()

Runs a complete scenario inside the test process: before hooks, background steps, scenario steps, and after hooks.

Generated feature tests call this as their entire body. The runtime owning the whole lifecycle (rather than splitting it across ExUnit setup and on_exit) keeps after-hooks in the test process and gives the runner control over cross-step flow — the foundation for skip/pending semantics, retry, and event emission.

Semantics:

  • a {:error, reason} from a before hook fails the scenario without running any steps (after hooks do not run)
  • a failing background step fails the scenario; after hooks do not run (they are only armed once the background succeeded)
  • after hooks run whether the scenario's steps pass or fail, receiving the post-background context
  • a :pending or :skipped signal (from a before hook, a background step, or a scenario step) skips the remaining steps — the unexecuted steps land in the context under :skipped_steps — and after hooks still run. Skipped scenarios pass with a printed notice; pending scenarios fail with Cucumber.PendingStepError
  • a failing scenario is retried when a retry limit is configured (config :cucumber, retry: n or a @retry-n tag; a tag beats the config, and a scenario-level tag beats a feature-level one): each attempt re-runs before hooks, background, steps, and after hooks with a fresh context (carrying :retry_attempt, 1-based), and the scenario passes if any attempt passes. Undefined, ambiguous, and pending scenarios are never retried — they cannot succeed by repetition — and neither is a cached before_all failure

Returns the final context.