Explains CI test numbers that disagree with what the suite actually did - a 'slowest tests' list dominated by the wrong suite, a release gate or dashboard reading only the summary attributes on the suite element and never the cases below them, or a pass rate that quietly counts skipped tests as passes. Parses JUnit-format XML (the interchange format Jenkins, GitHub Actions, GitLab, Buildkite, and CircleCI all ingest) into per-suite and per-case metrics tables - passed / failed / errored / skipped, time, classname, message, stack - groups failures by classname for trend analysis, and separates new failures from flakes by cross-referencing the `flakyFailure` and rerun elements. Use when a report, gate, or metric derived from test results cannot be trusted.
97
85%
Does it follow best practices?
Impact
99%
1.02xAverage score across 10 eval scenarios
Passed
No findings from the security scan
Companion detail for junit-xml-analysis. The Python parse_junit.py in SKILL.md is the runnable core; this file holds the Node.js equivalent and the CI workflow.
import { XMLParser } from 'fast-xml-parser';
import { readFileSync } from 'node:fs';
const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
const xml = parser.parse(readFileSync(path, 'utf8'));
const suites = xml.testsuites
? (Array.isArray(xml.testsuites.testsuite) ? xml.testsuites.testsuite : [xml.testsuites.testsuite])
: [xml.testsuite];
for (const suite of suites) {
const cases = Array.isArray(suite.testcase) ? suite.testcase : [suite.testcase];
// ...
}Handle both root shapes (<testsuites> or a bare <testsuite>) and single-element collapsing (one testcase = bare object, multiple = array), which is common in JS XML libraries.
# .github/workflows/test-analytics.yml
- name: Run tests (any framework, JUnit XML reporter enabled)
run: npm test -- --reporters=default,jest-junit
env:
JEST_JUNIT_OUTPUT_FILE: junit.xml
- name: Analyze JUnit XML
if: always()
run: python scripts/parse_junit.py junit.xml > analytics.json
- name: Upload analytics
if: always()
uses: actions/upload-artifact@v4
with:
name: junit-analytics
path: |
junit.xml
analytics.jsonif: always() is critical - JUnit XML matters most on failed runs.