Skip to main content

flakerate

Measure how flaky a test actually is, find out why, and quarantine it with an expiry date.

PyPI Python CI License

The usual response to a flaky test is to retry it until it passes. That turns the build green and throws away the only evidence there was. You never learn whether it fails one run in fifty or one in three, and you never learn why, so it stays in the suite getting slowly worse.

flakerate runs the suite repeatedly, reports a failure rate with a confidence interval, diagnoses the cause by experiment, and writes a quarantine file that expires.

Installation

pip install flakerate

Requires Python 3.10+ and pytest 8+.

Quick start

flakerate run -n 20 -- tests
20 runs, 412 tests, 3 flaky

failed  rate  95% range  cause  test
------  ----  ---------  -----  ---------------------------------------------
  7/20   35%    18%-57%         tests/test_orders.py::test_cancel_refunds
  6/20   30%    15%-52%         tests/test_api.py::test_rate_limit_headers
  1/20    5%     1%-24%         tests/test_sync.py::test_backfill_idempotent

Every run gets a different shuffle seed, because a suite that always runs in the same order will never show you an order-dependent failure, and that is the most common kind.

The range is a Wilson score interval and it is the number to make decisions against. Note the last row: one failure in twenty runs is anywhere from 1% to 24%. That is not a measurement yet, it is a signal to run it more times. Wilson rather than the textbook normal approximation because the counts are small and the rates sit near zero, which is exactly where the normal approximation returns a negative lower bound.

Diagnosing the cause

flakerate run -n 30 --classify -- tests
failed  rate  95% range  cause             test
------  ----  ---------  ----------------  ---------------------------------------------
  9/30   30%    17%-48%  order-dependent   tests/test_orders.py::test_cancel_refunds
  8/30   27%    14%-45%  environment       tests/test_api.py::test_rate_limit_headers

tests/test_orders.py::test_cancel_refunds
  in suite: 9/30 failed, alone: 0/5 failed, fixed order: 0/3 failed
  passes on its own, so something earlier in the suite leaves state behind. Look for module-level
  globals, a cached singleton, an unclosed database transaction, or a fixture with a wider scope
  than it needs.

Classification runs experiments rather than pattern-matching tracebacks.

Experiment What it answers
Run the test alone, several times Is the rest of the suite involved at all?
Run the whole suite, several times, in one fixed order Is the order the variable, or something else?
Cause What it means
order-dependent Passes alone. Something that runs before it leaves state behind.
nondeterministic Fails alone too. An unseeded random source, a real clock, dict ordering, a thread.
environment Fails alone, and the failures look like they come from outside the process.
unclear The test could not be run on its own, so no conclusion is offered.

Failure messages only ever refine a conclusion the experiments already reached. A traceback mentioning a socket is suggestive, not proof, and it is never the sole basis for a verdict.

The isolation experiment is per test and cheap. The fixed-order runs are shared across every flaky test in the report, so classifying ten of them costs the same suite runs as classifying one.

Quarantine

flakerate run -n 30 --classify --quarantine --days 14 -- tests

Writes flakerate.toml:

[quarantine."tests/test_orders.py::test_cancel_refunds"]
reason = "tests/test_orders.py:88: AssertionError"
rate = 0.3
cause = "order-dependent"
added = "2026-08-24"
expires = "2026-09-07"

Commit it, then in CI:

- run: pip install flakerate
- run: flakerate check
- run: FLAKERATE_QUARANTINE=flakerate.toml pytest

Quarantined tests still run and still report. They are marked non-strict xfail, so one that starts passing again shows up as an xpass rather than silently becoming load-bearing.

An entry with no expiry date, or one that cannot be read, counts as expired. Failing the other way would make expires = "soon" a quarantine nobody ever sees the end of, which is the thing the date exists to prevent.

flakerate check fails the build once an entry is past its date:

$ flakerate check
fail 2 quarantine entry/entries have expired

expired     cause             test
----------  ----------------  ---------------------------------------------
2026-09-07  order-dependent   tests/test_orders.py::test_cancel_refunds
2026-09-07  environment       tests/test_api.py::test_rate_limit_headers

Fix the test, or renew it on purpose with 'flakerate run --quarantine'.

That date is the point. Quarantine without one is deletion with extra steps, and every codebase that has one has tests in it nobody has looked at in three years.

Configuration

All keys live under [tool.flakerate] in pyproject.toml. All are optional.

Key Type Default Meaning
runs integer 20 How many times run repeats the suite
quarantine string "flakerate.toml" Path to the quarantine file
pytest_args list of strings [] Arguments used when none are given on the command line
max_flake_rate number, 0 to 1 0.0 Reserved for a future rate-based gate

Values are type-checked rather than coerced, so max_flake_rate = true is an error rather than quietly becoming 1.0.

Command reference

Command What it does
flakerate run -n N -- <pytest args> Run the suite N times and report flake rates
flakerate run --classify Also diagnose the cause of each flaky test
flakerate run --quarantine --days N Write findings to the quarantine file
flakerate run --no-shuffle Keep pytest's own order
flakerate check Exit non-zero if a quarantine entry has expired
flakerate list Show what is quarantined and how long it has left

Every command takes --json.

pytest options

Option Environment variable Effect
--flakerate-report PATH FLAKERATE_REPORT Write a JSON outcome report
--flakerate-seed N FLAKERATE_SEED Shuffle test order with a reproducible seed
--flakerate-quarantine PATH FLAKERATE_QUARANTINE Apply a quarantine file

How it compares

Tool Measures the rate Confidence interval Diagnoses cause Quarantine expiry Maintained
pytest-rerunfailures no no no no yes
flaky no no no no last release 2024
pytest-flakefinder reruns only no no no last release 2022
flakerate yes yes yes yes yes

Notes

How many runs you need depends on what you are trying to catch. Twenty finds anything failing more than about 1 in 10. A 1-in-50 flake needs a few hundred, and flakerate run says so when the sample is too small to conclude anything.

A test that fails on every run is broken, not flaky, and is reported separately.

Failures in setup and teardown count as failures. A fixture that intermittently explodes is a flaky test to anyone waiting on the build, whatever pytest calls it internally.

A pytest run that exits 2 or higher (a collection error, a bad argument) is discarded rather than counted as everything failing.

--flakerate-seed reproduces an exact order, so a failure found here can be re-run on its own.

The plugin is a pytest entry point, so it is imported by every pytest process on the machine. It registers nothing and returns immediately from every hook unless given a report path or a quarantine file.

pytest-xdist is not supported yet: outcomes from several worker processes are not merged.

Contributing

Bug reports and pull requests are welcome. uv sync then uv run pytest to get started.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flakerate-0.1.0.tar.gz (72.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

flakerate-0.1.0-py3-none-any.whl (23.0 kB view details)

Uploaded Python 3

File details

Details for the file flakerate-0.1.0.tar.gz.

File metadata

  • Download URL: flakerate-0.1.0.tar.gz
  • Upload date:
  • Size: 72.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for flakerate-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9f45bbea1b0c272d5166cdfd3eb4f1b75a3195c239f2fab20ce7388ff8e1a97d
MD5 0315339126b0c1d716c8693cff48ccf6
BLAKE2b-256 7d62e1f20cb6b8618d37f6a3ebd33cdb873233c1fc0f59df8da5406bc7c866f9

See more details on using hashes here.

File details

Details for the file flakerate-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: flakerate-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 23.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for flakerate-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 50b62b11695ec0ff800f6791857c24eaf39c66cf410fa214f032da39fae4afcf
MD5 7917e098c312d3674b63aa208ea7523b
BLAKE2b-256 208a407657405b1b486bf4f20fa2a32e8d999c35361f7b422b412c5d48e5d9da

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page