Metric · CI/CD

What is flaky test rate, and how do you measure it in Metabase?

Flaky test rate is the share of tests — or of runs — that both pass and fail against the same code. Flaky tests erode the one thing a test suite is for: a trustworthy verdict. They drag down first-attempt build success rate, stretch pipeline duration with reruns, and train developers to ignore red builds. Measure the rate in Metabase from per-test results exported from Jenkins, CircleCI, Buildkite, or Azure DevOps Pipelines.

TL;DR — a test that failed and then passed on the same commit is flaky by construction; that's the strict definition. Rank the flaky set by engineering time wasted (failures × reruns × duration), not by raw count.

What does flaky test rate measure?

It measures how much of your suite's signal is noise. Two definitions are in common use, and it matters which one you pick:

  • Strict (same-commit): a test that produced both a pass and a fail against an identical commit_sha is nondeterministic — no further evidence needed. This requires retry or repeated-run data, which is exactly what CI retries produce.
  • Loose (trailing-window proxy): a test whose failure rate over the last few weeks sits strictly between 0% and 100%. No retry data needed, but it overcounts: a genuinely broken test that got fixed mid-window looks flaky under this definition. Use it as a candidate list, not a verdict.

Whichever definition feeds the list, prioritize by cost, not count: a flaky five-second unit test is an annoyance, while a flaky twelve-minute integration test that fails weekly burns hours of rerun time and blocks merges. Track newly-flaky tests per week too — a suite where the flaky set is churning is a suite where reliability debt is being created faster than it's paid down, which eventually shows up across your software delivery analytics as slower lead time for changes and noisier DORA metrics.

What data does it need?

  • A test_results table with one row per test execution: build_id, commit_sha, suite, test_name,result, run_at, duration_seconds,attempt.
  • The commit_sha is what makes the strict definition possible — without it you can't tell a flaky test from a broken-then-fixed one.
  • The attempt number, so retried executions are recorded rather than overwritten — auto-retry that discards first attempts destroys the evidence.

SQL patterns

Tests that both passed and failed on the same commit (14 days)PostgreSQL
WITH per_commit AS (
  SELECT
    suite,
    test_name,
    commit_sha,
    COUNT(*) FILTER (WHERE result = 'pass') AS passes,
    COUNT(*) FILTER (WHERE result = 'fail') AS failures
  FROM test_results
  WHERE run_at >= CURRENT_DATE - INTERVAL '14 days'
  GROUP BY suite, test_name, commit_sha
)
SELECT
  suite,
  test_name,
  COUNT(*) AS flaky_commits,
  SUM(failures) AS flaky_failures
FROM per_commit
WHERE passes > 0
  AND failures > 0
GROUP BY suite, test_name
ORDER BY flaky_failures DESC;
Share of failed builds explained by flaky tests, by weekPostgreSQL
WITH flaky AS (
  SELECT DISTINCT suite, test_name
  FROM test_results
  GROUP BY suite, test_name, commit_sha
  HAVING COUNT(*) FILTER (WHERE result = 'pass') > 0
    AND COUNT(*) FILTER (WHERE result = 'fail') > 0
),
failed_builds AS (
  SELECT
    t.build_id,
    date_trunc('week', MIN(t.run_at)) AS week,
    COUNT(*) FILTER (WHERE t.result = 'fail') AS failed_tests,
    COUNT(*) FILTER (
      WHERE t.result = 'fail'
        AND (t.suite, t.test_name) IN (SELECT suite, test_name FROM flaky)
    ) AS flaky_failed_tests
  FROM test_results t
  GROUP BY t.build_id
  HAVING COUNT(*) FILTER (WHERE t.result = 'fail') > 0
)
SELECT
  week,
  COUNT(*) AS failed_builds,
  ROUND(
    100.0 * COUNT(*) FILTER (WHERE failed_tests = flaky_failed_tests)
    / NULLIF(COUNT(*), 0), 1
  ) AS flaky_only_failures_pct
FROM failed_builds
GROUP BY week
ORDER BY week;
Time wasted per flaky test (trailing 30 days)PostgreSQL
WITH flaky AS (
  SELECT DISTINCT suite, test_name
  FROM test_results
  WHERE run_at >= CURRENT_DATE - INTERVAL '30 days'
  GROUP BY suite, test_name, commit_sha
  HAVING COUNT(*) FILTER (WHERE result = 'pass') > 0
    AND COUNT(*) FILTER (WHERE result = 'fail') > 0
)
SELECT
  t.suite,
  t.test_name,
  COUNT(*) FILTER (WHERE t.result = 'fail') AS failures,
  ROUND(
    COUNT(*) FILTER (WHERE t.result = 'fail')
    * (percentile_cont(0.5) WITHIN GROUP (
        ORDER BY t.duration_seconds
      ))::numeric / 60, 1
  ) AS est_minutes_wasted
FROM test_results t
JOIN flaky f
  ON f.suite = t.suite AND f.test_name = t.test_name
WHERE t.run_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY t.suite, t.test_name
ORDER BY est_minutes_wasted DESC
LIMIT 20;

Pitfalls

Calling broken-then-fixed tests flaky.→ A test that failed for two days and then passed forever wasn't flaky — it was broken, and someone fixed it. Without same-commit evidence, the trailing-window proxy sweeps these in and inflates the rate. Treat proxy hits as candidates to confirm, not convictions.
Auto-retrying everything and hiding the cost.→ Blanket retries make build success rate look healthy while every build quietly runs longer and the flaky set grows. If you retry, log every attempt and keep the time-wasted ranking visible — the tax should stay on the books.
Deleting flaky tests instead of quarantining them.→ Deletion trades a reliability problem for a coverage hole nobody remembers creating. Quarantine instead: move the test out of the blocking path, assign an owner and a deadline, and review the quarantine list weekly so it shrinks instead of becoming a graveyard.

Where this metric applies

Metrics

Dashboards

FAQ

How is a flaky test different from a failing test?
Determinism. A failing test fails every time against the same code — it's telling you something is broken, and fixing the code fixes the test. A flaky test fails nondeterministically: same commit, different outcomes, usually because of timing assumptions, shared state, or infrastructure. That's why the strict definition requires same-commit evidence — a pass and a fail against one commit_sha proves nondeterminism by construction, while a test that failed on one commit and passed on the next was probably just a bug that got fixed.
Do automatic retries fix flakiness?
No — they hide it and charge every build for the privilege. Auto-retry turns a visible red build into an invisible tax: the failure disappears from build success rate while the reruns quietly extend pipeline duration for everyone. Retries are a reasonable stopgap while a fix is in flight, but only if the retried failures are still recorded and ranked — otherwise the flaky set grows until the suite's verdict means nothing.
How do you calculate flaky test rate?
The strict version: the share of tests with both a pass and a fail against the same commit_sha in a window — group test_results by test and commit, keep groups where passes > 0 AND failures > 0. The looser proxy — tests whose failure rate over a trailing window sits strictly between 0% and 100% — needs no retry data but overcounts tests that were genuinely broken and then fixed. Whichever you use, rank by time wasted (failures × reruns × duration), not raw count.
How do you track flaky tests in Metabase?
Export per-test results — most CI tools emit JUnit XML that an ETL job can land in a warehouse — from Jenkins, CircleCI, Buildkite, or Azure DevOps Pipelines into a test_results table with commit, result, duration, and attempt number. Chart the same-commit flaky list, the weekly flaky-failure share, and the time-wasted ranking on a CI pipeline health dashboard, and review the top of the list weekly.