Skip to content
Join Now Login

Azure DevOps

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.

1. Add your upload token as a pipeline variable

Section titled “1. Add your upload token 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_UPLOAD_TOKEN
  5. Value: Your Gaffer project upload token
  6. Check Keep this value secret
  7. Click OK and Save
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_UPLOAD_TOKEN)" \
-F "files=@test-results/junit.xml" \
-F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"$(Build.SourceBranchName)"}'
displayName: 'Upload to Gaffer'
condition: always()

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
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_UPLOAD_TOKEN)" \
-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()
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_UPLOAD_TOKEN)" \
-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()
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_UPLOAD_TOKEN)" \
-F "files=@report.html" \
-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()

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_UPLOAD_TOKEN)" \
-F "files=@ctrf-report.json" \
-F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"$(Build.SourceBranchName)"}'
displayName: 'Upload CTRF to Gaffer'
condition: always()

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_UPLOAD_TOKEN)" \
-F "files=@test-results/junit.xml" \
-F 'tags={"commitSha":"$(Build.SourceVersion)","branch":"'"$BRANCH_NAME"'"}'
displayName: 'Upload to Gaffer'
condition: always()
  • Verify GAFFER_UPLOAD_TOKEN is set as a secret variable
  • Ensure condition: always() is set on the upload step
  • Check the file path is correct
  • Check your upload token starts with gfr_
  • Verify the variable name matches exactly (case-sensitive)

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

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

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