rspec-rewind
Deterministic retry orchestration for flaky RSpec examples.
Installation • Quick Start • Per-Example Controls • Configuration • Observability • Compatibility
rspec-rewind is a modern retry gem for RSpec, inspired by rspec-retry, with deterministic control and flaky-test observability for CI-heavy projects.
Why rspec-rewind
-
retriesalways means "extra attempts" (not total attempts). - Retry filtering with
retry_on,skip_retry_on, andretry_if. - Configurable delay via fixed, linear, exponential, or custom backoff.
- Suite-level retry budget to prevent hidden retry inflation.
- Flaky detection hooks and optional JSONL reporting.
Installation
Add to your Gemfile:
gem "rspec-rewind"Then run:
bundle installQuick Start
require "rspec/rewind" installs an around hook automatically.
# spec/spec_helper.rb
require "rspec/rewind"
RSpec::Rewind.configure do |config|
config.default_retries = 1
config.backoff = RSpec::Rewind::Backoff.exponential(base: 0.1, factor: 2.0, max: 1.0, jitter: 0.2)
config.retry_on = [Net::ReadTimeout, Errno::ECONNRESET]
config.skip_retry_on = [NoMethodError]
config.retry_budget = 20
config.flaky_report_path = "tmp/rspec-rewind/flaky.jsonl"
endBasic per-example retry:
it "eventually becomes consistent", rewind: 2 do
expect(fetch_remote_state).to eq("ready")
endPer-Example Controls
it "uses metadata overrides",
rewind: 3,
rewind_wait: 0.2,
rewind_retry_on: [Net::ReadTimeout, /502/],
rewind_skip_retry_on: [NoMethodError],
rewind_if: ->(exception, _example) { exception.message.include?("gateway") } do
expect(call_api).to eq(:ok)
end| Metadata key | Description |
|---|---|
rewind |
Retry count override. true = use default, false = disable retries for that example/group. |
rewind_wait |
Fixed sleep before next attempt. |
rewind_backoff |
Backoff strategy (numeric or callable). |
rewind_retry_on |
Extra allow-list matchers. |
rewind_skip_retry_on |
Extra deny-list matchers (checked first). |
rewind_if |
Predicate (exception) or (exception, example) returning truthy/falsey. |
Matcher types for retry_on and skip_retry_on: Module, Regexp, or callable.
Configuration
RSpec::Rewind.configure do |config|
config.default_retries = 0
config.backoff = RSpec::Rewind::Backoff.fixed(0)
config.retry_on = []
config.skip_retry_on = []
config.retry_if = nil
config.retry_callback = nil
config.flaky_callback = nil
config.retry_budget = nil
config.flaky_report_path = nil
config.verbose = false
config.display_retry_failure_messages = false
config.clear_lets_on_failure = true
endBackoff helpers:
RSpec::Rewind::Backoff.fixed(0.2)
RSpec::Rewind::Backoff.linear(step: 0.1, max: 1.0)
RSpec::Rewind::Backoff.exponential(base: 0.1, factor: 2.0, max: 2.0, jitter: 0.2)Environment override:
RSPEC_REWIND_RETRIES=2 bundle exec rspecRSPEC_REWIND_RETRIES has highest priority over defaults and metadata.
Retry Decision Order
- Stop if no exception happened.
- Stop if exception matches any
skip_retry_on. - If
retry_onis set, stop unless exception matches at least one matcher. - If
retry_ifexists, retry only when predicate returns truthy. - Stop if retry budget is exhausted.
Observability
Retry and Flaky Callbacks
RSpec::Rewind.configure do |config|
config.retry_callback = ->(event) do
puts "[retry] #{event.example_id} attempt=#{event.attempt}/#{event.retries}"
end
config.flaky_callback = ->(event) do
puts "[flaky] #{event.description} (attempt #{event.attempt})"
end
endJSONL Flaky Report
RSpec::Rewind.configure do |config|
config.flaky_report_path = "tmp/rspec-rewind/flaky.jsonl"
endEach flaky JSONL row includes:
schema_version-
status(flaky) retry_reasonexample_iddescriptionlocationattemptretriesexception_classexception_messagedurationsleep_secondstimestamp
Compatibility
- Ruby
>= 3.1 - RSpec Core
>= 3.12,< 4.0
Development
bundle exec rspec
bundle exec rake rbsCI validates:
- Specs across Ruby 3.1, 3.2, 3.3, 3.4, 4.0, and head
- Minimum compatibility with RSpec 3.12 (
BUNDLE_GEMFILE=gemfiles/rspec_3_12.gemfile) - Type signatures (
rake rbs) - Coverage threshold (
COVERAGE=1 rspec) - Gem packaging (
rake build) - Dependency security audit (
bundler-audit)
Contributing
Bug reports and pull requests are welcome on GitHub: https://github.com/ydah/rspec-rewind
License
Released under the MIT License.