Automatic retry for failed Behave scenarios — real re-execution, tag overrides, exception filtering, and flakiness stats.
Behave has no built-in retry. When a scenario fails due to flakiness (timing, network, race conditions), there's no way to re-run it automatically. Cucumber has --retry natively. Behave doesn't.
behave-retry fills that gap by patching Behave's Scenario.run to re-execute failed scenarios automatically — with tag overrides, exception filtering, and flakiness stats.
| Feature | behave-retry | Cucumber --retry |
pytest-rerunfailures |
|---|---|---|---|
| Per-scenario retry override | @retry:N tag |
@retry N tag |
@pytest.mark.flaky(reruns=N) |
| Exception filtering | retry_on=[...] |
No | reruns_exceptions |
| Tag filtering | retry_tags=["@flaky"] |
No | No |
| Global retry budget | max_total_retries |
No | No |
| Exponential backoff | retry_delay + backoff_factor |
No | reruns_delay (fixed) |
| On-retry callback | on_retry |
No | No |
| Retry stats | Human + JSON | No | No |
| Scenario Outline support | Per-example keys | N/A | N/A |
| Runtime dependencies | Zero | — | pytest plugin |
pip install behave-retry# 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))That's it. Failed scenarios will now be re-executed up to 3 times automatically.
- Global retry — retry all failed scenarios up to N times
- Tag-filtered retry — only retry scenarios with specific tags (
@flaky) - Exception-filtered retry — only retry on specific exception types or string names
- Per-scenario override —
@retry:Ntag overrides global config - Feature-level tags —
@retry:Non Feature inherits to scenarios - Global retry budget — limit total retries across all scenarios
- Retry delay and backoff — configurable delay with exponential backoff
- On-retry callback — custom logic before each retry (cleanup, screenshots, etc.)
- Flakiness stats — human-readable summary and machine-readable JSON export
- Scenario Outline support — unique keys per example, independent retry counts
- Environment variables — control retry from
behave-runneror CI without touching code - Logging — via standard
loggingmodule underbehave_retrylogger - Type-safe —
py.typedmarker included, full type hints, mypy clean
setup_retry(
context,
max_retries=3, # max retries per scenario
retry_tags=["@flaky"], # only retry tagged scenarios
retry_on=[AssertionError, TimeoutError], # only retry these exceptions
retry_delay=2.0, # 2s delay before first retry
backoff_factor=2.0, # double delay each retry (2s, 4s, 8s)
on_retry=lambda ctx, sc, att, exc: print(f"Retry {sc.name} #{att}: {exc}"),
max_total_retries=20, # stop after 20 total retries across all scenarios
)See the configuration guide for full details.
You can control retry behavior via environment variables. This is useful when running tests through behave-runner, CI pipelines, or any orchestration tool that passes configuration through the environment.
# environment.py — no hard-coded values
def before_all(context):
setup_retry(context)# CLI
BEHAVE_RETRY_MAX_RETRIES=3 BEHAVE_RETRY_DELAY=2.0 BEHAVE_RETRY_BACKOFF=2.0 behave| Env var | Type | Default | Maps to |
|---|---|---|---|
BEHAVE_RETRY_MAX_RETRIES |
int |
0 |
max_retries |
BEHAVE_RETRY_DELAY |
float |
0.0 |
retry_delay |
BEHAVE_RETRY_BACKOFF |
float |
1.0 |
backoff_factor |
BEHAVE_RETRY_MAX_TOTAL |
int |
None |
max_total_retries |
Explicit arguments always win. If you call setup_retry(context, max_retries=5), the env var is ignored.
setup_retrypatchesbehave.model.Scenario.runwith a retry-aware wrapper.- When a scenario fails, the wrapper checks:
- Does the scenario have retries remaining? (global
max_retriesor@retry:Noverride) - Is the scenario tagged for retry? (if
retry_tagsis set) - Is the exception type eligible? (if
retry_onis set) - Is the global retry budget exhausted? (if
max_total_retriesis set)
- Does the scenario have retries remaining? (global
- If all checks pass, it resets the scenario state and re-runs it.
- Stats are tracked and available via
retry_report()orstats.to_dict().
| Section | Description |
|---|---|
| Installation | Install from PyPI or source |
| Quick start | Three-step setup guide |
| Features | Complete feature walkthrough with examples |
| Configuration | All parameters, validation, and precedence rules |
| Examples | Real-world recipes for common use cases |
| API reference | Full autodoc API |
| Changelog | Version history |
Contributions are welcome! See CONTRIBUTING.md for guidelines.
MIT — Copyright (c) 2026 Mathias Paulenko