Step Functions and the Context Object
Every step function receive one argument: context. It is behave's shared, per-scenario state container and the primary channel through which steps communicate. Mastering what lives in context, what its lifetime is, and what behaves adds to it is essential for writing steps that cooperate instead of fighting each other.
Lifecycle and Isolation
Behave creates a fresh context object for each scenario and discards it afterwards. Steps in the same scenario see the same attributes; steps in the next scenario start with a clean slate. This isolation guarantees scenarios stay independent — but it also means anything cached in context must be rebuilt per scenario, or hoisted into before_all fixtures stored via context.config.userdata or module-level caches.
@given("a logged-in shopper")
def logged_in(context):
context.browser = browser_login("[email protected]", "secret")
context.cart = []
Attributes Behave Prepopulates
Besides your own attributes, behave puts framework data on context before your steps run:
| Attribute | Type | Meaning |
|---|---|---|
| context.feature | Feature | The currently running feature: name, tags, description. |
| context.scenario | Scenario | Current scenario, including outline name and tags. |
| context.scenario.outline | ScenarioOutline | Set when the scenario is an expanded outline. |
| context.rule | Rule or None | Enclosing rule, if the feature uses Rule. |
| context.step | Step | The step currently executing; its keywords and text. |
| context.table | Table or None | Rows attached to the step, if any. |
| context.text | Text or None | Docstring attached to the step, if any. |
| context.config | Configuration | Parsed options; context.config.userdata holds -D values. |
| context.active_steps | list | Stack of the currently executing steps (useful in hooks). |
Reading and Writing Attributes
Assign any attribute name that suits your domain. Access before assignment raises AttributeError, which behave reports as a step failure — this is a feature: it surfaces store/state bugs in tests instead of silently passing on stale data.
@when("signup is attempted with the details") # flask view handler smoke
def signup_attempt(context):
payload = {"email": context.user_email, # set by a previous Given
"password": context.user_password}
context.last_error = None
context.request = call_api("POST", "/signup", payload)
Passing Values Between Step Kinds
Typical pattern: Given creates domain objects, When triggers an operation, Then asserts. All three use context keys agreed by naming convention, e.g., context.response, context.order, context.created_id:
# cart_steps.py
@given("the customer has a current cart")
def get_cart(context):
customer = build_customer()
context.cart = create_cart_for(customer)
@when("the item is added to the cart")
def add_item(context):
context.cart.update({"item": context.item}) # context.item set earlier
@then("the cart shows the item")
def show(context):
assert context.item in context.cart
Sharing Across Step Modules
The context object is one instance even when steps live in different Python files — cart_steps.py can read what auth_steps.py stored. Keep keys top-level and documented to avoid confusion. Two related notes on access logic:
- Use
getattr(context, "key", default)to read a possibly-unset value without raising. - Never mutate attributes behave created for you (e.g., swapping
context.table) from a normal step. contextis reentrant: insideafter_scenarioyou get a mostly-pristine copy per behave's fixture semantics, so don't rely on scenario-created state unless you save it yourself.
Anti-Patterns
- Storing one big blob (a dict per scenario you poke keys into) — prefer named attributes.
- Putting functions or lambdas on
contextinstead of importing helpers. - Depending on attribute values from a previous scenario — isolation removes them by design.
Summary
The context object is per-scenario shared state: step functions read and write attributes on it, behave prepopulates framework attributes like feature, scenario, step, table, and config, and nothing survives into the next scenario.