Examples¶
Minimal setup¶
The simplest configuration — retry all failed scenarios up to 3 times:
# environment.py
from behave_retry import setup_retry, after_scenario_hook, retry_report
def before_all(context):
setup_retry(context, max_retries=3)
def after_scenario(context, scenario):
after_scenario_hook(context, scenario)
def after_all(context):
print(retry_report(context))
Full environment.py¶
All features enabled:
from behave_retry import setup_retry, after_scenario_hook, retry_report
def before_all(context):
setup_retry(
context,
max_retries=3,
retry_tags=["@flaky"],
retry_on=[AssertionError, TimeoutError],
retry_delay=2.0,
backoff_factor=2.0,
on_retry=lambda ctx, sc, att, exc: print(f"Retry {sc.name} #{att}: {exc}"),
max_total_retries=20,
)
def after_scenario(context, scenario):
after_scenario_hook(context, scenario)
def after_all(context):
print(retry_report(context))
Exception-filtered retry with custom exceptions¶
Only retry on database or network errors, with a 1s delay and 1.5x backoff:
from myapp.errors import DatabaseError, NetworkError
def before_all(context):
setup_retry(
context,
max_retries=5,
retry_on=[DatabaseError, NetworkError],
retry_delay=1.0,
backoff_factor=1.5,
)
Or using string names (no imports needed):
def before_all(context):
setup_retry(
context,
max_retries=5,
retry_on=["myapp.errors.DatabaseError", "myapp.errors.NetworkError"],
retry_delay=1.0,
backoff_factor=1.5,
)
Global budget with tag filtering¶
Limit total retries to 50 across the entire test run, only retrying tagged scenarios:
def before_all(context):
setup_retry(
context,
max_retries=3,
retry_tags=["@flaky", "@unstable"],
max_total_retries=50,
)
On-retry callback with traceback¶
def on_retry(context, scenario, attempt, exception):
import traceback
print(f"Retrying {scenario.name} (attempt {attempt})")
print(f" Exception: {exception}")
if exception:
traceback.print_exception(
type(exception), exception, exception.__traceback__
)
def before_all(context):
setup_retry(
context,
max_retries=3,
on_retry=on_retry,
)
On-retry callback with screenshot (Selenium)¶
def on_retry(context, scenario, attempt, exception):
if hasattr(context, "driver"):
filename = f"screenshot_{scenario.name}_{attempt}.png"
context.driver.save_screenshot(filename)
print(f"Screenshot saved: {filename}")
def before_all(context):
setup_retry(
context,
max_retries=3,
on_retry=on_retry,
)
Dedicated logging handler¶
import logging
def before_all(context):
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("[behave-retry] %(message)s"))
logging.getLogger("behave_retry").addHandler(handler)
logging.getLogger("behave_retry").setLevel(logging.INFO)
setup_retry(context, max_retries=3)
Stats for CI/CD reporting¶
Export retry stats as JSON for CI/CD pipelines:
import json
from behave_retry import retry_report
def after_all(context):
# Print human-readable summary
print(retry_report(context))
# Export machine-readable stats
stats = getattr(context, "_behave_retry_stats", None)
if stats:
with open("retry_report.json", "w") as f:
json.dump(stats.to_dict(), f, indent=2)
The JSON output looks like:
{
"total_retries": 5,
"scenarios_retried": [
{
"scenario": "Login with invalid credentials",
"attempts": 3,
"final_status": "failed",
"exceptions": ["AssertionError"],
"was_retried": true,
"passed_on_retry": false
},
{
"scenario": "Search products",
"attempts": 2,
"final_status": "passed",
"exceptions": ["TimeoutError"],
"was_retried": true,
"passed_on_retry": true
}
],
"scenarios_passed_on_retry": 1,
"scenarios_failed_after_retry": 1
}
Scenario Outline with retry¶
Each example in a Scenario Outline gets its own independent retry count:
@flaky
Feature: Login tests
Scenario Outline: Login with <user>
Given a user "<user>"
When they log in with password "<pass>"
Then the login should <result>
Examples:
| user | pass | result |
| alice | secret123 | succeed |
| bob | wrong | fail |
| carol | expired | fail |
With max_retries=3:
“alice” passes on first try → no retry
“bob” fails → retried up to 3 times independently
“carol” fails → retried up to 3 times independently
Each example is tracked by filename:line:name (e.g. features/login.feature:5:Login with bob).
Conditional retry based on environment¶
Only retry in CI, not locally:
import os
def before_all(context):
if os.environ.get("CI"):
setup_retry(context, max_retries=3, retry_tags=["@flaky"])
else:
# No retry in local development
setup_retry(context, max_retries=0)