CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/test-management-sync

Syncs automated test results into test management tools - TestRail (standalone, `add_run` + batched `add_results_for_cases`), Xray for Jira (JWT auth + `/api/v2/import/execution/*`), and Zephyr Scale (Bearer token + `/testexecutions`) - from CI. The body carries the vendor-independent push-results workflow (map tests to case IDs, open a run / execution / cycle per build, batch results back, close on main only, run as an `if: always()` step) with TestRail as the worked example; full vendor specifics live in references/ (testrail.md, xray.md, zephyr.md). Use when automated suites must keep the team's test management view in sync without a human copy-paste step; for hosted cross-run flakiness analytics rather than TCM sync use currents-integration, and for authoring / migrating test CASES rather than pushing results see qa-test-management's tcm-case-management.

80

Quality

100%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

zephyr.mdreferences/

Zephyr Scale - vendor specifics

Reference detail for test-management-sync. "Zephyr" disambiguates into three Jira test-management products that are not API-compatible - picking the right one is step zero:

ProductOrigin / current ownerKey API host pattern
Zephyr Scale (formerly TM4J)Adaptavist -> SmartBearhttps://api.zephyrscale.smartbear.com/v2/
Zephyr Squad (the older one)Atlassian -> SmartBearhttps://prod-api.zephyr4jiracloud.com/connect/
Zephyr Enterprise (server-only)SmartBearOn-prem Jira; per-instance

This page covers Zephyr Scale Cloud as the primary path - it's the most-deployed Zephyr variant in 2026 and the one new projects pick. Notes for Squad / Enterprise are inline.

The official documentation is at support.smartbear.com/zephyr-scale-cloud/. At the time of authoring (2026-05-05), the documentation site was behind WebFetch limits (auth/region-gated content); the URL is the canonical reference for real-browser navigation. Patterns below are the stable shapes documented across the SmartBear KB and per-language clients (zephyr-scale-python-client, the Postman collection SmartBear ships, and the mgechev/zephyr-scale-cloud-cli community client).

Authenticate (Zephyr Scale Cloud)

Zephyr Scale Cloud uses a long-lived API token (generated via "API Access Tokens" in the Zephyr Scale settings) sent as a Bearer token:

ZEPHYR_TOKEN=<long-lived-token>

curl -H "Authorization: Bearer $ZEPHYR_TOKEN" \
  'https://api.zephyrscale.smartbear.com/v2/healthcheck'

Unlike Xray Cloud, no JWT exchange step - the token is used directly.

The token is per-account, not per-project - guard it with the same care as a Jira admin credential.

If the team uses Zephyr Squad, the endpoints + auth differ significantly - see the Squad-specific REST API docs and the distinct prod-api.zephyr4jiracloud.com host.

Map test methods to Zephyr Test Cases

Two patterns mirror the TestRail / Xray approach.

Pattern A - Embed Test Case key in test name

def test_TC1234_can_add_to_cart():
    ...
test('can add to cart [TC1234]', async () => { /* ... */ });

A regex extracts TC1234 (the Zephyr Scale Test Case key) at sync time.

Pattern B - JUnit metadata via custom adapter

For Java / TestNG:

@Test
@TestCaseKey("PROJ-T1234")
public void canAddToCart() { /* ... */ }

The @TestCaseKey annotation is provided by community adapters (no first-party SmartBear annotation library at the time of writing); a small custom JUnit extension reads the annotation and emits a Zephyr-compatible JSON file alongside the JUnit XML.

Open a Test Cycle for the build

# scripts/zephyr_sync.py
import os, requests

BASE = 'https://api.zephyrscale.smartbear.com/v2'
HEADERS = {
    'Authorization': f"Bearer {os.environ['ZEPHYR_TOKEN']}",
    'Content-Type': 'application/json',
}
PROJECT_KEY = os.environ['JIRA_PROJECT_KEY']    # e.g. "CALC"

def open_cycle(name, version=None):
    r = requests.post(f'{BASE}/testcycles', headers=HEADERS, json={
        'projectKey': PROJECT_KEY,
        'name': name,                          # e.g. "Build #1234"
        'plannedStartDate': iso_now(),
        'description': f'Automated cycle for {os.environ.get("BUILD_VERSION", "")}',
        'jiraProjectVersion': version,         # optional Jira version ID
    })
    r.raise_for_status()
    return r.json()['key']                      # e.g. "CALC-R42"

The returned key (e.g. CALC-R42) is the Test Cycle's identifier; results land inside it.

Post execution results

Per the documented Zephyr Scale Cloud /testexecutions endpoint shape (consistent across SmartBear KB versions):

def post_execution(cycle_key, test_case_key, status, comment=None,
                   actual_end_date=None, execution_time=None):
    r = requests.post(f'{BASE}/testexecutions', headers=HEADERS, json={
        'projectKey': PROJECT_KEY,
        'testCycleKey': cycle_key,
        'testCaseKey': test_case_key,           # e.g. "CALC-T1234"
        'statusName': status,                   # 'Pass' | 'Fail' | 'Blocked' | 'Not Executed'
        'comment': comment,
        'actualEndDate': actual_end_date,       # ISO-8601
        'executionTime': execution_time,        # milliseconds
    })
    r.raise_for_status()
    return r.json()

statusName accepts the Zephyr-installed status names. For projects with custom statuses, query /statuses?projectKey=...&statusType=TEST_EXECUTION at script init to confirm the available names - don't hard-code beyond the four built-ins (Pass, Fail, Blocked, Not Executed).

Batch multiple results

The /testexecutions endpoint is per-execution. For batched POSTs, the documented /automations/executions endpoint accepts a payload that wraps multiple results - the exact shape is variant per Zephyr Scale version. The conservative pattern is to retry per-execution with bounded concurrency:

from concurrent.futures import ThreadPoolExecutor

def post_all(cycle_key, results, max_concurrent=5):
    with ThreadPoolExecutor(max_workers=max_concurrent) as ex:
        list(ex.map(lambda r: post_execution(cycle_key, **r), results))

max_concurrent=5 keeps under the rate limit (60 req/min on most plans) for typical run sizes.

Wire into CI

- name: Run tests
  run: npm test -- --reporters=jest-junit

- name: Sync to Zephyr Scale
  if: always()
  env:
    ZEPHYR_TOKEN: ${{ secrets.ZEPHYR_TOKEN }}
    JIRA_PROJECT_KEY: 'CALC'
    BUILD_VERSION: ${{ github.sha }}
  run: python scripts/zephyr_sync.py junit.xml

The script:

  1. Parses junit.xml (junit-xml-analysis).
  2. Extracts Test Case keys.
  3. Opens a Test Cycle.
  4. Posts executions with bounded concurrency (above).

Folder + label organization

Zephyr Scale Test Cases live in folders. Two patterns:

  • Per-feature folder: Checkout/, Cart/, Auth/ - automated tests in those folders sync to Test Cases there.
  • Per-tier folder: Smoke/, Regression/, Edge cases/ - automated tests carry a tier label that the sync script translates to folder.

The folder structure is created via the Zephyr UI; the sync script references existing Test Case keys and doesn't create folders on the fly.

JUnit XML import (alternative path)

Zephyr Scale also accepts a JUnit XML file via the /automations/executions/junit endpoint with a multipart body. This is simpler than the per-execution sync but loses per-test metadata (no comment, no execution time per case beyond what JUnit XML carries):

curl -X POST "https://api.zephyrscale.smartbear.com/v2/automations/executions/junit?projectKey=$JIRA_PROJECT_KEY&autoCreateTestCases=true" \
  -H "Authorization: Bearer $ZEPHYR_TOKEN" \
  -F "file=@junit.xml"

Per-execution POST is preferred when comment / evidence matters; this JUnit XML import is the lightweight default.

Worked example

A Jest suite syncing one build to project CALC:

  1. Tests carry the Test Case key in the name (Pattern A): test('can add to cart [CALC-T1234]', ...).
  2. Export the token and run tests to JUnit XML:
export ZEPHYR_TOKEN=...             # from Zephyr Scale > API Access Tokens
export JIRA_PROJECT_KEY=CALC
npm test -- --reporters=jest-junit
  1. The sync script opens a cycle, then posts one execution:
cycle = open_cycle("Build #1234")           # returns e.g. "CALC-R42"
post_execution(cycle, "CALC-T1234", "Pass",
               comment="green on CI", execution_time=1240)

The execution lands inside cycle CALC-R42; open it in Jira to see the Pass recorded against CALC-T1234.

Zephyr-specific anti-patterns

Anti-patternWhy it failsFix
Targeting Zephyr Squad endpoints with Zephyr Scale authDifferent host, different auth model; immediate 401.Confirm the variant (see the variant table at the top).
Hard-coding statusName: "Pass" / "Fail" onlyCustom statuses installed by the project break silently.Query /statuses at init; cache the valid set.
Per-execution POST with 1000 tests, no concurrencySingle-threaded; 30+ minutes for a release run.Bounded concurrency (above).
Per-execution POST with unbounded concurrencyTrips rate limit (60/min); execution drops.max_workers=5.
Reusing one Test Cycle across many buildsCycle accumulates noise; release sign-off is unreadable.One Cycle per build; Cycles can be archived per release.
autoCreateTestCases=true in CIEvery renamed test creates a new Test Case; folder fills with orphans.Pre-create Test Cases manually; sync references existing keys.
Treating the API token as session-scopedToken is long-lived per-account; no refresh.Store in CI secrets; rotate via Zephyr Scale settings, not per-run.

Limitations

  • Three Zephyr products with different APIs. Squad and Scale diverged years ago; Enterprise is its own thing. Check which variant the team has before pattern-matching tutorials.
  • No first-party adapter library across all languages. SmartBear ships Postman collections + Java reference clients; Python / JS / Ruby teams use community-maintained adapters with varying maintenance status.
  • Folder + Test Case management is a UI workflow. Programmatic Test Case creation exists but is fragile across versions; the sync-to-existing-cases pattern is more durable.
  • Documentation site is auth/region-gated. Per the source-fetch failure documented above (2026-05-05), the canonical references require real-browser navigation; per-language client repos are the most reliable programmatic source.

References

  • https://support.smartbear.com/zephyr-scale-cloud/ - canonical Zephyr Scale Cloud documentation portal (auth/region-gated; consult in a real browser).
  • https://support.smartbear.com/zephyr-scale-cloud/api-docs/ - REST API reference for Scale Cloud.
  • https://support.smartbear.com/zephyr-squad-cloud/ - Squad Cloud reference (different product, different API).

SKILL.md

tile.json