Skip to content
Back to skills

Flaky Test Quarantine

ASecurity

Builds a quarantine workflow for flaky tests - marks the test with the framework's skip/fixme/retry annotation, records the failure-rate observation and a bisect link in the annotation body, sets an auto-expiry date, and produces a CI report listing every quarantined test that has expired and needs re-evaluation. Use when a flaky test is blocking the trunk and must be removed from the gating path without losing track of it.

  • 10 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 12, 2026
testingtypescriptgobashtestinggitapi

Works with

  • api

Security analysis

A100/100

Pro scans all 20 files and shows the line behind each finding

Scanned October 12, 2026

npx -y skills add testland/qa --skill flaky-test-quarantine --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Flaky Test Quarantine?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Flaky Test Quarantine
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/testland-flaky-test-quarantine/badge)](https://www.skillsdirectory.com/skills/testland-flaky-test-quarantine)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: flaky-test-quarantine
description: "Builds a quarantine workflow for flaky tests - marks the test with the framework's skip/fixme/retry annotation, records the failure-rate observation and a bisect link in the annotation body, sets an auto-expiry date, and produces a CI report listing every quarantined test that has expired and needs re-evaluation. Use when a flaky test is blocking the trunk and must be removed from the gating path without losing track of it."
---

# flaky-test-quarantine

## Overview

A "flaky test" is a test that produces inconsistent pass/fail results
across runs without an underlying code change ([google-flaky][gtb]).
Industry consensus from Google Testing Blog and similar
practitioner-engineering sources is that flaky tests should be
**isolated from the gating path** rather than left to mask real
regressions or be silently ignored ([google-flaky][gtb]).

[gtb]: https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html

> **Terminology note:** "flaky test" is a practitioner-emergent term
> popularized by the Google Testing Blog. ISTQB does not maintain a
> canonical entry for it. This skill cites industry-engineering
> sources, not ISTQB authority.

## How to use

1. **Confirm it is a flake, not a regression** - a 1% to 50% failure
   rate, not 100% (see "When to use"). A 100%-failing test is a
   regression: bisect and fix, do not quarantine.
2. **Mark** the test with the framework's skip / fixme / retry
   annotation (Step 1).
3. **Annotate** the body with date, issue link, failure rate, bisect
   status, and a `Re-evaluate by` date (Step 2) - this is the
   load-bearing, machine-parseable part.
4. **Set the TTL** that produces the `Re-evaluate by` date (Step 3).
5. **Wire the expired-quarantine report** into a scheduled CI job (Step 4
   and [references/ci-quarantine-report.md](references/ci-quarantine-report.md)).
6. **Prune on expiry** - fix and un-quarantine, renew once (never past two
   consecutive renewals), or delete (Step 5).

## When to use

- A test is failing on the trunk between 1% and 50% of runs (above 50%
  it's not flaky, it's broken; under 1% the noise is acceptable for
  most projects).
- The team's incident process has been triggered more than once by the
  same test.
- A new feature merge is blocked by a known-flaky pre-existing test
  that's unrelated to the change.

If the test fails 100% of the time after a code change, it's a
regression - bisect to the introducing commit and fix, do not
quarantine.

## Worked example

Test `tests/checkout.spec.ts:42` fails about 12% of runs on the
`tablet-768` project in CI. An unrelated feature PR is blocked by it.

1. **Confirm it is a flake.** 12% sits between 1% and 50%, and the test
   predates the PR - quarantine it, do not treat it as a regression of
   the PR under review.
2. **Mark and annotate** with a single `test.fixme()` carrying the full
   record (Steps 1 and 2):

```typescript
test('checkout flow flaky test', async ({ page }) => {
  test.fixme(
    true,
    'Quarantined 2026-07-20 (#1234) - fails ~12% of runs on tablet-768; bisect inconclusive. Re-evaluate by 2026-08-19. Owner: @web-platform.',
  );
  // ... test body, no longer runs
});
```

3. **Set the TTL.** The default 30-day TTL (Step 3) puts
   `Re-evaluate by 2026-08-19` in the annotation. The PR merges the same
   day, unblocked.
4. **Let CI track it.** The Monday report job (Step 4) greps every
   `test.fixme` annotation. On the first Monday after 2026-08-19 it
   prints `EXPIRED` for this entry and opens a tracking issue.
5. **Prune.** A fix for the `tablet-768` layout race has since landed, so
   remove `test.fixme()`, re-run the test at depth to confirm it is
   green, and close the issue. Had it still failed, renew once with an
   updated annotation - never past two consecutive renewals.

## Step 1 - Mark the test

### Playwright

`test.fixme()` is the canonical Playwright primitive for "this test is
broken; do not run past this point" ([pw-test][pw-test]):

[pw-test]: https://playwright.dev/docs/api/class-test

```typescript
test('checkout flow flaky test', async ({ page }) => {
  test.fixme(
    true,
    'Quarantined 2026-05-04 (#1234) - fails ~12% of runs on tablet-768; bisect inconclusive. Re-evaluate by 2026-06-04.',
  );
  // ... test body, no longer runs
});
```

`test.fixme(condition, description)` skips with the description
visible in the report. Unlike `test.skip()`, `fixme` carries the
explicit "this needs to be fixed" intent ([pw-test][pw-test]).

If the goal is to allow retries before quarantining, use the
`retries` config first ([pw-retries][pw-retries]):

[pw-retries]: https://playwright.dev/docs/test-retries

```typescript
// playwright.config.ts
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
});
```

A test that passes on retry is reported with the **`flaky`** status
(distinct from `passed` and `failed`); track these separately -
flaky-but-passing tests are quarantine candidates, not yet
quarantined ([pw-retries][pw-retries]).

### Cypress

Cypress configures retries at the suite level via
`Cypress.config('retries', { runMode: 2, openMode: 0 })`. For
quarantining individual specs, use `it.skip(...)` or the
[`cypress-grep`](https://github.com/cypress-io/cypress-grep) plugin's
tagging convention.

### Jest / Vitest

`test.skip(...)` and `test.todo(...)` are the canonical primitives.
For periodic auto-evaluation, use `test.skip.if(condition)` patterns or
introduce a project-specific tagging convention parsed by your CI.

### JUnit / TestNG (JVM)

JUnit 5: `@Disabled("Quarantined 2026-05-04 (#1234) - ...")`.
TestNG: `@Test(enabled = false, description = "...")`.
For per-method retries before quarantine, JUnit 5's `@RetryingTest(N)`
extension and TestNG's `@Test(retryAnalyzer = ...)`.

## Step 2 - Annotate with failure rate + bisect link + expiry

The annotation body is the load-bearing part of the workflow. Every
quarantine record carries:

| Field             | Required | Format                                                |
|-------------------|----------|-------------------------------------------------------|
| Date              | yes      | `YYYY-MM-DD` of the quarantine.                       |
| Issue link        | yes      | `#1234` or full URL - links a tracked ticket.         |
| Failure rate      | yes      | `~12% of runs` - measured, not guessed.               |
| Bisect status     | yes      | `bisect inconclusive` / `bisected to commit abc1234` / `not yet bisected`. |
| Re-evaluate by    | yes      | `YYYY-MM-DD` - the auto-expiry date.                  |
| Owner             | optional | `@team-handle` for routing.                           |

The format is parseable by the re-evaluation report (Step 4):

```
Quarantined 2026-05-04 (#1234) - fails ~12% of runs on tablet-768;
bisect inconclusive. Re-evaluate by 2026-06-04. Owner: @web-platform.
```

## Step 3 - Auto-expiry

Default TTL: **30 days**. Picked because:

- Long enough to fix non-trivial issues without creating churn.
- Short enough that quarantined tests don't become a permanent
  graveyard.

Adjust per project:

- 90 days for low-traffic suites where the underlying issue is known
  but un-prioritized.
- 14 days for high-traffic CI where flakiness is an actively-monitored
  metric.

## Step 4 - Re-evaluation report

A scheduled CI job greps every quarantine annotation, extracts the
`Re-evaluate by` date, and lists the entries that have expired - then
opens a tracking issue (or posts to Slack) per expired entry. Because the
Step 2 format is machine-parseable, the whole report is a short grep.
The Bash report script and the scheduled GitHub Actions workflow are in
[references/ci-quarantine-report.md](references/ci-quarantine-report.md).

## Step 5 - Pruning rules

When a re-evaluation expires, the team has three options:

| Outcome                               | Action                                                       |
|---------------------------------------|--------------------------------------------------------------|
| Underlying issue fixed                | Remove `test.fixme()` and re-run; close the issue.           |
| Underlying issue still present        | Renew the quarantine for one more TTL with updated annotation; **never** more than two consecutive renewals - at that point, delete the test or rewrite it. |
| The test is no longer relevant        | Delete the test outright; close the issue.                   |

The two-renewal cap is the lever that prevents quarantine from
becoming a permanent dead-letter. Past two renewals, the team has
either lost interest in the assertion or the test is fundamentally
unfixable - both signal "delete."

## References

- [google-flaky][gtb] - Google Testing Blog on flaky tests at scale;
  practitioner-emergent canonical reference for the term.
- [pw-test][pw-test] - Playwright `test.fixme()` / `test.skip()` /
  `test.fail()` API.
- [pw-retries][pw-retries] - Playwright retries config + `flaky`
  status reporting.
- [references/ci-quarantine-report.md](references/ci-quarantine-report.md) -
  the Step 4 report script + scheduled CI workflow.
- `flake-pattern-reference` -
  catalog of flake patterns to consult during bisect.

Files in this skill

  • .tessl-plugin/plugin.json564 B
  • SKILL.md9 KB
  • evals/brand-new-test-blocking-its-own-pr/criteria.json4.4 KB
  • evals/brand-new-test-blocking-its-own-pr/task.md3.9 KB
  • evals/deletion-pr-review/criteria.json4.5 KB
  • evals/deletion-pr-review/task.md4 KB
  • evals/disabled-annotations-audit/criteria.json4.3 KB
  • evals/disabled-annotations-audit/task.md4.1 KB
  • evals/intermittent-capture-failure/criteria.json4.6 KB
  • evals/intermittent-capture-failure/task.md4.2 KB
  • evals/nothing-tells-us-when-to-look-again/criteria.json4.5 KB
  • evals/nothing-tells-us-when-to-look-again/task.md4 KB
  • evals/retry-everything-directive/criteria.json4.7 KB
  • evals/retry-everything-directive/task.md4 KB
  • evals/rotted-skip-list-review/criteria.json5.1 KB
  • evals/rotted-skip-list-review/task.md4.2 KB
  • evals/six-red-tests-before-branch-cut/criteria.json4.9 KB
  • evals/six-red-tests-before-branch-cut/task.md5 KB
  • evals/standup-anecdotes-vs-run-history/criteria.json4.5 KB
  • evals/standup-anecdotes-vs-run-history/task.md3.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.