SpiraTest CI/CD Integration: Automated Test Results in Your Pipeline
Running automated tests in CI/CD without connecting results to your test management system means you have two sources of truth: CI logs and SpiraTest. They drift apart. Developers look at CI, QA managers look at SpiraTest, and nobody has the full picture.
SpiraTest's CI/CD integrations solve this by automatically recording automated test results against test cases in SpiraTest — maintaining a single source of truth for all testing, manual and automated.
How SpiraTest CI/CD Integration Works
The integration has two components:
- Test framework reporter — reports automated test results from your test framework to SpiraTest, linking results to test case IDs
- CI/CD plugin — triggers test runs and captures build metadata
When a CI build runs, the test reporter sends each automated test result to SpiraTest, marking the corresponding test case as passed or failed in a new test run. Test managers see automated results alongside manual results, with full traceability to requirements.
Prerequisites
Before integrating:
- SpiraTest with test cases set up and linked to requirements
- Test cases in SpiraTest with Automation Engine configured
- Your SpiraTest URL and API key (from user profile settings)
- CI/CD environment (Jenkins, GitHub Actions, or similar)
Get Your API Credentials
- Log in to SpiraTest
- Click your username → My Profile
- Navigate to the API Keys tab
- Copy your RSS Token (used for API authentication)
- Note your User ID (numeric)
You'll also need your SpiraTest base URL (e.g., https://mycompany.spiraservice.net).
Setting Up Automation in Test Cases
For automated results to appear against test cases, each test case needs an automation engine configured.
- Open a test case
- Click the Overview tab
- Set Automation Engine to the appropriate option:
NUnitfor .NETJUnitfor JavaPyTestfor PythonSelenium IDEfor record/playback- Custom name for other frameworks
- Set Automation Attachment — the test script identifier (used to match automated results to test cases)
Jenkins Integration
Install the SpiraTest Jenkins Plugin
- In Jenkins: Manage Jenkins → Manage Plugins
- Search for "SpiraTest" in the Available tab
- Install "Inflectra SpiraTest Plugin"
- Restart Jenkins
Configure SpiraTest Credentials
- Manage Jenkins → Configure System
- Find the SpiraTest section
- Enter:
- SpiraTest URL
- User name
- API Key (RSS token from your profile)
- Click Test Connection to verify
Pipeline Configuration
For a declarative pipeline:
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'mvn test'
}
}
}
post {
always {
// Publish JUnit results to Jenkins
junit 'target/surefire-reports/*.xml'
// Report to SpiraTest
spiraTest(
projectId: 5,
releaseId: 12,
testSetId: 34,
username: 'ci-user',
password: env.SPIRATEST_API_KEY,
url: 'https://mycompany.spiraservice.net'
)
}
}
}The projectId, releaseId, and testSetId come from SpiraTest URLs when viewing those items:
- Project ID: visible in project settings
- Release ID: in the URL when viewing a release
- Test Set ID: in the URL when viewing a test set
Freestyle Job Configuration
For non-pipeline Jenkins jobs:
- Open the job configuration
- Scroll to Post-build Actions
- Click Add post-build action → Report Test Results to SpiraTest
- Configure:
- SpiraTest connection (from global config)
- Project ID
- Release ID
- Test Set ID (optional — creates a new test run if not specified)
Mapping JUnit Results to SpiraTest Test Cases
For Java/Maven projects, the JUnit XML reports need test class/method names that match SpiraTest's automation attachment field.
In SpiraTest, set the Automation Attachment field to the fully qualified test class name:
com.example.tests.CheckoutTests.testCheckoutCompletesThe Jenkins plugin matches this against the JUnit XML test name to associate the result with the correct test case.
GitHub Actions Integration
SpiraTest doesn't have a native GitHub Actions action, but integration works via the SpiraTest REST API.
Using the REST API Approach
Create a workflow step that posts results after your test run:
name: Tests with SpiraTest Reporting
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install pytest pytest-junit-xml
- name: Run tests
run: pytest tests/ --junitxml=results.xml
- name: Upload results to SpiraTest
if: always()
env:
SPIRA_URL: ${{ secrets.SPIRATEST_URL }}
SPIRA_USERNAME: ${{ secrets.SPIRATEST_USERNAME }}
SPIRA_API_KEY: ${{ secrets.SPIRATEST_API_KEY }}
SPIRA_PROJECT_ID: ${{ vars.SPIRATEST_PROJECT_ID }}
SPIRA_RELEASE_ID: ${{ vars.SPIRATEST_RELEASE_ID }}
run: python .github/scripts/report-to-spiratest.py results.xmlCreate .github/scripts/report-to-spiratest.py:
import sys
import xml.etree.ElementTree as ET
import requests
import json
import os
from datetime import datetime
def parse_junit(xml_file):
tree = ET.parse(xml_file)
root = tree.getroot()
tests = []
for testsuite in root.iter('testsuite'):
for testcase in testsuite.iter('testcase'):
test = {
'name': testcase.get('classname') + '.' + testcase.get('name'),
'duration': float(testcase.get('time', 0)) * 1000,
'passed': testcase.find('failure') is None and testcase.find('error') is None,
'message': ''
}
failure = testcase.find('failure') or testcase.find('error')
if failure is not None:
test['message'] = failure.get('message', '')
tests.append(test)
return tests
def create_test_run(base_url, username, api_key, project_id, release_id, tests):
auth = (username, api_key)
headers = {'Content-Type': 'application/json'}
# Create test run
run_data = {
'Name': f'CI Run {datetime.now().strftime("%Y-%m-%d %H:%M")}',
'ReleaseId': int(release_id),
'ExecutionStatusId': 1, # In Progress
'TestRunTypeId': 2, # Automated
}
url = f"{base_url}/services/v6_0/RestService.svc/projects/{project_id}/test-runs"
response = requests.post(url, auth=auth, headers=headers, json=run_data)
run = response.json()
run_id = run['TestRunId']
# Add test results
for test in tests:
step_data = {
'Description': test['name'],
'ActualResult': test['message'] or 'Test completed',
'ExecutionStatusId': 2 if test['passed'] else 4, # 2=Passed, 4=Failed
'Position': 1
}
url = f"{base_url}/services/v6_0/RestService.svc/projects/{project_id}/test-runs/{run_id}/test-run-steps"
requests.post(url, auth=auth, headers=headers, json=step_data)
# Complete the run
passed_count = sum(1 for t in tests if t['passed'])
status = 2 if passed_count == len(tests) else 4 # 2=Passed, 4=Failed
update_data = {'ExecutionStatusId': status}
url = f"{base_url}/services/v6_0/RestService.svc/projects/{project_id}/test-runs/{run_id}"
requests.put(url, auth=auth, headers=headers, json=update_data)
print(f"Test run {run_id} created: {passed_count}/{len(tests)} passed")
if __name__ == '__main__':
xml_file = sys.argv[1]
tests = parse_junit(xml_file)
create_test_run(
os.environ['SPIRA_URL'],
os.environ['SPIRA_USERNAME'],
os.environ['SPIRA_API_KEY'],
os.environ['SPIRA_PROJECT_ID'],
os.environ['SPIRA_RELEASE_ID'],
tests
)Add secrets to your GitHub repository:
SPIRATEST_URL— your SpiraTest instance URLSPIRATEST_USERNAME— service account usernameSPIRATEST_API_KEY— API key from user profile
Add variables (not secrets):
SPIRATEST_PROJECT_ID— numeric project IDSPIRATEST_RELEASE_ID— numeric release ID
Using the SpiraTest REST API Directly
For more control, use the SpiraTest REST API directly in your pipeline. The API documentation is available in your SpiraTest instance at /services/v6_0/RestService.svc/help.
Key endpoints:
POST /projects/{id}/test-runs— create a test runPOST /projects/{id}/test-runs/{runId}/test-run-steps— add test resultsPUT /projects/{id}/test-runs/{runId}— update run statusGET /projects/{id}/test-cases— list test cases
Python / pytest Integration
For Python projects, use pytest-spiratest if available, or use conftest hooks:
# conftest.py
import pytest
import requests
import os
SPIRA_URL = os.getenv('SPIRA_URL')
SPIRA_USERNAME = os.getenv('SPIRA_USERNAME')
SPIRA_API_KEY = os.getenv('SPIRA_API_KEY')
PROJECT_ID = os.getenv('SPIRA_PROJECT_ID')
RELEASE_ID = os.getenv('SPIRA_RELEASE_ID')
results = []
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
if rep.when == 'call':
results.append({
'name': item.nodeid,
'passed': rep.passed,
'message': str(rep.longrepr) if rep.failed else ''
})
def pytest_sessionfinish(session, exitstatus):
if SPIRA_URL and results:
# Post results to SpiraTest
auth = (SPIRA_USERNAME, SPIRA_API_KEY)
# ... create test run and add results
passViewing Automated Results in SpiraTest
After CI runs, view results in SpiraTest:
- Navigate to your project
- Click Test Runs
- Find the automated run (marked with an automation icon)
- Click to see individual test case results
The test run is linked to the release, so the Requirements Coverage report updates automatically — automated test passes contribute to requirement coverage alongside manual test passes.
Configuring Test Sets for Automation
Create dedicated test sets for automated test suites:
- Test Sets → Insert Test Set
- Name: "Automated Regression Suite"
- Set Type to "Automated"
- Add test cases with automation configured
- Set the Automation Host to your CI agent
When the CI job runs, it can be configured to automatically update this test set's execution status.
Troubleshooting Common Issues
Results not appearing in SpiraTest:
- Verify project ID, release ID match exactly
- Check API key permissions — the user needs "Edit Test Run" permission
- Test the API connection from your CI environment with a curl call
Test case matching failing:
- Automation Attachment field must exactly match the test identifier in JUnit XML
- Check for namespace differences (e.g.,
com.example.CheckoutTestsvsCheckoutTests)
Duplicate test runs:
- Each pipeline trigger creates a new test run — this is expected
- Use test run names that include build numbers to distinguish them
Summary
Integrating SpiraTest with CI/CD closes the gap between automated test execution and test management reporting. QA managers see automated results in context with manual testing. Requirements coverage reflects actual CI test execution. Release readiness reports include automated test data.
24/7 Production Monitoring
CI/CD integration ensures every deployment is tested. For continuous monitoring of your production app between deployments, HelpMeTest runs automated tests every 5 minutes, 24/7, alerting you the moment something breaks in production.