Bring your existing suite
If your suite already writes JUnit XML, that file is a complete path onto the dashboard. No case files, no CLI.
One step
Every mainstream runner can emit JUnit XML — pytest --junitxml, jest-junit, Playwright's junit reporter, and so on. Post that file at the ingest endpoint with a project API token and every <testcase> in it lands as run history.
curl -X POST "https://api.releasetwin.com/api/ingest/junit" \
-H "Authorization: Bearer $RELEASETWIN_API_TOKEN" \
-H "Content-Type: application/xml" \
--data-binary @junit-results.xmlOn GitHub Actions the same upload needs no stored token: bind the repository on the project's Settings page, give the job permissions: id-token: write, and run releasetwin upload-junit junit-results.xml with RELEASETWIN_PROJECT_ID set — the CLI exchanges the job's own GitHub identity for the credential. See CI & GitHub Actions.
A 201 comes back with recorded, the number of test cases stored, and runUrl, where to see them. Add ?release=<label> to group the upload into a release, or put a <property name="release" value="..."/> in the document — the query parameter wins if you supply both.
What gets read
- The case identifier is the test's
classnameandname, joined with a dot. Whichever one your runner omits is left out. - A test with no
<failure>,<error>, or<skipped>child is recorded as passing. The other three are recorded as not passing, and are told apart from each other in the classification column. - The failure detail is the failure's message, its body text, and the test's
system-out, truncated to 4,000 characters. - The duration comes from
time. A missing or unreadable value records as zero rather than failing the upload. - A merged report spanning several
<testsuite>elements is fine — all of them are read.
Limits
An upload is capped at 256 KB and 1,000 test cases. Exceeding either rejects the whole upload with nothing stored, and the response says which limit you hit — split a larger report and upload it in parts. Every rejection happens before anything is written, so a refused upload never leaves half a run behind.
Redaction is yours
The failure detail is stored exactly as it appears in your document. ReleaseTwin does not inspect or strip it — the same posture the platform takes for evidence documents. If your assertion messages or stack traces can carry a token, an account identifier, or customer data, scrub them in your own pipeline before you upload.
What imported runs do and don't get
Imported runs are marked JUnit in run history, and they feed everything that reads run history: trends, release roll-up, regression diffing, run notifications, usage, and share links.
They are not flag proof. A flag proof is a paired known-bad/known-good run that demonstrates a case would actually have caught the regression — a JUnit result carries no such pairing, and no fixture hash to check integrity against. So the merge gate, blast radius, and fixture-integrity views do not act on imported runs. To prove a flag, author a case file; the two live side by side in the same project.
Next
- Hosted platform — issue the token this endpoint needs.
- CI & GitHub Actions — where the upload step goes.
- Case files — the upgrade path to flag proof.