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
- Go to your Azure DevOps project
- Navigate to Pipelines → select your pipeline → Edit
- Click Variables → New variable
- Name:
GAFFER_API_KEY - Value: Your Gaffer project API key
- Check Keep this value secret
- 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:
| Variable | Description | Example |
|---|---|---|
$(Build.SourceVersion) | Full commit SHA | abc123def456... |
$(Build.SourceBranchName) | Branch name (short) | main, feature/login |
$(Build.SourceBranch) | Full branch ref | refs/heads/main |
$(System.PullRequest.SourceBranch) | PR source branch | refs/heads/feature/login |
$(Build.BuildNumber) | Build number | 20231215.1 |
$(Build.Repository.Name) | Repository name | my-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_KEYis 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
- CTRF Guide - Use the universal test format
- Upload API Reference - Full API documentation
- Slack Integration - Get test results in Slack
Other CI Providers: GitHub Actions · GitLab CI · CircleCI · Jenkins · Bitbucket