Help Center/Automation & CI/CD/Your CI provider

Azure Pipelines: Publish Test Results to TestCollab

Publish JUnit test results from Azure Pipelines to TestCollab with @testcollab/cli.

Publish automated test results from Azure Pipelines to TestCollab with @testcollab/cli. One pipeline can create the TestCollab plan, run tests, upload JUnit XML to TestCollab, and publish the same XML to Azure DevOps.

The core workflow is createTestPlan → tests → report. Recording a TestCollab build or evaluating a quality gate is optional.

For CLI installation and workflow choices, see TestCollab CLI: Install and First Run.

Prerequisites

  • A TestCollab project containing the automated test cases.
  • A CI tag applied to those cases, plus the project ID, tag ID, and assignee user ID.
  • A TestCollab API token belonging to the assignee. The user needs permission to create and assign test plans.
  • An Azure DevOps repository and YAML pipeline.
  • A runner that writes JUnit XML. The complete example uses Jest with jest-junit.

Install the Jest reporter as a development dependency if needed:

npm install --save-dev jest-junit

For reporter configuration options, see Jest: Generate JUnit XML with jest-junit and Upload to TestCollab.

Add Azure Pipeline variables

Open the pipeline, choose Edit > Variables, and add:

Variable Value Secret?
TESTCOLLAB_TOKEN Your TestCollab API token Yes
TC_PROJECT_ID TestCollab project ID No
TC_CI_TAG_ID Tag ID selecting the CI cases No
TC_ASSIGNEE_ID User ID that owns the executions No

Mark TESTCOLLAB_TOKEN as secret. The YAML maps it into the Bash task's environment instead of printing it.

Complete azure-pipelines.yml

The Bash task below saves the Jest exit status, reports the JUnit result, and then restores the appropriate failure. The final Azure task publishes the XML even when the Bash task fails.

trigger:
  branches:
    include:
      - main

pr:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

variables:
  TC_API_URL: https://api.testcollab.io

steps:
  - task: NodeTool@0
    displayName: Use Node.js 22
    inputs:
      versionSpec: 22.x

  - script: npm ci
    displayName: Install dependencies

  - bash: |
      set -e

      npx @testcollab/cli createTestPlan \
        --project "$TC_PROJECT_ID" \
        --ci-tag-id "$TC_CI_TAG_ID" \
        --assignee-id "$TC_ASSIGNEE_ID" \
        --api-url "$TC_API_URL"

      . tmp/tc_test_plan
      mkdir -p test-results

      set +e
      JEST_JUNIT_OUTPUT_DIR=./test-results \
      JEST_JUNIT_OUTPUT_NAME=results.xml \
        npm test -- --ci --reporters=default --reporters=jest-junit
      TEST_EXIT=$?

      npx @testcollab/cli report \
        --project "$TC_PROJECT_ID" \
        --test-plan-id "$TESTCOLLAB_TEST_PLAN_ID" \
        --format junit \
        --result-file ./test-results/results.xml \
        --api-url "$TC_API_URL"
      REPORT_EXIT=$?
      set -e

      if [ "$REPORT_EXIT" -ne 0 ]; then
        exit "$REPORT_EXIT"
      fi
      exit "$TEST_EXIT"
    displayName: Run tests and upload to TestCollab
    env:
      TESTCOLLAB_TOKEN: $(TESTCOLLAB_TOKEN)
      TC_PROJECT_ID: $(TC_PROJECT_ID)
      TC_CI_TAG_ID: $(TC_CI_TAG_ID)
      TC_ASSIGNEE_ID: $(TC_ASSIGNEE_ID)
      TC_API_URL: $(TC_API_URL)

  - task: PublishTestResults@2
    displayName: Publish JUnit results to Azure DevOps
    condition: always()
    inputs:
      testResultsFormat: JUnit
      testResultsFiles: test-results/results.xml
      failTaskOnFailedTests: false
      failTaskOnMissingResultsFile: false
      publishRunAttachments: true

How the Azure workflow works

  1. createTestPlan selects the CI-tagged cases, assigns their executions, and writes the new plan ID to tmp/tc_test_plan.
  2. The Bash task loads that file with the POSIX . command.
  3. Jest writes test-results/results.xml and its exit status is saved.
  4. tc report uploads the results to the matching TestCollab cases.
  5. The script fails when either reporting or tests fail, so the pipeline accurately reflects the run.
  6. PublishTestResults@2 uses condition: always() to add the same JUnit file to the Azure DevOps test view.

Map results with [TC-42]

Include the TestCollab case ID in each automated test title. The CLI recognizes [TC-42], TC-42, id-42, and testcase-42, case-insensitively.

test('[TC-42] customer can sign in', async () => {
  // Test steps and assertions
});

The number must identify a case included in the TestCollab plan, not an Azure work item or test plan.

Use Playwright, pytest, or Maven

Replace the Jest command while keeping the TestCollab commands and failure-capture pattern:

# Playwright
PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-results/results.xml \
  npx playwright test --reporter=junit

# pytest
pytest --junitxml=test-results/results.xml

# Maven Surefire
mvn test

Update --result-file and testResultsFiles to the actual XML path. Because tc report accepts one file per command, merge multiple Surefire files first or select a combined report.

Optional build traceability and quality gates

After the core pipeline reports results successfully, you can optionally record a deployed build and link the plan to it. You can independently add a quality gate after tc report. Neither extension is required for the pipeline above.

Use the EU region

For an EU account, change the pipeline variable in the YAML to:

variables:
  TC_API_URL: https://api-eu.testcollab.io

Both TestCollab commands already pass --api-url "$TC_API_URL".

Troubleshooting

No API key provided

Verify the secret variable is named TESTCOLLAB_TOKEN and is mapped under the Bash task's env block. Secret Azure variables are not automatically available as shell environment variables.

Azure DevOps reports that no result files were found

Confirm the runner creates test-results/results.xml. Keep the same path in the test command, tc report, and PublishTestResults@2.

The TestCollab plan stays unexecuted

Inspect the JUnit <testcase name> values for explicit [TC-42] markers. Confirm each case is in the plan and assigned to the token owner.

The pipeline succeeds when tests fail

Do not use continueOnError or leave the shell in set +e mode. Preserve TEST_EXIT and exit with it after the upload succeeds.

Last updated 2026-08-26.