Getting started

<- Back to home

This page shows the smallest useful integration first. If you want full recipes that combine several optional helpers, continue with Integration examples.

Before you start

  • You already have a Behave project with a features/ directory.

  • You want to keep environment.py explicit instead of hiding setup behind a large plugin layer.

  • You are running on a supported Python version. See Compatibility and support.

1. Install the package

pip install behave-toolkit

That single install gives you:

  • the runtime helpers used from features/environment.py

  • the behave-toolkit-docs CLI

  • the Sphinx dependencies needed to build generated HTML step documentation

2. Create a minimal project layout

features/
  behave-toolkit.yaml
  environment.py
  steps/
    reporting_steps.py
  smoke.feature

3. Create a toolkit config

Start with one variable, one feature-scoped resource, and one scenario-scoped path built from it:

version: 1
variables:
  report_name: report.json

objects:
  workspace:
    factory: tempfile.TemporaryDirectory
    scope: feature
    cleanup: cleanup

  workspace_path:
    factory: pathlib.Path
    scope: feature
    args:
      - $ref: workspace
        attr: name

  report_path:
    factory: pathlib.Path
    scope: scenario
    args:
      - $ref: workspace_path
      - $var: report_name

factory can point to:

  • your own project code

  • an installed dependency

  • the Python standard library

args and kwargs can also use direct YAML values. You only need $ref and $var when you want to reuse another managed object or a root config variable.

objects:
  api_client:
    factory: demo.clients.ApiClient
    kwargs:
      base_url: https://example.test
      timeout: 30
      verify_ssl: true

4. Wire features/environment.py

from pathlib import Path

from behave_toolkit import (
    activate_feature_scope,
    activate_scenario_scope,
    install,
)

CONFIG_PATH = Path(__file__).with_name("behave-toolkit.yaml")


def before_all(context):
    install(context, CONFIG_PATH)


def before_feature(context, feature):
    del feature
    activate_feature_scope(context)


def before_scenario(context, scenario):
    del scenario
    activate_scenario_scope(context)

This is the core runtime path:

  • install() loads and validates the config, attaches the manager under context.toolkit, and activates global objects by default

  • activate_feature_scope() creates feature-scoped objects inside before_feature

  • activate_scenario_scope() creates scenario-scoped objects inside before_scenario

  • cleanup is registered with Behave so each scope tears down at the right time automatically

5. Use the injected objects from a step

If you do not set inject_as, the object name becomes the context attribute name.

from behave import then


@then("the report path is available")
def step_report_path_available(context):
    context.report_path.write_text("ready\n", encoding="utf-8")
    assert context.report_path.exists()

And a matching feature file can stay completely ordinary:

Feature: Toolkit smoke

  Scenario: Use toolkit-managed objects
    Then the report path is available

6. Run Behave

behave

At runtime the flow is:

  1. before_all() installs the toolkit and creates global objects.

  2. before_feature() creates feature-scoped objects.

  3. before_scenario() creates scenario-scoped objects.

  4. Steps use the injected instances through context.

  5. Behave cleanup tears objects down in reverse creation order at the matching scope boundary.

7. Add optional helpers only when you need them

If you need…

Helper

When it runs

Where it belongs

Behave custom type registration from YAML

configure_parsers(CONFIG_PATH)

import time

top-level in environment.py, before step modules load

root config variables directly in .feature files

substitute_feature_variables(context)

before_all

after install()

repeated runs of one plain scenario

expand_scenario_cycles(context)

before_all

after install(), and after feature-variable substitution if you use both

one persistent suite log

configure_test_logging(...)

usually before_all

after install() so your path objects already exist

several named logs from YAML

configure_loggers(context)

usually before_all

after install() so global objects already exist

8. Full hook template with optional helpers

Use this as the starting point when your suite grows beyond the minimal path:

from pathlib import Path

from behave_toolkit import (
    activate_feature_scope,
    activate_scenario_scope,
    configure_parsers,
    configure_test_logging,
    expand_scenario_cycles,
    install,
    substitute_feature_variables,
)

CONFIG_PATH = Path(__file__).with_name("behave-toolkit")

# Optional: only useful when your config defines a `parsers:` section.
configure_parsers(CONFIG_PATH)


def before_all(context):
    install(context, CONFIG_PATH)

    # Optional: enable `{{var:name}}` placeholders in parsed feature files.
    substitute_feature_variables(context)

    # Optional: expand `@cycling(N)` tags on plain scenarios.
    expand_scenario_cycles(context)

    # Optional: keep one persistent suite log.
    # Remove this line if your config does not define `test_log_path`.
    context.test_logger = configure_test_logging(context.test_log_path)


def before_feature(context, feature):
    del feature
    activate_feature_scope(context)


def before_scenario(context, scenario):
    del scenario
    activate_scenario_scope(context)

If CONFIG_PATH points to a directory instead of a single file, use Path(__file__).with_name("behave-toolkit") or any other directory path that suits your project layout.

Next steps