Documenting Test Workflows in pytest with Allure Steps
Alllure’s step API is an effective way to describe multi-step test flows in pytest, improving readability and making reports actionable. This guide shows how to:
- Add step annotations to linear flows
- Nest steps and reuse them across modules
- Use placeholders to inject arguments in to step titles
- Record steps executed inside fixtures (from conftest.py)
Linear workflow with step annotations
Example scenario: sign in, browse catalog, add to cart, submit order, pay.
# file: test_allure_steps_flow.py
import pytest
import allure
@allure.step("Sign in to the application")
def sign_in():
print("Signing in...")
@allure.step("Browse the product catalog")
def browse_catalog():
print("Browsing catalog...")
@allure.step("Add SKU={sku} (qty={qty}) to cart")
def add_to_cart(sku: str, qty: int = 1):
print(f"Adding {qty} x {sku} to the cart")
@allure.step("Create a new order")
def create_order():
print("Creating order...")
@allure.step("Pay for order using {method}")
def pay_for_order(method: str = "card"):
print(f"Paying with {method}")
def test_purchase_happy_path():
sign_in()
browse_catalog()
add_to_cart("BOOK-123", qty=2)
create_order()
pay_for_order(method="wallet")
with allure.step("Assert order submission succeeded"):
assert True
if __name__ == "__main__":
pytest.main(["-s", "test_allure_steps_flow.py"])
Run and open the report:
pytest test_allure_steps_flow.py --alluredir=./report/result_data
allure serve ./report/result_data
Nested steps and importing steps from another module
Steps can call other steps, and can be orgenized in separate modules for reuse.
# file: helpers.py
import allure
@allure.step("External helper step 02")
def step_two():
print("executing step_two")
# file: test_allure_steps_nested.py
import pytest
import allure
from helpers import step_two
@allure.step("Helper step 01")
def step_one():
print("executing step_one")
@allure.step("Composite step: invoke nested steps")
def composite_step():
inner_step()
@allure.step("Inner step")
def inner_step():
inner_with_args(7, "xyz")
@allure.step("Inner with args x={x}, y={y}")
def inner_with_args(x, y):
print(f"x={x}, y={y}")
def test_imported_step_usage():
step_one()
step_two()
def test_nested_steps():
step_one()
composite_step()
step_two()
if __name__ == "__main__":
pytest.main(["-s", "test_allure_steps_nested.py"])
Run and open the report:
pytest test_allure_steps_nested.py --alluredir=./report/result_data
allure serve ./report/result_data
In the report, nested calls appear as a hierarchical tree under the parent step. Importing from another module preserves the step structure and title.
Step titles with placeholders and parameter injection
Allure allows formatting step titles using function arguments. You can combine positional and keyword placeholders.
# file: test_allure_steps_with_placeholders.py
import pytest
import allure
@allure.step('Prepare dataset run={0} for user="{user_id}" in mode="{mode}"')
def prepare_dataset(run_id: int, user_id: str = "guest", mode: str | None = None):
print(f"run={run_id}, user={user_id}, mode={mode}")
def test_step_placeholders():
prepare_dataset(1, user_id="alice", mode="fast")
prepare_dataset(2) # uses defaults
prepare_dataset(3, mode="slow")
if __name__ == "__main__":
pytest.main(["-s", "test_allure_steps_with_placeholders.py"])
Run and open the report:
pytest test_allure_steps_with_placeholders.py --alluredir=./report/result_data
allure serve ./report/result_data
The rendered step titles include argument values injected through the placeholders.
Steps inside fixtures defined in confetst.py
You can place steps in fixtures so they show up in the Setup and Teardown sections of the report.
# file: conftest.py
import pytest
import allure
@pytest.fixture()
def env_fixture():
setup_environment()
yield
cleanup_environment()
@allure.step("Setup: initialize test environment")
def setup_environment():
print("env setup")
@allure.step("Teardown: cleanup test environment")
def cleanup_environment():
print("env cleanup")
# file: test_allure_steps_in_fixture.py
import pytest
import allure
@allure.step("Execute main test step")
def main_step():
print("main step")
def test_uses_env_fixture(env_fixture):
main_step()
if __name__ == "__main__":
pytest.main(["-s", "test_allure_steps_in_fixture.py"])
Run and open the report:
pytest test_allure_steps_in_fixture.py --alluredir=./report/result_data
allure serve ./report/result_data
Steps executed within fixtures will be grouped under Setup and Teardown in the test’s report node.