zephyr-integration
Syncs automated test results to Zephyr Scale for Jira (formerly TM4J / SmartBear / Adaptavist): picks the product variant (Scale Cloud / Squad / Enterprise), authenticates with a long-lived API token as a Bearer header, opens a Test Cycle per build, posts executions via `POST /testexecutions` (or bulk JUnit via `/automations/executions/junit`), and maps test methods to Zephyr Test Cases via `@TestCaseKey`-style annotations. Use when the team's Jira test management is Zephyr Scale; for the Xray Jira app use xray-integration, for standalone TestRail use testrail-integration, and for over-time flakiness analytics rather than TCM sync use currents-integration.
Install with skills.sh (any agent)
npx skills add testland/qa --skill zephyr-integrationzephyr-integration
Overview
"Zephyr" disambiguates into three Jira test-management products that are not API-compatible - picking the right one is step zero:
| Product | Origin / current owner | Key API host pattern |
|---|---|---|
| Zephyr Scale (formerly TM4J) | Adaptavist -> SmartBear | https://api.zephyrscale.smartbear.com/v2/ |
| Zephyr Squad (the older one) | Atlassian -> SmartBear | https://prod-api.zephyr4jiracloud.com/connect/ |
| Zephyr Enterprise (server-only) | SmartBear | On-prem Jira; per-instance |
This skill 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).
When to use
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.
How to use
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.
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).
For a lighter path that skips per-execution POSTs, Zephyr Scale also ingests a JUnit XML file directly via /automations/executions/junit
Worked example
A Jest suite syncing one build to project CALC:
export ZEPHYR_TOKEN=... # from Zephyr Scale > API Access Tokens
export JIRA_PROJECT_KEY=CALC
npm test -- --reporters=jest-junitcycle = 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. To sync the whole run, extract every key from the JUnit XML and post with bounded concurrency - see references/api-wiring.md.
Operating in CI
Run the sync as an if: always() step after the test step so failed runs still update Zephyr. The script parses junit.xml (junit-xml-analysis), extracts Test Case keys, opens one Test Cycle per build, and posts executions with bounded concurrency (keep max_workers=5 to stay under the 60 req/min rate limit). Supply ZEPHYR_TOKEN from CI secrets. The full GitHub Actions workflow, the batch helper, folder / label organization, and the bulk JUnit XML import alternative are in references/api-wiring.md.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Targeting Zephyr Squad endpoints with Zephyr Scale auth | Different host, different auth model; immediate 401. | Confirm the variant (see the Overview variant table). |
Hard-coding statusName: "Pass" / "Fail" only | Custom statuses installed by the project break silently. | Query /statuses at init; cache the valid set. |
| Per-execution POST with 1000 tests, no concurrency | Single-threaded; 30+ minutes for a release run. | Bounded concurrency (see references/api-wiring.md). |
| Per-execution POST with unbounded concurrency | Trips rate limit (60/min); execution drops. | max_workers=5 (see references/api-wiring.md). |
| Reusing one Test Cycle across many builds | Cycle accumulates noise; release sign-off is unreadable. | One Cycle per build; Cycles can be archived per release. |
autoCreateTestCases=true in CI | Every 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-scoped | Token is long-lived per-account; no refresh. | Store in CI secrets; rotate via Zephyr Scale settings, not per-run. |
Limitations
References
Zephyr Scale batch sync, CI wiring, folders, and JUnit XML import
View source (opens in new window)Zephyr Scale batch sync, CI wiring, folders, and JUnit XML import
Deep reference for the zephyr-integration SKILL.md. Consult for the bounded- concurrency batch helper, the full GitHub Actions workflow, Test Case folder / label organization, and the bulk JUnit XML import alternative.
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.xmlThe script:
Folder + label organization
Zephyr Scale Test Cases live in folders. Two patterns:
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 (Post execution results in the SKILL) is preferred when comment / evidence matters; this JUnit XML import is the lightweight default.
Related skills
allure-reports
Configures Allure Report (test-runner adapter install, `allure-results` directory wiring, `categories.json` for failure classification, `history-trend.json` retention via the copy-history-between-runs pattern), runs the Allure CLI to convert `allure-results` to a static HTML site, and uploads the report as a CI artifact. Use when the team needs richer test reporting than JUnit XML - step-level attachments, per-test history, retry tracking, and severity / epic / feature labeling across framework-agnostic adapters (pytest, Jest, JUnit, TestNG, NUnit, Mocha). As a rich static HTML report generator, it is the open-source alternative to extentreports (JVM/.NET per-test HTML narrative); for hosted cross-run flakiness analytics rather than a static per-run report use currents-integration.
cobertura-analysis
Parses Cobertura XML coverage reports (the JVM-canonical format originally from the cobertura-cobertura tool, also emitted by JaCoCo `--coverage-xml`, coverage.py `--xml`, Istanbul / Jest `cobertura` reporter, gocover-cobertura, and dotnet's `coverlet`). Walks the coverage-04 DTD structure (coverage → packages → classes → methods → lines + conditions), computes per-file deltas, and emits PR-time gating verdicts. Use when the existing CI emits Cobertura XML - typical for JVM-heavy stacks and tools that ship Cobertura as a default reporter.
coverage-diff-reporter
Builds a per-PR coverage delta report from any pair of LCOV / Cobertura / JSON coverage outputs (current run + baseline from the merge target) - emits a per-file table with line% / branch% deltas, called-out new files, hidden drops (overall +0.1pp but one file -8pp), and a single-line PR-comment summary. Use when the team has coverage in CI but needs human-readable PR feedback that points at the specific file the reviewer should focus on, not just an aggregate number.
coverage-py-analysis
Configures coverage.py for Python projects - wires `coverage run` (replacing `python` for instrumentation), enables branch coverage via the `--branch` flag or `branch = True` config, manages the `.coverage` data file (single-process and `combine` for parallel pytest-xdist runs), authors `.coveragerc` with `source` / `omit` / `fail_under`, and emits the format the downstream tool needs (`coverage report` for terminal, `coverage xml` for Cobertura, `coverage html` for human review, `coverage lcov` for SaaS, `coverage json` for programmatic post-processing). Use for any Python test stack (pytest, unittest, nose) that needs PR-time coverage signal.
currents-integration
Wires Currents.dev cross-run test analytics into a Playwright suite: installs `@currents/playwright`, authors `currents.config.ts` (env-sourced `recordKey` + `projectId`), registers `currentsReporter()`, enables trace/video/screenshot artifacts, and runs via `npx pwc` so per-test traces stream to the Currents dashboard with over-time flakiness, slowest-test, and pass-rate trends. Use when a Playwright suite needs hosted cross-run suite-health analytics; for a static per-run report use extentreports or allure-reports, and to sync results into Jira test management use zephyr-integration or xray-integration.
extentreports
Configures ExtentReports v5 for a JVM (or .NET via `extentreports-dotnet`) test run: wires `ExtentSparkReporter`, `attachReporter`, `createTest`, the `info`/`pass`/`warning`/`skip`/`fail` log chain, screenshots via `MediaEntityBuilder`, hierarchical `createNode` parent/child tests, and category/author/device labels, emitting a static HTML report alongside JUnit XML for CI artifact upload. Use when a suite on the Aventstack ExtentReports stack wants a richer per-test HTML narrative than JUnit XML gives; for code-coverage reporting use jacoco-analysis, and for hosted cross-run flakiness analytics use currents-integration.
jacoco-analysis
Configures JaCoCo for JVM projects (Java / Kotlin / Scala / Groovy) - wires the runtime agent via `jacoco-maven-plugin` `prepare-agent`, generates per-build reports (HTML / XML / CSV) via the `report` goal, gates the build via the `check` goal with element / limit / minimum rules, parses the six native counters (instructions, branches, lines, methods, classes, cyclomatic complexity), and converts JaCoCo XML to LCOV / Cobertura when downstream tools need a different format. Use when the JVM build is Maven / Gradle and the team wants the canonical JVM coverage tool - or to convert JaCoCo output for cross-language coverage aggregation.
jest-coverage-analysis
Configures Jest's built-in coverage (Istanbul-instrumented `babel` provider or V8-native `v8` provider), wires the right `coverageReporters` for downstream consumption (`lcov` for SaaS / cross-tool, `cobertura` for Jenkins, `text-summary` for terminal, `html` for human review), authors per-file `coverageThreshold` rules that focus the gate on critical paths (vs the global-only foot-gun), and parses the per-file JSON output for PR-time deltas. Use when the project tests with Jest (or Vitest, which uses the same Istanbul/V8 provider) and the team needs PR-time coverage signal that's both local-runnable and CI-gateable.
junit-xml-analysis
Parses JUnit-format XML reports (the de-facto interchange format every CI ingests - Jenkins, GitHub Actions, GitLab, Buildkite, CircleCI) into structured, machine-readable per-suite and per-case metrics tables (passed / failed / errored / skipped, time, classname, message, stack), groups failures by classname for trend analysis, and distinguishes "new failures vs flakes" by cross-referencing the `flakyFailure` and `rerunFailure` rerun elements. Use when the downstream consumer is a dashboard, script, or aggregator - not when the goal is a human-readable prose summary (use test-run-summary-author for that). Single-run, in-XML aggregation only; for cross-run cross-environment roll-ups, use a cross-run test-suite aggregator.
lcov-analysis
Parses LCOV `.info` text files (the de-facto coverage interchange format produced by gcov, llvm-cov, Coverage.py via `py2lcov`, JaCoCo via `xml2lcov`, Devel::Cover, Jest via `lcov` reporter, NYC, and most others). Extracts per-file line / function / branch metrics from the canonical record keywords (TN/SF/FN/FNDA/FNF/FNH/BRDA/BRF/BRH/DA/LH/LF), computes the diff vs a baseline, and emits per-file gating verdicts. Use for PR coverage gates that don't depend on a specific language runtime.
test-coverage-targeter
Builds a "what to test next" recommendation by combining a coverage report (LCOV / Cobertura / coverage.py JSON / Jest JSON / JaCoCo XML) with the PR's `git diff`, ranking uncovered branches by risk × cost - risk weighted by McCabe cyclomatic complexity and code-churn frequency, cost weighted by the unit-test pyramid layer (unit tests cheaper than integration than E2E). Emits a prioritized list with concrete file:line targets and the test layer recommended for each. Use when a team has the budget to write 5 - 10 new tests and needs help picking which uncovered code to target first instead of blindly chasing 100% coverage.
test-run-summary-author
Build-an-X workflow that turns a structured test-run artifact (JUnit XML, Allure JSON, TestRail / Xray / Zephyr export) plus optional release context (version, build URL, deploy target) into a narrative markdown summary for release notes, an exec status update, or a stand-up Slack post. Distinct from the per-framework parsers junit-xml-analysis / allure-reports / coverage-diff-reporter, which emit structured tabular reports: this skill takes the same data and writes the human-readable narrative. Use when a manager needs a draft release note or stand-up summary from a single run; for cross-run trend analytics use currents-integration.
testrail-integration
Syncs test runs / results / cases between an automated test suite and TestRail (Gurock / Idera) - opens a Test Run for the build (`add_run`), batches per-case results back via `add_results_for_cases` (preferred over per-test `add_result_for_case` - N+1 API calls vs 1), maps the test framework's pass/fail/skip to TestRail status IDs, and attaches build URL + version + elapsed time. Use when the team's test management is standalone TestRail (not a Jira app) and automated suites must update it without a human copy-paste step; when the TCM is instead a Jira app use xray-integration (Xray) or zephyr-integration (Zephyr Scale), and for hosted cross-run flakiness analytics rather than TCM sync use currents-integration.
xray-integration
Imports CI test results into Xray for Jira - authenticates via the `client_id` + `client_secret` → JWT exchange (Cloud) or PAT / Basic (Server), posts to the format-specific `/api/v2/import/execution/*` endpoint (`/junit` for JUnit XML, `/cucumber` for Cucumber JSON, `/nunit` / `/testng` / `/robot` for the others), and maps automated results to existing Xray Test issues via the `xray-junit-extensions` `@XrayTest(key="...")` annotation. Use when the team uses the Xray Jira app to manage Test, Test Set, and Test Execution issue types and CI must keep those execution issues in sync; for the other Jira TCM app use zephyr-integration (Zephyr Scale), for standalone non-Jira TestRail use testrail-integration, and for hosted cross-run flakiness analytics rather than TCM sync use currents-integration.