Retry Mechanism
Flaky steps are a fact of life when tests touch networks, browsers, or background jobs. Behave has no built-in retry, but retrying a step is a small piece of code: a decorator that re-runs the step function a few times with a delay and only raises once every attempt failed.
A Retry Decorator
# features/steps/retry.py
import functools
import time
def retry(times=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(context, *args, **kwargs):
last_exc = None
for attempt in range(times):
try:
return func(context, *args, **kwargs)
except Exception as exc: # noqa: BLE001
last_exc = exc
if attempt < times - 1:
time.sleep(delay)
raise last_exc
return wrapper
return decorator
Applying Retry to a Step
from behave import step
from steps.retry import retry
@retry(times=3, delay=2)
@step("the payment gateway confirms the charge")
def step_impl(context):
receipt = gateway.poll(context.charge_id, timeout=5)
assert receipt is not None, f"no receipt for {context.charge_id}"
Order matters: the retry decorator must wrap the behave step decorator, so behave sees one stable function while the wrapper handles the re-execution. If an assertion still fails after the last attempt, the scenario fails with the original exception and traceback.
Rerunning Only Failed Scenarios
For whole-scenario reruns instead of per-step retries, the third-party behave-rerun plugin adds a formatter that records failures in a rerun file; the next run executes only those scenarios:
pip install behave-rerun
# first run records failures
behave -f rerun -o rerun.txt features/
# second run executes only the recorded failures
behave -r rerun.txt
Know When to Retry
Retry network hiccups and async propagation, never logic errors. If a retry hides a real bug, the test will pass locally and fail in a stranger's environment. Keep the retry count small, log each attempt, and treat "retried and passed" as a signal to investigate the flake.
Summary
Wrap flaky steps in a retry decorator that repeats with a delay, or use the behave-rerun plugin to rerun failed scenarios. Reserve retries for genuine instability and keep them visible in the output.