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

GitHub Actions: Upload JUnit Test Results to TestCollab

Generate a JUnit XML report in GitHub Actions and upload automated test results to TestCollab with @testcollab/cli.

On this page

This guide shows how to create a TestCollab plan in GitHub Actions, run automated tests, and upload their JUnit XML or Mochawesome JSON results with the current TestCollab CLI.

The core reporting workflow is createTestPlan → tests → report. It does not require a build record or quality gate. See Automated Testing in a CI/CD Pipeline with TestCollab for the core path and optional extensions.

Prerequisites

  1. A TestCollab API token from your profile settings

  2. Your numeric TestCollab project ID

  3. A tag applied to the cases that should be included in the CI plan, plus that tag's ID

  4. The API token owner's user ID, used as the plan assignee

  5. Test names whose generated result contains an explicit TestCollab ID such as [TC-42] Login should succeed

  6. Node.js 22 and your test framework's dependencies, installed by the workflow examples below

Step 1: Add Repository Secrets

Open your GitHub repository, then go to Settings > Secrets and variables > Actions. Add these repository secrets:

Secret name

Value

TESTCOLLAB_TOKEN

Your TestCollab API token

TC_PROJECT_ID

Your TestCollab project ID

TC_CI_TAG_ID

The tag ID used to select cases for CI

TC_ASSIGNEE_ID

The API token owner's user ID

Step 2: Add a Workflow

Create .github/workflows/tests.yml and choose the example for your framework. Each example is a complete workflow, not a fragment.

Playwright with JUnit XML

Playwright's JUnit reporter writes to standard output unless you give it an output file. This workflow sets PLAYWRIGHT_JUNIT_OUTPUT_NAME, so tc report receives a real results.xml file.

name: Playwright tests and TestCollab report

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      TESTCOLLAB_TOKEN: ${{ secrets.TESTCOLLAB_TOKEN }}
      TC_PROJECT_ID: ${{ secrets.TC_PROJECT_ID }}
      TC_CI_TAG_ID: ${{ secrets.TC_CI_TAG_ID }}
      TC_ASSIGNEE_ID: ${{ secrets.TC_ASSIGNEE_ID }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Install project dependencies
        run: npm ci

      - name: Install TestCollab CLI
        run: npm install -g @testcollab/cli

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Create TestCollab test plan
        run: |
          tc createTestPlan \
            --project "$TC_PROJECT_ID" \
            --ci-tag-id "$TC_CI_TAG_ID" \
            --assignee-id "$TC_ASSIGNEE_ID"

      - name: Load TestCollab plan ID
        run: cat tmp/tc_test_plan >> $GITHUB_ENV

      - name: Run Playwright and write JUnit XML
        env:
          PLAYWRIGHT_JUNIT_OUTPUT_NAME: results.xml
        run: npx playwright test --reporter=junit

      - name: Upload results to TestCollab
        if: always()
        run: |
          tc report \
            --project "$TC_PROJECT_ID" \
            --test-plan-id "$TESTCOLLAB_TEST_PLAN_ID" \
            --format junit \
            --result-file ./results.xml

Cypress with Mochawesome JSON

Cypress can write one Mochawesome JSON file per spec. This workflow installs mochawesome-merge, merges every generated JSON file, and uploads the combined file that the current CLI expects.

name: Cypress tests and TestCollab report

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      TESTCOLLAB_TOKEN: ${{ secrets.TESTCOLLAB_TOKEN }}
      TC_PROJECT_ID: ${{ secrets.TC_PROJECT_ID }}
      TC_CI_TAG_ID: ${{ secrets.TC_CI_TAG_ID }}
      TC_ASSIGNEE_ID: ${{ secrets.TC_ASSIGNEE_ID }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Install project dependencies
        run: npm ci

      - name: Install reporting tools and TestCollab CLI
        run: |
          npm install --no-save mochawesome mochawesome-merge
          npm install -g @testcollab/cli

      - name: Create TestCollab test plan
        run: |
          tc createTestPlan \
            --project "$TC_PROJECT_ID" \
            --ci-tag-id "$TC_CI_TAG_ID" \
            --assignee-id "$TC_ASSIGNEE_ID"

      - name: Load TestCollab plan ID
        run: cat tmp/tc_test_plan >> $GITHUB_ENV

      - name: Run Cypress and write Mochawesome JSON
        run: |
          npx cypress run \
            --reporter mochawesome \
            --reporter-options reportDir=results,overwrite=false,html=false,json=true

      - name: Merge Mochawesome files
        if: always()
        run: npx mochawesome-merge "./results/*.json" --output ./results/mochawesome.json

      - name: Upload results to TestCollab
        if: always()
        run: |
          tc report \
            --project "$TC_PROJECT_ID" \
            --test-plan-id "$TESTCOLLAB_TEST_PLAN_ID" \
            --format mochawesome \
            --result-file ./results/mochawesome.json

pytest with JUnit XML

pytest has built-in JUnit XML output, so it does not need an additional reporter package.

name: pytest and TestCollab report

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    env:
      TESTCOLLAB_TOKEN: ${{ secrets.TESTCOLLAB_TOKEN }}
      TC_PROJECT_ID: ${{ secrets.TC_PROJECT_ID }}
      TC_CI_TAG_ID: ${{ secrets.TC_CI_TAG_ID }}
      TC_ASSIGNEE_ID: ${{ secrets.TC_ASSIGNEE_ID }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - uses: actions/setup-node@v4
        with:
          node-version: '22'

      - name: Install Python dependencies
        run: pip install -r requirements.txt

      - name: Install TestCollab CLI
        run: npm install -g @testcollab/cli

      - name: Create TestCollab test plan
        run: |
          tc createTestPlan \
            --project "$TC_PROJECT_ID" \
            --ci-tag-id "$TC_CI_TAG_ID" \
            --assignee-id "$TC_ASSIGNEE_ID"

      - name: Load TestCollab plan ID
        run: cat tmp/tc_test_plan >> $GITHUB_ENV

      - name: Run pytest and write JUnit XML
        run: pytest --junitxml=results.xml

      - name: Upload results to TestCollab
        if: always()
        run: |
          tc report \
            --project "$TC_PROJECT_ID" \
            --test-plan-id "$TESTCOLLAB_TEST_PLAN_ID" \
            --format junit \
            --result-file ./results.xml

How the Workflows Work

  1. Create the plan - tc createTestPlan adds the cases carrying your CI tag, assigns them, starts the first run, and writes TESTCOLLAB_TEST_PLAN_ID=<id> to tmp/tc_test_plan.

  2. Load the plan ID - appending that file to GITHUB_ENV makes TESTCOLLAB_TEST_PLAN_ID available to later steps in the same job.

  3. Run the tests - the framework creates JUnit XML or Mochawesome JSON.

  4. Upload the results - tc report parses the file and updates matching executions in the plan.

if: always() lets the upload step run after a test failure. GitHub Actions still keeps the earlier test step's failed result, so uploading evidence does not turn a failing workflow green.

Test Case ID Mapping

The generated result must contain the TestCollab case ID in its test name or classname. The recommended form is:

[TC-42] Login should succeed

The CLI also recognizes TC-42, id-42, and testcase-42, without case sensitivity. Explicit markers take priority, so technical text such as UTF-8 or SHA-256 does not override [TC-42].

See JUnit XML Format Explained for result elements, attachments, and configuration IDs.

EU Region

For an EU-hosted TestCollab account, add this option to both tc createTestPlan and tc report in the selected workflow:

--api-url https://api-eu.testcollab.io

Optional Enhancements: Build Traceability and Quality Gates

After the core reporting workflow works, you can optionally connect results to a version by recording it with tc createBuild and passing its ID or version to tc createTestPlan --build.

Independently, you can stop a deployment when the reported plan does not meet your rules by adding tc gate after tc report.

Neither enhancement is required to create a plan or upload test results, and you can adopt either one without the other.

Troubleshooting

The result file is missing

  • For Playwright, keep PLAYWRIGHT_JUNIT_OUTPUT_NAME=results.xml or configure the JUnit reporter with an explicit output file.

  • For Cypress, make sure the merge step runs and produces ./results/mochawesome.json.

  • For pytest, confirm the command includes --junitxml=results.xml.

Test cases are not matched

  • Confirm every generated test name contains an explicit marker such as [TC-42].

  • Confirm the case carries the CI tag and was added to the new plan.

  • Use the API token owner's user ID as TC_ASSIGNEE_ID.

Authentication or permission errors

  • Confirm TESTCOLLAB_TOKEN exists in repository secrets and is available to this workflow.

  • Confirm the token owner and assignee can access the project.

  • For pull requests from forks, remember that GitHub does not pass repository secrets to an untrusted fork workflow.

Last updated 2026-08-26.