behave-retry

August 8, 2026 · View on GitHub

CI Docs PyPI Python License Coverage

Automatic retry for failed Behave scenarios — real re-execution, tag overrides, exception filtering, and flakiness stats.

Full documentation →

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

Featurebehave-retryCucumber --retrypytest-rerunfailures
Per-scenario retry override@retry:N tag@retry N tag@pytest.mark.flaky(reruns=N)
Exception filteringretry_on=[...]Noreruns_exceptions
Tag filteringretry_tags=["@flaky"]NoNo
Global retry budgetmax_total_retriesNoNo
Exponential backoffretry_delay + backoff_factorNoreruns_delay (fixed)
On-retry callbackon_retryNoNo
Retry statsHuman + JSONNoNo
Scenario Outline supportPer-example keysN/AN/A
Runtime dependenciesZeropytest 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:N tag overrides global config
  • Feature-level tags@retry:N on 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-runner or CI without touching code
  • Logging — via standard logging module under behave_retry logger
  • Type-safepy.typed marker 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 varTypeDefaultMaps to
BEHAVE_RETRY_MAX_RETRIESint0max_retries
BEHAVE_RETRY_DELAYfloat0.0retry_delay
BEHAVE_RETRY_BACKOFFfloat1.0backoff_factor
BEHAVE_RETRY_MAX_TOTALintNonemax_total_retries

Explicit arguments always win. If you call setup_retry(context, max_retries=5), the env var is ignored.

How it works

  1. setup_retry patches behave.model.Scenario.run with a retry-aware wrapper.
  2. When a scenario fails, the wrapper checks:
    • Does the scenario have retries remaining? (global max_retries or @retry:N override)
    • Is the scenario tagged for retry? (if retry_tags is set)
    • Is the exception type eligible? (if retry_on is set)
    • Is the global retry budget exhausted? (if max_total_retries is set)
  3. If all checks pass, it resets the scenario state and re-runs it.
  4. Stats are tracked and available via retry_report() or stats.to_dict().

Documentation

SectionDescription
InstallationInstall from PyPI or source
Quick startThree-step setup guide
FeaturesComplete feature walkthrough with examples
ConfigurationAll parameters, validation, and precedence rules
ExamplesReal-world recipes for common use cases
API referenceFull autodoc API
ChangelogVersion history

Contributing

Contributions are welcome! See CONTRIBUTING.md for guidelines.

License

MIT — Copyright (c) 2026 Mathias Paulenko