JUnit XML
JUnit XML is the de-facto interchange format for test results: an XML
document of <testsuite> elements containing <testcase> elements, each
optionally carrying a <failure>, <error> or <skipped> child.
Nearly every test runner emits it; nearly every CI system ingests it.
Despite the name, it is not Java-specific. pytest (--junitxml), Jest,
Vitest, RSpec, go test via gotestsum, PHPUnit, Cypress, Playwright and
Robot Framework all produce it.
The shape
<testsuites tests="3" failures="1" time="4.12">
<testsuite name="checkout" tests="3" failures="1" time="4.12">
<testcase classname="checkout.cart" name="applies discount" time="0.31"/>
<testcase classname="checkout.cart" name="rejects expired code" time="0.44">
<failure message="expected 400, got 200" type="AssertionError">
traceback…
</failure>
</testcase>
<testcase classname="checkout.cart" name="handles currency" time="3.37">
<skipped/>
</testcase>
</testsuite>
</testsuites>
A <testcase> with no children passed. That is the whole model.
There is no specification
The format was an output of Ant's JUnit task and was never standardised. The consequences are practical:
- Consumers implement tolerant parsers that disagree. The same file can render fully in GitLab and lose the skip reasons in another system.
- Runners add non-portable attributes (
file,line,retries,flaky) that only their own tooling reads. - The
<testsuites>root is optional, and some emitters omit it — which some parsers reject. - There is no agreed place for attachments, screenshots or traces.
Treat it as a lowest common denominator that happens to be universal, not as a rich format.
What it cannot carry
The XML records that a test failed and the assertion message. It does not usefully carry:
- Screenshots and video of the failing run.
- Playwright traces, network logs, DOM snapshots.
- Retry history, which is what identifies a flaky test rather than a real one.
- Grouped console output per test.
- Any of the navigation that makes a 4,000-test run readable.
All of that lives in the runner's HTML report, which is why most pipelines produce both: the XML for the gate and the annotations, the HTML for the person diagnosing the failure.
The usual gap
The XML gets ingested and shows up on the pull request. The HTML gets uploaded as a build artifact, where it is served as a zip, expires under retention, and is invisible to anyone without repository access.
Publishing the HTML to a stable URL closes it: the PR annotation says which test failed, the link next to it shows the trace and the screenshot, and a reviewer can select the failing assertion and comment on it directly (comment on HTML).
Try it
Comma is free — unlimited reports, unlimited commenters, unlimited revision history.