Documentation

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.

CircleCI Contexts page showing the new testnod context with a TESTNOD_TOKEN environment variable

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.

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.