behave-retry
August 8, 2026 · View on GitHub
Automatic retry for failed Behave scenarios — real re-execution, tag overrides, exception filtering, and flakiness stats.
Why?
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.
Comparison
| 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 |
Install
pip install behave-retry
Quick start
# 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.
Features
- 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
Configuration
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.
Environment variables
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.
How it works
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().
Documentation
| 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 |
Contributing
Contributions are welcome! See CONTRIBUTING.md for guidelines.
License
MIT — Copyright (c) 2026 Mathias Paulenko