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.The type is also registered with Behave, so
{param:name}placeholders in@given/@when/@thenpatterns (parse/cfparse matchers) are converted automatically.patternis the regular expression used to match the parameter text; when omitted, Behave falls back to its default “anything” pattern.
- 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.
Registered types are also usable as
{param:name}in Behave step patterns (e.g.{email:email},{url:url}).
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()
Registered types are usable both via convert() and as {name:Type}
placeholders in Behave step patterns (parse/cfparse matchers).
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
from behave_kit.skip.conditions import is_env
@when("I run the staging-only step")
@when_if(lambda ctx: is_env(ctx, "staging"))
def step(context):
...
@when("I take a screenshot")
@when_if(lambda ctx: hasattr(ctx, "browser"))
def step(context):
context.browser.save_screenshot("debug.png")
@when("I reset the test database")
@when_if(lambda ctx: not is_env(ctx, "production"))
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 to valid Python identifiers and injected as keyword
arguments. @data_driven must be placed inside (below) the Behave
step decorator so Behave registers the wrapped function:
- 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 are sanitized to valid Python identifiers (non-identifier characters become_).Usage:
@when("I login as {username}") @data_driven("tests/data/users.csv") def step(context, username=None, password=None): ...
Note:
@data_drivenmust be placed inside (below) the Behave step decorator so that the wrapped function is what Behave registers.- Raises:
BehaveKitError – If the data file cannot be loaded or a row is not a dict.
Examples¶
from behave_kit import data_driven
@when("I login with the test credentials")
@data_driven("tests/data/users.csv")
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¶
@when("I process each item")
@data_driven("tests/data/items.json")
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¶
Behave hooks cannot be injected — the returned after_step hook must be
called explicitly. setup() attaches it to context.kit_suggestions:
Automatic (via `setup()`):
from behave_kit import setup
def before_all(context):
setup(context, env="staging")
def after_step(context, step):
context.kit_suggestions(context, step)
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.