Documentation

TestNod documentation

Learn how to set up TestNod and configure it to help you spot flaky tests, catch regressions, and see how performance changes over time.

Sending JUnit XML test results from your CI

Sending JUnit XML test results to TestNod is a sequence of HTTP requests: one or more uploads using a shared build_id, followed by a single /finalize call once the build is done. This page is a high-level overview of that flow. For the exact request and response contract, see Submission API. For a working CI snippet, see CI integrations.

Supported test frameworks and formats

TestNod ingests JUnit XML files, which covers test frameworks like RSpec, pytest, JUnit, Jest (with jest-junit), Go test (with gotestsum), Cypress, Playwright, and most JVM runners. JUnit XML files are often generated with a single flag or a small reporter package. See JUnit XML format notes for which elements TestNod reads and which it ignores.

The upload and finalize endpoints

A complete build sends at least three requests. The first registers the upload and carries the metadata:

POST https://app.testnod.com/integrations/test_runs/upload
Project-Token: <your project token>
Content-Type: multipart/form-data

test_run[metadata][build_id]=<CI build id, required>
test_run[metadata][branch]=main
test_run[metadata][commit_sha]=<git sha>
test_run[metadata][run_url]=<CI run URL>
tags[][value]=smoke

That request does not carry the JUnit file. It returns a presigned_url, and you PUT the file straight to it:

PUT <presigned_url from the upload response>
Content-Type: application/xml

<your JUnit XML file>

Once every file for the build is uploaded, finalize the run:

POST https://app.testnod.com/integrations/test_runs/finalize
Project-Token: <your project token>

build_id=<CI build id>

The upload step can run more than once per build, with every call that shares a build_id attached to the same run. The finalize step finishes processing the test run. A successful upload returns 201 Created with test_run_id, upload_id, test_run_url, and the presigned_url to send the file to; finalize returns 200 OK with the run's current status. The full contract is in Submission API.

Finding the project token

Each project has its own token, generated when the project is created. Open the project in TestNod, click Settings, and copy the token. The token is the only credential the upload and finalize endpoints need. It scopes uploads to that project, so a leaked token only affects one project's data, but you should still treat it as sensitive.

What success looks like

Upload returns immediately with test_run_id and upload_id. The JUnit file is parsed asynchronously, and the parent run stays in pending until /finalize has been called and every upload is terminal. Once both are true, the run transitions to processing and then to processed.

What failure looks like

Failures fall into four buckets:

  • 404 Not Found: the Project-Token header did not match any project (usually due to a typo or missing CI secret), or a /finalize call named a build_id with no run behind it (usually means the finalize call and the upload did not have matching build_id parameters).
  • 422 Unprocessable Content (upload): either metadata.build_id was missing or the request format was rejected. The response body has an error_message field.
  • 422 Unprocessable Content (finalize): a run exists for that build_id but no uploads were ever attached to it.
  • Stuck in pending: the upload succeeded but no /finalize call ever landed for that build_id. The run will not move until you call finalize (it is idempotent, so call it again from the same build_id whenever you notice the stall).

The Troubleshooting uploads page walks through each one in order.

Be first to try TestNod

We're opening early access soon. Drop your email and we'll get you in, and we're happy to help you set up too.

No spam. We'll only email you about TestNod.