Azure DevOps

Upload test reports to Gaffer from Azure DevOps Pipelines.

Azure DevOps Pipelines is Microsoft’s CI/CD solution for cloud and on-premises deployments. With Gaffer, you can automatically upload and share test reports from your Azure Pipelines.

Prerequisites

  • A Gaffer account with a project
  • Your project’s API key
  • An Azure DevOps project with a pipeline configured

Setup

1. Add your API key as a pipeline variable

  1. Go to your Azure DevOps project
  2. Navigate to Pipelines → select your pipeline → Edit
  3. Click VariablesNew variable
  4. Name: GAFFER_API_KEY
  5. Value: Your Gaffer project API key
  6. Check Keep this value secret
  7. Click OK and Save

2. Add the upload step to your pipeline

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '20.x'

  - script: npm ci
    displayName: 'Install dependencies'

  - script: npm test
    displayName: 'Run tests'

  - script: |
      curl -X POST https://app.gaffer.sh/api/upload \
        -H "X-API-Key: $(GAFFER_API_KEY)" \
        -F "files=@test-results/junit.xml" \
        -F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"$(Build.SourceBranchName)"}'
    displayName: 'Upload to Gaffer'
    condition: always()

Environment Variables

Azure DevOps provides these predefined variables:

VariableDescriptionExample
$(Build.SourceVersion)Full commit SHAabc123def456...
$(Build.SourceBranchName)Branch name (short)main, feature/login
$(Build.SourceBranch)Full branch refrefs/heads/main
$(System.PullRequest.SourceBranch)PR source branchrefs/heads/feature/login
$(Build.BuildNumber)Build number20231215.1
$(Build.Repository.Name)Repository namemy-app

Tip: Use $(Build.SourceBranchName) for clean branch names without the refs/heads/ prefix.

Examples

Playwright

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '20.x'

  - script: npm ci
    displayName: 'Install dependencies'

  - script: npx playwright install --with-deps
    displayName: 'Install Playwright browsers'

  - script: npx playwright test
    displayName: 'Run Playwright tests'

  - script: |
      curl -X POST https://app.gaffer.sh/api/upload \
        -H "X-API-Key: $(GAFFER_API_KEY)" \
        -F "files=@playwright-report/index.html" \
        -F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"$(Build.SourceBranchName)","test_framework":"playwright","test_suite":"e2e"}'
    displayName: 'Upload to Gaffer'
    condition: always()

  - publish: playwright-report
    artifact: playwright-report
    condition: always()

Jest with JUnit Reporter

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: '20.x'

  - script: npm ci
    displayName: 'Install dependencies'

  - script: npm test -- --reporters=default --reporters=jest-junit
    displayName: 'Run Jest tests'
    env:
      JEST_JUNIT_OUTPUT_DIR: ./test-results

  - script: |
      curl -X POST https://app.gaffer.sh/api/upload \
        -H "X-API-Key: $(GAFFER_API_KEY)" \
        -F "files=@test-results/junit.xml" \
        -F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"$(Build.SourceBranchName)","test_framework":"jest"}'
    displayName: 'Upload to Gaffer'
    condition: always()

  - task: PublishTestResults@2
    inputs:
      testResultsFormat: 'JUnit'
      testResultsFiles: '**/junit.xml'
    condition: always()

pytest

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: UsePythonVersion@0
    inputs:
      versionSpec: '3.11'

  - script: pip install pytest pytest-html
    displayName: 'Install dependencies'

  - script: pytest --html=report.html --self-contained-html
    displayName: 'Run pytest'

  - script: |
      curl -X POST https://app.gaffer.sh/api/upload \
        -H "X-API-Key: $(GAFFER_API_KEY)" \
        -F "[email protected]" \
        -F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"$(Build.SourceBranchName)","test_framework":"pytest"}'
    displayName: 'Upload to Gaffer'
    condition: always()

  - publish: report.html
    artifact: pytest-report
    condition: always()

Using CTRF Format

For a standardized format across all your test frameworks, consider using CTRF:

# Install the CTRF reporter for your framework:
# npm install --save-dev jest-ctrf-json-reporter
# npm install --save-dev playwright-ctrf-json-reporter
# npm install --save-dev vitest-ctrf-json-reporter
- script: npm install --save-dev jest-ctrf-json-reporter
  displayName: 'Install CTRF reporter'

- script: npm test -- --reporter=jest-ctrf-json-reporter
  displayName: 'Run tests with CTRF'

- script: |
    curl -X POST https://app.gaffer.sh/api/upload \
      -H "X-API-Key: $(GAFFER_API_KEY)" \
      -F "[email protected]" \
      -F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"$(Build.SourceBranchName)"}'
  displayName: 'Upload CTRF to Gaffer'
  condition: always()

Pull Request Pipelines

For PR pipelines, use the source branch:

trigger: none

pr:
  - main

steps:
  - script: npm ci && npm test
    displayName: 'Run tests'

  - script: |
      # Extract clean branch name from PR source
      BRANCH_NAME=$(echo "$(System.PullRequest.SourceBranch)" | sed 's|refs/heads/||')

      curl -X POST https://app.gaffer.sh/api/upload \
        -H "X-API-Key: $(GAFFER_API_KEY)" \
        -F "files=@test-results/junit.xml" \
        -F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"'"$BRANCH_NAME"'"}'
    displayName: 'Upload to Gaffer'
    condition: always()

Troubleshooting

Report not uploading

  • Verify GAFFER_API_KEY is set as a secret variable
  • Ensure condition: always() is set on the upload step
  • Check the file path is correct

401 Unauthorized

  • Check your API key starts with gfr_
  • Verify the variable name matches exactly (case-sensitive)

Variable syntax

Azure DevOps uses $(VariableName) syntax, not $VARIABLE_NAME. Make sure to use the correct format.

Branch name includes refs/heads/

Use $(Build.SourceBranchName) instead of $(Build.SourceBranch) for clean branch names.

Next Steps

Other CI Providers: GitHub Actions · GitLab CI · CircleCI · Jenkins · Bitbucket