flake-dashboard-author
Builds a persistent flakiness infrastructure dashboard from JUnit XML or JSON CI run history: defines the flake-rate metric (failures per test over a configurable window), authors the data model, generates a Grafana time-series panel JSON or configures a Datadog CI Visibility view, derives the quarantine-candidate query, and wires trend alerts. Also generates the periodic (weekly / monthly) test-suite trend report - total runs, suite duration, flakiness rate, top failing tests, time-to-green per PR, week-over-week deltas - as a markdown summary for a team Slack channel or wiki page. Use when a team needs a long-lived observability surface for test reliability, or a scheduled comparable health report on top of it.
Install with skills.sh (any agent)
npx skills add testland/qa --skill flake-dashboard-authorflake-dashboard-author
Terminology note: "flaky test" is a practitioner-emergent term used in the industry engineering tradition (Google Testing Blog, google-flaky (opens in new window)). "Defect," "failure," and "test run" follow ISTQB Glossary v4.7.1 definitions. "Test suite" and "test case" are used per ISTQB as well.
A weekly markdown snapshot report gives a comparable point-in-time view. This skill builds the persistent infrastructure layer: a live dashboard that accumulates run history and surfaces the flake-rate metric continuously, rather than on demand.
Step 1 - Define the flake-rate metric
The canonical flake-rate formula for a single test T over a window of N runs is:
flake_rate(T, window) = (failed_runs(T, window) + retried_passed_runs(T, window))
-------------------------------------------------------
total_runs(T, window)A run counts as "retried-passed" when the test framework reports it as flaky (passed only after at least one retry). Playwright marks these in reporter.onTestEnd with result.status === 'flaky' (Playwright reporter API (opens in new window)). JUnit XML uses a <rerunFailure> element inside <testcase> (Surefire / junit-xml convention) or a <flakyFailure> element in the Jenkins JUnit plugin extension.
Choose your window at ingestion time. Recommended defaults:
| Team cadence | Window | Minimum runs before showing rate |
|---|---|---|
| Multiple deploys per day | 7 days | 20 |
| One deploy per day | 14 days | 10 |
| Weekly releases | 30 days | 5 |
Step 2 - Build the data model
Persist one row per test-case execution. Minimum schema:
CREATE TABLE test_runs (
run_id TEXT NOT NULL,
suite_name TEXT NOT NULL,
test_name TEXT NOT NULL,
status TEXT NOT NULL, -- 'passed' | 'failed' | 'flaky' | 'skipped'
duration_ms INTEGER NOT NULL,
branch TEXT,
commit_sha TEXT,
worker_index INTEGER,
started_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (run_id, suite_name, test_name)
);
CREATE INDEX idx_test_runs_name_time ON test_runs (test_name, started_at);status = 'flaky' is the retried-passed value emitted by Playwright retries (Playwright retries (opens in new window)) and by the Jenkins JUnit plugin's <flakyFailure> extension. For raw JUnit XML without retry markup, derive flaky by joining two rows with the same run_id + test_name where one is failed and the next is passed within the same CI run.
Populate from JUnit XML using xmllint --xpath:
# Extract per-testcase rows from a JUnit XML report
xmllint --xpath '//testcase' report.xml \
| python3 scripts/parse_junit.py --output jsonl >> test_runs.jsonlPopulate from Playwright JSON reporter output (--reporter=json) by iterating results[].suites[].specs[].tests[].results[].
Step 3 - Author the Grafana panel JSON
The following panel JSON renders a time-series of per-test flake rate over a 14-day rolling window. Paste it into Dashboard JSON model (toolbar Export > Copy JSON) or POST it to the Grafana Dashboard HTTP API (Grafana Dashboard API (opens in new window)).
{
"type": "timeseries",
"title": "Flake rate per test (14-day rolling)",
"datasource": { "type": "postgres", "uid": "${DS_POSTGRES}" },
"gridPos": { "h": 8, "w": 24, "x": 0, "y": 0 },
"id": 1,
"targets": [
{
"refId": "A",
"datasource": { "type": "postgres", "uid": "${DS_POSTGRES}" },
"rawSql": "SELECT date_trunc('day', started_at) AS time, test_name, ROUND(100.0 * SUM(CASE WHEN status IN ('failed','flaky') THEN 1 ELSE 0 END) / COUNT(*), 2) AS flake_rate FROM test_runs WHERE started_at >= NOW() - INTERVAL '14 days' GROUP BY 1, 2 ORDER BY 1",
"format": "time_series"
}
],
"fieldConfig": {
"defaults": {
"color": { "mode": "palette-classic" },
"unit": "percent",
"thresholds": {
"mode": "absolute",
"steps": [
{ "color": "green", "value": null },
{ "color": "yellow", "value": 2 },
{ "color": "red", "value": 5 }
]
},
"custom": {
"lineWidth": 2,
"fillOpacity": 10,
"pointSize": 5,
"showPoints": "auto",
"spanNulls": false
}
},
"overrides": []
},
"options": {
"legend": { "displayMode": "table", "placement": "bottom", "calcs": ["lastNotNull", "max"] },
"tooltip": { "mode": "multi", "sort": "desc" }
}
}Key fields per the Grafana time-series panel docs (opens in new window):
Step 4 - Configure Datadog CI Visibility (alternative)
If your team uses Datadog, CI Visibility ingests test results natively via the datadog-ci CLI or SDK reporters. The built-in CI Visibility - Tests dashboard (opens in new window) tracks Total Flaky Tests (updated every 30 minutes per Datadog flaky test docs (opens in new window)).
Datadog applies three tags automatically (Datadog flaky test docs (opens in new window)):
Quarantine query in CI Visibility Explorer:
@test.status:fail @test.is_flaky:trueFlakiness rate formula in a Datadog Timeboard widget using the Metrics query editor (CI Visibility emits ci.test.flaky as a count metric):
(count:ci.test.flaky{*} by {test.name}.as_count() /
count:ci.test.run{*} by {test.name}.as_count()) * 100Trend alert using a Datadog Monitor:
Step 5 - Quarantine-candidate query
A test becomes a quarantine candidate when its flake rate exceeds the team threshold over the window AND it has enough samples to be statistically meaningful. Recommended SQL query for the data model in Step 2:
SELECT
test_name,
suite_name,
COUNT(*) AS total_runs,
SUM(CASE WHEN status IN ('failed', 'flaky') THEN 1 ELSE 0 END) AS flaky_runs,
ROUND(
100.0 * SUM(CASE WHEN status IN ('failed', 'flaky') THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0), 2
) AS flake_rate_pct,
MAX(started_at) AS last_seen
FROM test_runs
WHERE started_at >= NOW() - INTERVAL '14 days'
GROUP BY test_name, suite_name
HAVING
COUNT(*) >= 10
AND ROUND(
100.0 * SUM(CASE WHEN status IN ('failed', 'flaky') THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0), 2
) >= 5
ORDER BY flake_rate_pct DESC;The HAVING COUNT(*) >= 10 guard prevents a test with 1 run and 1 failure from appearing as 100% flaky. Adjust the minimum run count per your window size using the table in Step 1.
Hand quarantine candidates to the flaky-test-quarantine skill, which enforces the two-week TTL and renewal cap.
Step 6 - Wire trend alerting in Grafana
Grafana managed alert rules evaluate expressions against your datasource on a configurable schedule (Grafana alert rules (opens in new window)).
Steps to create a flake-rate spike alert:
The fieldConfig.defaults.thresholds.steps bands in the panel JSON (green/yellow/red at null/2/5) visually mirror the alert thresholds so on-call engineers see the same boundary lines in the chart that trigger the alert (Grafana time-series thresholds (opens in new window)).
Worked example: bootstrap from a Playwright JSON report
Given pw-results.json (Playwright --reporter=json output):
# 1. Parse into the test_runs table
node scripts/ingest_playwright_json.js pw-results.json \
--db postgres://localhost/qa_metrics \
--branch "$CI_BRANCH" \
--commit "$CI_COMMIT_SHA" \
--run-id "$CI_RUN_ID"
# 2. Run the quarantine-candidate query and emit a CSV
psql postgres://localhost/qa_metrics \
-f scripts/quarantine_candidates.sql \
--csv > candidates-$(date +%F).csv
# 3. Import the Grafana dashboard JSON
curl -s -X POST http://grafana:3000/api/dashboards/import \
-H 'Content-Type: application/json' \
-u "$GRAFANA_USER:$GRAFANA_PASS" \
-d @dashboards/flakiness-overview.jsonAfter the first ingestion, the Grafana panel populates immediately for the last 14 days of history that was just loaded. The trend alert begins evaluating on the next 1-minute evaluation cycle.
Periodic trend report
The dashboard is the live surface; a scheduled markdown report gives the team a stable, comparable point-in-time view on top of the same data.
| Metric | Definition |
|---|---|
| Total runs | Count of test executions in the window. |
| Total suite duration (CI) | Sum of time attributes across all <testcase> elements. |
| Suite duration mean per run | Total duration / number of CI runs. |
| Pass rate | (passed + flaky-passed) / total runs. |
| Flakiness rate | (flaky runs per pw-retries (opens in new window)) / total. |
| Top failing tests | Top 5 by failure count. |
| Top slowest tests | Top 5 by mean duration. |
| Time-to-green per PR | Mean wall-clock from first PR push to first all-green CI. |
| Quarantine count | Tests under test.fixme() / it.skip() annotations. |
Report shape:
# Test Suite Trend Report - week of <YYYY-MM-DD>
**Reporting window:** YYYY-MM-DD to YYYY-MM-DD · **Comparison window:** prior 7 days
## Health summary
| Metric | This week | Last week | Δ |
|------------------------------|-----------:|----------:|---------:|
| Total CI runs | 820 | 795 | +3.1% |
| Suite mean duration | 11m 42s | 10m 58s | +6.7% |
| Pass rate | 96.3% | 97.1% | -0.8% |
| Flakiness rate | 2.4% | 1.7% | +0.7% |
| Time-to-green per PR (mean) | 23 min | 18 min | +5 min |
| Quarantined tests | 14 | 12 | +2 |
## Top failing tests
| Test | Failures | Runs | Failure rate | Trend |
|-----------------------------------|---------:|------:|-------------:|-------|
| tests/checkout.spec.ts:42 | 18 | 820 | 2.2% | ↑↑ |
| tests/auth.spec.ts:88 | 12 | 820 | 1.5% | ↑ |
## Notes
- **Flakiness up 0.7 pp** - `checkout.spec.ts:42` started flaking on tablet-768 viewport.
- **Suite duration up 6.7%** - accounted for by 3 new `dashboard.spec.ts` tests.
## Suggested follow-ups
1. Hand `tests/checkout.spec.ts:42` to the `e2e-flake-bisector` agent - flakiness trend (↑↑) is the strongest signal of the week.
2. Review the 14 quarantined tests against the two-renewal cap from `flaky-test-quarantine`.Trend arrows: ↑↑ >50% WoW increase, ↑ 10-50%, → ±10%, ↓ 10-50% decrease, ↓↓ >50% decrease.
When pass rate drops 5 pp and flakiness doubles in one week with two specific tests accounting for most of the drop, flag them as "regression, not flake" (a jump from <0.5% to >2% in one week is unlikely to be variance) and recommend the regression-bisector agent. For improving weeks, Notes surfaces the cleanup pattern (e.g. "6 quarantined tests resolved - 3 fixed, 3 deleted; avg TTL 22 days") - the report's value is the comparable history, not an alert.
Cadence and caveats: weekly for daily-CI teams; monthly for slow-cadence projects (sparse 7-day data); on-demand for incident triage (window=2 days). The report surfaces what changed, not why - hand off to a bisector for root cause. It is sensitive to CI volume changes (adding 10 tests trivially raises suite duration; note it in Notes), and quarantine count alone isn't a quality metric - read it alongside the pass rate.
Limitations
References
Related skills
flake-axis-bisection
Locates the condition a known-flaky test actually depends on by holding the test constant and varying one axis at a time (isolation, execution order, worker count, viewport, network latency, repetition depth), recording a pass/fail count per variation, and testing whether the gap between two conditions exceeds sampling noise. Covers choosing the run count N from the failure rate you need to detect, binomial confidence intervals on a measured reproduction rate, a two-proportion comparison rule, what a zero-failure result does and does not prove, and the resource-collision walk (DB row, DB schema, file path, port, env var, module state, inode, cookie jar) used once parallelism is implicated. Use when a specific test is already known to fail intermittently, reading its source has not explained why, and a decision about what to change must rest on measurement rather than on a plausible-sounding guess.
flake-pattern-reference
Reference catalog of the eight flake patterns - async/timing, test ordering, shared parallel state, resource leaks, network, locator drift, environment variance, randomness - with detection heuristics, remediation per pattern, and the concrete code-level fixes: replacing fixed sleeps with framework auto-waits, isolating state in beforeEach fixtures, per-worker DB schemas via workerIndex, try/finally teardown, mocking network + clock at the boundary, stable role-based locators, TZ pinning, and RNG seeding. Use when triaging an unknown flake to identify the category before bisecting, or when a classified flake needs the specific code change to apply.
flaky-test-quarantine
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.