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
-
createTestPlanselects the CI-tagged cases, assigns their executions, and writes the new plan ID totmp/tc_test_plan. - The Bash task loads that file with the POSIX
.command. - Jest writes
test-results/results.xmland its exit status is saved. tc reportuploads the results to the matching TestCollab cases.- The script fails when either reporting or tests fail, so the pipeline accurately reflects the run.
-
PublishTestResults@2usescondition: 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.


