Embedded CI Failure Analysis Dashboard
Most teams wire up a test dashboard once, point it at pass/fail counts, and call it observability. Six months later, the same five tests appear in every incident postmortem, triage still takes 20 minutes per failure, and nobody can answer "is this suite getting more reliable?" without pulling raw logs. The signal was always there — it just wasn't surfaced where engineers make decisions: inside the CI run itself.
An embedded CI failure analysis dashboard is different from a standalone reporting portal. It renders actionable failure context — failure rate by test, runtime percentiles, flake scores, log correlation — directly within the pipeline interface or as a first-class artifact linked from every run. No context switch to Allure, no digging through 4,000-line console output. The analysis is where the engineer already is.
By the end of this article you'll know how to instrument your pipeline to capture structured failure data, store it in a queryable backend, and build panels that answer the questions your on-call rotation actually asks — without a BI team or a six-figure APM contract.
Learn Python, Behave, GitHub Copilot, APIs, and CI/CD by building a real framework you can finish in a weekend.
What an Embedded CI Failure Dashboard Actually Tracks
A standard JUnit XML report gives you pass, fail, skip, and duration per test case. That's the raw material, not the insight. An embedded failure analysis dashboard aggregates those reports across runs — correlating test identity, branch, commit SHA, runner environment, retry count, and wall-clock time into a queryable time series. The result is a failure fingerprint per test, not just a red dot per run. What test results actually tell you goes well beyond that binary outcome once you start tracking variance and retry behavior alongside outcome.
Where this fits architecturally: the dashboard sits downstream of your test executor (Pytest, Playwright, k6, JUnit) and upstream of your alerting layer (PagerDuty, Slack). JUnit XML or OTLP spans flow into a storage backend — ClickHouse, BigQuery, or PostgreSQL — and Grafana panels or a GitHub Actions summary render the aggregated view. Critically, the render happens inside the CI artifact or PR comment, not in a separate portal that requires a separate login and a separate mental context.
Building the Pipeline: Ingest, Store, and Render Failure Trends
Start with structured ingest. Parse JUnit XML at the end of every run and push rows to a ClickHouse table. The schema needs at minimum: test_id, suite, outcome, duration_ms, retry_count, branch, run_id, timestamp. A lightweight Python step in GitHub Actions handles this cleanly:
# .github/workflows/test-ingest.yml (partial)
- name: Parse and ship test results
run: |
python scripts/ingest_junit.py \
--xml-glob "reports/**/*.xml" \
--clickhouse-dsn "${{ secrets.CH_DSN }}" \
--branch "${{ github.ref_name }}" \
--run-id "${{ github.run_id }}"
The ingest script normalizes classname+name into a stable test_id hash so renames don't silently break trend lines. Once you have 14 days of data, the failure trend query becomes straightforward:
-- ClickHouse: 7-day failure rate per test, ordered by worst offenders
SELECT
test_id,
countIf(outcome = 'failed') / count() AS failure_rate,
quantile(0.95)(duration_ms) AS p95_ms,
sum(retry_count) AS total_retries
FROM test_results
WHERE timestamp >= now() - INTERVAL 7 DAY
AND branch = 'main'
GROUP BY test_id
HAVING failure_rate > 0.05
ORDER BY failure_rate DESC
LIMIT 30;
Wire this query into a Grafana panel (Table visualization, threshold coloring on failure_rate) and publish the panel URL as a GitHub Actions job summary. The summary step is two lines:
- name: Post dashboard link to job summary
run: |
echo "### Test Failure Analysis" >> $GITHUB_STEP_SUMMARY
echo "[Open failure dashboard](https://grafana.internal/d/ci-failures?var-run=${{ github.run_id }})" >> $GITHUB_STEP_SUMMARY
For teams already using Loki for log aggregation, correlate failure rows with log streams by injecting run_id as a Loki label. A Grafana + Loki triage setup lets you click a failing test in the table panel and jump directly to its stderr output — no manual log search. That correlation dropped triage time from 22 minutes per failure to under 4 in one platform team's production setup. For a full walkthrough of wiring panels to a CI backend, the step-by-step CI test dashboard guide covers Grafana datasource config and alerting rules in detail.
Pipeline Instrumentation Mistakes That Corrupt Your Failure Data
The most common mistake is ingesting only the final retry outcome. If a test fails twice and passes on the third attempt, recording only "passed" makes your reliability numbers look better than they are and completely hides flake behavior. Always record every attempt with its own row and a retry_index column. Flake rate is countIf(retry_count > 0 AND outcome = 'passed') / count() — you can't compute it from deduplicated data. Teams skip this because their JUnit parser discards retries by default; add --keep-retries flags or parse the raw Surefire XML before any test runner post-processing strips them.
A second failure mode is inconsistent test_id generation. Pytest parameterized tests produce names like test_checkout[usd-visa-3ds] — if the parameter set changes, your trend line breaks silently. Normalize to a hash of module::classname::function only, stripping parameters into a separate params column. The third mistake is treating all branches equally in the same dashboard without a branch filter. Failure rates on feature branches are expected to be higher; mixing them with main trends inflates your baseline and makes real regressions invisible.
Myths About CI Dashboards That Lead to Bad Triage Decisions
Myth 1: A green run means the suite is healthy. A suite that auto-retries three times before reporting green is masking failures, not resolving them. Pass/fail at the run level is a lagging, lossy signal. Failure rate over a rolling window — especially combined with retry count — is the leading indicator. Myth 2: More panels equal better observability. A dashboard with 40 panels measuring everything from test count to runner CPU is noise. The panels that drive action are: failure rate trend (7-day), top-10 failing tests by frequency, P95 runtime by suite, and flake score. Everything else is decoration until someone proves it changes a decision.
Myth 3: Flakiness is a test-layer problem. Roughly half of what looks like test flakiness originates in infrastructure — network timeouts, shared fixture contention, underpowered runners. Failure sequencing analysis distinguishes infra flakes from logic flakes by correlating failure timestamps with runner metadata, not by re-running the test and hoping. Treating all flakes as "bad test code" sends engineers down the wrong root cause path and wastes sprint capacity. Similarly, understanding how failure meaning shifts by pipeline stage prevents teams from treating a pre-merge E2E failure with the same urgency as a post-deploy smoke failure — context is everything.
An embedded CI failure analysis dashboard earns its place when it changes behavior: engineers stop guessing which test broke the build, on-call rotations stop re-investigating the same flaky test every week, and engineering leaders can answer "is the suite getting more reliable?" with a chart instead of a shrug. Start with the ClickHouse ingest schema above, get 14 days of data on main, and build the four panels that matter before adding anything else. Complexity scales better when the foundation is honest.
Note: This article is for informational purposes only and is not a substitute for professional advice. If you need guidance on specific situations described in this article, consider consulting a qualified professional.