Automatic retry for failed Behave scenarios — CLI flags, tag overrides, exception filtering, flakiness stats
Project description
behave-retry
Automatic retry for failed Behave scenarios — CLI flags, 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 with a lightweight, zero-dependency library that integrates through Behave's hook system.
Table of contents
Install
pip install behave-retry
For development:
pip install -e ".[dev]"
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 retried up to 3 times automatically.
Features
Global retry
setup_retry(context, max_retries=3)
Retry every failed scenario up to 3 times.
Tag-filtered retry
setup_retry(context, max_retries=3, retry_tags=["@flaky"])
Only retry scenarios tagged with @flaky.
@flaky
Scenario: Login with slow network
Given the server is slow
...
Exception-filtered retry
setup_retry(
context,
max_retries=3,
retry_on=[AssertionError, TimeoutError],
)
Only retry when the scenario fails with specific exception types. Subclasses of the listed exceptions also match.
Per-scenario override
Override the global retry count per scenario using the @retry:N tag:
@retry:5
Scenario: Very flaky test
...
@retry:0
Scenario: Never retry this
...
The first @retry:N tag wins. @retry:0 disables retry for that scenario.
Cleanup between retries
Define an after_retry hook in your environment.py to clean up state between retry attempts:
# environment.py
def after_retry(context, scenario):
# Close browser, reset DB, clean state
if hasattr(context, "driver"):
context.driver.quit()
Retry stats
from behave_retry import retry_report
def after_all(context):
report = retry_report(context)
print(report)
Output:
Retry Summary:
Total retries: 5
Scenarios retried: 3
Passed on retry: 2
Failed after retry: 1
- "Login with invalid credentials" — 3 attempts, failed (AssertionError)
- "Search products" — 2 attempts, passed
- "Checkout flow" — 1 attempt, passed
API reference
setup_retry(context, max_retries=0, retry_tags=None, retry_on=None)
Configure retry on the behave context. Call this in before_all.
| Parameter | Type | Default | Description |
|---|---|---|---|
context |
Any |
— | Behave context object |
max_retries |
int |
0 |
Maximum retries per scenario (0 = no retry) |
retry_tags |
list[str] | None |
None |
Only retry scenarios with these tags |
retry_on |
list[type[Exception]] | None |
None |
Only retry on these exception types |
after_scenario_hook(context, scenario)
Handle retry logic. Call this in after_scenario.
retry_report(context) -> str
Get a human-readable retry summary. Call this in after_all.
RetryConfig
Dataclass for configuration.
| Attribute | Type | Default | Description |
|---|---|---|---|
max_retries |
int |
0 |
Maximum retries per scenario |
retry_tags |
list[str] |
[] |
Tag filter (empty = retry all) |
retry_on |
list[type[Exception]] |
[] |
Exception filter (empty = retry on any) |
Methods:
should_retry_tag(tags) -> bool— Check if scenario tags allow retryshould_retry_exception(exc) -> bool— Check if exception type allows retryget_scenario_retries(tags) -> int— Get max retries for a scenario, checking@retry:Noverride
RetryStats
Aggregate retry statistics.
| Property | Type | Description |
|---|---|---|
total_retries |
int |
Total retry attempts across all scenarios |
scenarios_retried |
list[ScenarioRetry] |
Per-scenario retry records |
scenarios_passed_on_retry |
int |
Scenarios that passed after retry |
scenarios_failed_after_retry |
int |
Scenarios that still failed after retry |
ScenarioRetry
Record of retry attempts for a single scenario.
| Attribute | Type | Description |
|---|---|---|
scenario |
str |
Scenario name |
attempts |
int |
Total attempts (including first run) |
final_status |
str |
"passed" or "failed" |
exceptions |
list[str] |
Exception types encountered |
| Property | Description |
|---|---|
was_retried |
True if retried at least once |
passed_on_retry |
True if passed after retry |
RetryExhaustedError
Raised when a scenario has been retried the maximum number of times.
parse_retry_tag(tags) -> int | None
Parse @retry:N tag from scenario tags. Returns N if found, None otherwise.
Configuration
Python version
behave-retry requires Python 3.11+.
Dependencies
Zero required dependencies. behave is only needed as a dev dependency for running tests.
Linting and formatting
The project uses Ruff with the following rule sets: E, F, W, I, N, UP, B, SIM.
ruff check .
Testing
pytest tests/ -v --cov
Coverage threshold is 90%.
Examples
Full environment.py
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],
)
def after_scenario(context, scenario):
after_scenario_hook(context, scenario)
def after_all(context):
print(retry_report(context))
Feature file with retry tags
@flaky
@retry:5
Feature: Flaky scenarios
@retry:0
Scenario: Never retry this
Given a stable condition
Then it should pass
Scenario: Retry up to 5 times
Given a flaky condition
Then it might fail
Contributing
Contributions are welcome! See CONTRIBUTING.md for guidelines.
License
MIT — Copyright (c) 2026 Mathias Paulenko
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file behave_retry-1.0.1.tar.gz.
File metadata
- Download URL: behave_retry-1.0.1.tar.gz
- Upload date:
- Size: 15.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ad1a57d8ff0f876062daed234a86966e8f7c61cb691625a308ccbed41b18019
|
|
| MD5 |
478f16dd89a7f0cb45c304cb8109c289
|
|
| BLAKE2b-256 |
bb3d701940b5b14dbc8e86f6ae2dce6c15543343088ce16ac7ddcaf0e60eaade
|
Provenance
The following attestation bundles were made for behave_retry-1.0.1.tar.gz:
Publisher:
release.yml on MathiasPaulenko/behave-retry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
behave_retry-1.0.1.tar.gz -
Subject digest:
7ad1a57d8ff0f876062daed234a86966e8f7c61cb691625a308ccbed41b18019 - Sigstore transparency entry: 2160988143
- Sigstore integration time:
-
Permalink:
MathiasPaulenko/behave-retry@edab509bf4ed17a6a3d21d361192e7297f3bea72 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/MathiasPaulenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@edab509bf4ed17a6a3d21d361192e7297f3bea72 -
Trigger Event:
push
-
Statement type:
File details
Details for the file behave_retry-1.0.1-py3-none-any.whl.
File metadata
- Download URL: behave_retry-1.0.1-py3-none-any.whl
- Upload date:
- Size: 9.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a5c6f591c6dacdcc2c6da1b45951fe163ea821d2ee821f749e89a60df12fc56
|
|
| MD5 |
21dae22f663057327f51dcdcf57b8cd0
|
|
| BLAKE2b-256 |
13e933a3158714eeb93f640c927574bb50b815bcd645be39cccd41704e45670b
|
Provenance
The following attestation bundles were made for behave_retry-1.0.1-py3-none-any.whl:
Publisher:
release.yml on MathiasPaulenko/behave-retry
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
behave_retry-1.0.1-py3-none-any.whl -
Subject digest:
0a5c6f591c6dacdcc2c6da1b45951fe163ea821d2ee821f749e89a60df12fc56 - Sigstore transparency entry: 2160988204
- Sigstore integration time:
-
Permalink:
MathiasPaulenko/behave-retry@edab509bf4ed17a6a3d21d361192e7297f3bea72 -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/MathiasPaulenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@edab509bf4ed17a6a3d21d361192e7297f3bea72 -
Trigger Event:
push
-
Statement type: