Step Utilities¶
Custom parameter types, conditional steps, and “did you mean?” suggestions for undefined steps.
Custom parameter types¶
Register converters for custom step parameters:
- behave_kit.steps.parameters.parameter_type(name: str, pattern: str = '') Callable[[Callable[[str], Any]], Callable[[str], Any]][source]¶
Register
funcas the converter for parameter typename.patterndocuments the expected input shape; matching the pattern against step text is left to the caller (Cucumber Expressions, parse, or a manual regex) – this decorator only registers the conversion.
- behave_kit.steps.parameters.convert(name: str, match_string: str) Any[source]¶
Apply the converter registered under
nametomatch_string.
- behave_kit.steps.parameters.register_builtin_types() None[source]¶
Register every built-in converter. Safe to call more than once.
Built-in types¶
behave-kit ships converters for common types. Call register_builtin_types() once to register them all:
from behave_kit import register_builtin_types
def before_all(context):
register_builtin_types()
Available built-in types:
int— integer conversionfloat— float conversiondate— ISO date (YYYY-MM-DD)datetime— ISO datetimedecimal— Decimal for exact numeric comparisonsemail— validated email addressurl— validated HTTP/HTTPS URL
Custom parameter type example¶
from behave_kit import parameter_type, convert
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
@parameter_type("User", r'[\w.]+@[\w.]+')
def parse_user(text: str) -> User:
name, domain = text.split("@")
return User(name=name, email=text)
@when('I log in as "{user:User}"')
def step(context, user):
# user is a User instance, not a raw string
assert isinstance(user, User)
context.current_user = user
Using convert() directly¶
from behave_kit import convert
email = convert("email", "alice@example.com") # validated string
port = convert("int", "8080") # 8080 as int
url = convert("url", "https://example.com") # validated URL
Conditional steps with @when_if¶
Run a step only when a condition function returns True:
- behave_kit.steps.conditional.when_if(condition: Callable[[Context], object]) Callable[[Callable[[Concatenate[Context, P]], R]], Callable[[Concatenate[Context, P]], R]][source]¶
Decorator: execute the step only if
condition(context)is true.When the condition is false, the step is skipped (via unittest.SkipTest) rather than executed or failed.
Examples¶
from behave_kit import when_if
@when_if(lambda ctx: ctx.config.env == "staging")
@when("I run the staging-only step")
def step(context):
...
@when_if(lambda ctx: hasattr(ctx, "browser"))
@when("I take a screenshot")
def step(context):
context.browser.save_screenshot("debug.png")
@when_if(lambda ctx: ctx.config.env != "production")
@when("I reset the test database")
def step(context):
context.db.reset()
When the condition is False, the step is silently skipped (Behave marks
it as skipped, not failed).
Data-driven steps with @data_driven¶
Run a step once per row of a data file (CSV, JSON, YAML, Excel). Column names are sanitized (hyphens and spaces become underscores) and injected as keyword arguments:
- behave_kit.steps.data_driven.data_driven(path: str | Path) Callable[[Callable[[Concatenate[Context, P]], R]], Callable[[Concatenate[Context, P]], R]][source]¶
Decorator: run the step once per row in the data file at
path.Each row (a
dict) is unpacked as keyword arguments into the step function. Column names with hyphens or spaces are converted to valid Python identifiers (-and `` `` →_).Usage:
@data_driven("tests/data/users.csv") @when("I login as {username}") def step(context, username=None, password=None): ...
- Raises:
BehaveKitError – If the data file cannot be loaded or a row is not a dict.
Examples¶
from behave_kit import data_driven
@data_driven("tests/data/users.csv")
@when("I login with the test credentials")
def step(context, username, password):
login(username, password)
# Runs once per row in users.csv
Given tests/data/users.csv:
username,password
alice,secret
bob,pass123
The step is called twice with username="alice", password="secret" and
username="bob", password="pass123".
Column name sanitization¶
Column names with hyphens or spaces are converted to valid Python identifiers:
user-name,pass word
alice,secret
The step receives user_name and pass_word as keyword arguments.
Using JSON or YAML¶
@data_driven("tests/data/items.json")
@when("I process each item")
def step(context, id, name):
process(id, name)
The file must contain a list of dictionaries (or a single dict that will be wrapped in a list).
Step suggestions¶
When a step is undefined, behave-kit suggests the closest registered step patterns using difflib:
- behave_kit.steps.suggestions.setup_suggestions(context: Context) Callable[[Context, object], None][source]¶
Return an
after_step(context, step)hook that logs suggestions.Wire it in
environment.py:_suggest = behave_kit.steps.setup_suggestions(context) def after_step(context, step): _suggest(context, step)
- behave_kit.steps.suggestions.suggest_for_undefined(step: object) list[str][source]¶
Log the closest registered step pattern(s) for an undefined
step.Returns the list of close matches found (possibly empty).
Wiring suggestions¶
Automatic (via `setup()`):
from behave_kit import setup
def before_all(context):
setup(context, env="staging")
# Suggestions are wired automatically.
Manual:
from behave_kit import setup_suggestions
def before_all(context):
context._suggest = setup_suggestions(context)
def after_step(context, step):
context._suggest(context, step)
Example output¶
When a step is undefined, you’ll see a log message like:
INFO: Step 'I clikc the button' has not been defined. Did you mean: 'I click the button'?
This helps developers quickly identify typos in step definitions without searching through all registered steps.