CircleCI integration for JUnit XML uploads
The TestNod uploader orb sends your JUnit XML reports to TestNod from a CircleCI workflow. It keeps the change to your config down to a single step, and it handles the upload and finalize flow for you, so there are no API calls to script yourself.
If you haven't created a project yet, the Quickstart walks through that and adding your first upload step. This page picks up from there, covering the orb's parameters along with the patterns for parallel containers, split test suites, and forked pull requests.
Add the project token as a CircleCI context
In CircleCI, open Organization Settings → Contexts, create a context (testnod is a common name), and add an environment variable named TESTNOD_TOKEN with the project token from your project's settings page in TestNod as the value. A context keeps the token in one place and lets you share it across every project that uploads to TestNod, and jobs read it only when you attach the context to them, so it never appears in your config or in job logs.

TESTNOD_TOKEN is the variable name the orb reads by default, so nothing else has to be wired up. If you store the token under a different name, or as a project environment variable instead of a context, pass that name through the token parameter. For more on contexts, see "Using contexts" in CircleCI's documentation.
Add the upload step
Add the orb's upload step after your test step. It runs with when: always built in, so results upload on both passing and failing runs, and the failing ones are usually what you most want to inspect.
The following config runs RSpec and uploads the JUnit XML report to TestNod:
version: 2.1
orbs:
testnod-uploader: testnod/[email protected]
jobs:
test:
docker:
- image: cimg/ruby:3.3
steps:
- checkout
- run:
name: Run tests
command: bundle exec rspec --format progress --format RspecJunitFormatter --out tmp/rspec.xml
- testnod-uploader/upload:
file: tmp/rspec.xml
tags: ci,rspec
ignore_failures: true
workflows:
test-and-upload:
jobs:
- test:
context: testnod
That single step covers the whole upload. By default the orb uploads the file and finalizes the run in one pass, so a workflow that produces one JUnit XML file needs nothing else. The context: testnod line on the workflow is what puts the token in the job's environment, and without it the token is unset and the upload fails.
Orb parameters
| Parameter | Required | Default | What it does |
|---|---|---|---|
token |
No | TESTNOD_TOKEN |
Name of the environment variable that holds your project token, not the token value. A context variable named TESTNOD_TOKEN needs no override. |
file |
Conditional | "" |
Path to the JUnit XML report. Required unless finalize is only. A path that points to a missing file skips the upload instead of failing the job. |
tags |
No | "" |
Comma-separated tags applied to the run. Tags group runs in the dashboard and scope TestNod's analysis to comparable runs. |
finalize |
No | "true" |
"true" uploads the file and finalizes the run, "false" uploads without closing the run, and only finalizes without uploading. |
ignore_failures |
No | false |
When true, an upload or finalize error logs a warning instead of failing the step. |
build_id |
No | $CIRCLE_WORKFLOW_ID |
The identifier that groups uploads into one run. Override it to split runs apart or merge them. |
uploader_version |
No | latest |
Pins the underlying uploader to a specific version instead of tracking the latest release. Pinned versions are cached across runs. |
Metadata attached automatically
The orb reads the branch name, commit SHA, and build id from CircleCI's built-in environment variables, and it builds a link back to the CircleCI job. All of it travels with the upload, so the TestNod run page points straight back at the job that produced it. None of it needs to be passed by hand. On tag-triggered pipelines, where CircleCI leaves the branch empty, the orb records the tag instead.
Parallel and split builds
CircleCI's parallelism splits one job across several containers, and every container in a workflow shares the same $CIRCLE_WORKFLOW_ID, which the orb uses as the default build_id. Every container that uploads with that build id joins the same TestNod run, which ends up made of one upload per container.
Because the run stays open while containers are still reporting, each container uploads with finalize: "false", and a separate job finalizes once with finalize: "only":
version: 2.1
orbs:
testnod-uploader: testnod/[email protected]
jobs:
test:
docker:
- image: cimg/ruby:3.3
parallelism: 4
steps:
- checkout
- run:
name: Run tests
command: |
TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
bundle exec rspec --format RspecJunitFormatter --out tmp/rspec.xml $TESTFILES
- testnod-uploader/upload:
file: tmp/rspec.xml
tags: ci,rspec
finalize: "false"
workflows:
test-and-upload:
jobs:
- test:
context: testnod
- testnod-uploader/upload:
name: finalize-testnod
context: testnod
finalize: only
requires:
- test
The finalize-testnod job runs the orb's standalone job with finalize: only, and requires: [test] holds it until every container in the test job has finished. It needs the same context so it can read the token to close the run. The same pattern covers fan-out across distinct jobs, say one running RSpec and another running Jest: each uploads its own report with finalize: "false", and a single finalize-only job requires all of them.
If you would rather track a job as its own TestNod run, with separate history and alerts, give it a distinct build_id and let it finalize on its own with the default finalize: "true". A value like build_id: "$CIRCLE_WORKFLOW_ID-rspec" is enough to keep the runs apart, and the separate finalize job is no longer needed.
Forked pull requests
CircleCI does not share contexts or project environment variables with builds triggered by forked pull requests unless you turn on secret passing for forks, which exposes your token to code from outside your organization. Without the token, the upload step has no credentials and fails. Keep the upload out of any workflow that builds forked pull requests, or run those tests in a separate workflow that skips the upload step.
Demo
A complete working example lives at testnod/testnod-circleci-orb-demo. Its .circleci/config.yml runs the test suite from a Ruby on Rails application and uploads the JUnit XML report with the orb, so you can read a full config end to end or fork the repo, drop in your own project token, and watch a run land in TestNod.
What's next
The Test run page tour walks through the insights TestNod surfaces once your runs start arriving. If a run doesn't show up or its status looks wrong, Troubleshooting uploads covers the most common causes.