SpiraTest CI/CD Integration: Automated Test Results in Your Pipeline

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:

  1. Test framework reporter — reports automated test results from your test framework to SpiraTest, linking results to test case IDs
  2. 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

  1. Log in to SpiraTest
  2. Click your username → My Profile
  3. Navigate to the API Keys tab
  4. Copy your RSS Token (used for API authentication)
  5. 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.

  1. Open a test case
  2. Click the Overview tab
  3. Set Automation Engine to the appropriate option:
    • NUnit for .NET
    • JUnit for Java
    • PyTest for Python
    • Selenium IDE for record/playback
    • Custom name for other frameworks
  4. Set Automation Attachment — the test script identifier (used to match automated results to test cases)

Jenkins Integration

Install the SpiraTest Jenkins Plugin

  1. In Jenkins: Manage Jenkins → Manage Plugins
  2. Search for "SpiraTest" in the Available tab
  3. Install "Inflectra SpiraTest Plugin"
  4. Restart Jenkins

Configure SpiraTest Credentials

  1. Manage Jenkins → Configure System
  2. Find the SpiraTest section
  3. Enter:
    • SpiraTest URL
    • User name
    • API Key (RSS token from your profile)
  4. 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:

  1. Open the job configuration
  2. Scroll to Post-build Actions
  3. Click Add post-build action → Report Test Results to SpiraTest
  4. 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.testCheckoutCompletes

The 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.xml

Create .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 URL
  • SPIRATEST_USERNAME — service account username
  • SPIRATEST_API_KEY — API key from user profile

Add variables (not secrets):

  • SPIRATEST_PROJECT_ID — numeric project ID
  • SPIRATEST_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 run
  • POST /projects/{id}/test-runs/{runId}/test-run-steps — add test results
  • PUT /projects/{id}/test-runs/{runId} — update run status
  • GET /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
        pass

Viewing Automated Results in SpiraTest

After CI runs, view results in SpiraTest:

  1. Navigate to your project
  2. Click Test Runs
  3. Find the automated run (marked with an automation icon)
  4. 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:

  1. Test Sets → Insert Test Set
  2. Name: "Automated Regression Suite"
  3. Set Type to "Automated"
  4. Add test cases with automation configured
  5. 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.CheckoutTests vs CheckoutTests)

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.

Start monitoring →

Read more

Start now free