JUnit XML
JUnit XML is the lingua franca of CI test dashboards. Tales produces a Surefire-compatible XML file with one <testsuite> per .tales file and one <testcase> per scenario.
tales test ./e2e/pass --report-junit ./reports/junit.xmlThe parent directory is created if missing. The output is fully self-contained, no external schema dependencies.
Schema
Section titled “Schema”<?xml version="1.0" encoding="UTF-8"?><testsuites> <testsuite name="e2e/pass/blog.tales" tests="3" failures="0" skipped="0" time="1.232" timestamp="2026-05-27T18:30:00Z"> <testcase name="Create blog post" classname="e2e/pass/blog.tales" time="0.842"> <!-- empty when passed --> </testcase> <testcase name="Failing scenario" classname="e2e/pass/blog.tales" time="0.123"> <failure message="status mismatch: want 201, got 500" type="assertion"> expected status 201 at scenario.create_user.expect.status actual: 500 Internal Server Error </failure> </testcase> <testcase name="Skipped scenario" classname="e2e/pass/blog.tales" time="0"> <skipped message="iOS tests require macOS and IOS_APP_PATH"/> </testcase> </testsuite></testsuites>Mapping to Tales concepts
Section titled “Mapping to Tales concepts”| Tales concept | XML element |
|---|---|
.tales file | <testsuite name="<file path>"> |
| Scenario | <testcase name="<scenario name>" classname="<file path>"> |
| Failed scenario | <failure message="..." type="assertion">...</failure> |
| Skipped scenario | <skipped message="<skip reason>"/> |
| Step duration | aggregated into time="<seconds>" on the testcase |
| Teardown step | rolled into the scenario’s duration (no separate testcase) |
Skip support
Section titled “Skip support”The <testsuite> element gains a skipped="N" attribute, and each skipped scenario emits a <skipped message="…"/> child on its <testcase>. This matches Surefire / JUnit5 conventions and shows up correctly in:
- Jenkins JUnit plugin
- GitLab CI test reports
- CircleCI test summary
- Bitbucket Pipelines test reports
- Azure DevOps test runs
Action-level data is not flattened
Section titled “Action-level data is not flattened”Mobile action records (tap, swipe, etc.) are intentionally not decomposed into <testcase> elements. They live in the JSONL stream and the visual HTML report instead, JUnit consumers don’t typically know what to do with sub-step data.
CI integration
Section titled “CI integration”The most common pattern is to combine console (for live log scrolling) with JUnit (for the dashboard):
tales test ./e2e/pass \ --seed 1234 \ --report-junit ./reports/junit.xmlThen point your CI configuration at ./reports/junit.xml. Examples in CI integration.