From qa-test-management
Author and manage Zephyr Scale Cloud test cases via the REST API v2 - create tests, attach steps, link to Jira issues, organise into folders, manage test cycles. Covers Bearer-token auth, the /testcases endpoints, the testScript / steps shape, and folder hierarchy. Use for pre-execution case authoring in Jira-anchored teams using Zephyr Scale (formerly TM4J). Distinct from Zephyr's test-cycle / execution endpoints which post results.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-test-management:zephyr-scale-case-managementThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Zephyr Scale Cloud (formerly Adaptavist TM4J, now SmartBear) exposes
Zephyr Scale Cloud (formerly Adaptavist TM4J, now SmartBear) exposes a REST API v2 with Bearer-token authentication.
Per smartbear.com/test-management/zephyr-scale (Cloudflare- protected; cite by stable URL).
For canonical anatomy, see
test-case-anatomy-reference.
test-case-quality-critic
case-quality scans for Zephyr-using teams.Zephyr Scale Cloud uses Bearer tokens generated from the Zephyr Scale UI (API access tokens, per-user):
export ZS_TOKEN="<bearer-token-from-zephyr-scale-ui>"
import requests, os
BASE = "https://api.zephyrscale.smartbear.com/v2"
HEADERS = {
"Authorization": f"Bearer {os.environ['ZS_TOKEN']}",
"Content-Type": "application/json",
}
POST /testcases:
def create_test_case(project_key, name, objective=None, precondition=None,
steps=None, owner=None, folder_id=None,
labels=None, components=None, priority="Normal",
status="Approved"):
"""
steps: list of {"inline": {"description": "...", "expectedResult": "..."}}
priority: Highest / High / Normal / Low / Lowest (per project config)
status: Approved / Draft / Deprecated
"""
body = {
"projectKey": project_key,
"name": name,
"objective": objective,
"precondition": precondition,
"ownerId": owner,
"folderId": folder_id,
"labels": labels or [],
"componentId": components,
"priorityName": priority,
"statusName": status,
}
r = requests.post(f"{BASE}/testcases", json=body, headers=HEADERS)
r.raise_for_status()
created = r.json()
if steps:
attach_steps(created["key"], steps)
return created
Steps are managed via a separate testScript endpoint:
def attach_steps(test_case_key, steps):
"""
steps: list of {"inline": {"description": str, "expectedResult": str,
"testData": str}}
"""
body = {"mode": "OVERWRITE", "items": [{"inline": s} for s in steps]}
r = requests.post(f"{BASE}/testcases/{test_case_key}/teststeps",
json=body, headers=HEADERS)
r.raise_for_status()
return r.json()
steps = [
{"description": "Navigate to /login",
"expectedResult": "Login form rendered", "testData": ""},
{"description": "Enter credentials and click Submit",
"expectedResult": "Redirected to /dashboard",
"testData": "[email protected] / pw123"},
]
attach_steps("PROJ-T123", steps)
mode: OVERWRITE replaces the existing step list; APPEND adds
to it.
def create_folder(project_key, name, folder_type="TEST_CASE", parent_id=None):
r = requests.post(f"{BASE}/folders", json={
"projectKey": project_key, "name": name,
"folderType": folder_type, # TEST_CASE / TEST_PLAN / TEST_CYCLE
"parentId": parent_id,
}, headers=HEADERS)
r.raise_for_status()
return r.json()
Folders can nest; create the hierarchy first, then place cases.
def link_to_jira(test_case_key, issue_key):
r = requests.post(f"{BASE}/testcases/{test_case_key}/links/issues",
json={"issueId": resolve_jira_issue_id(issue_key)},
headers=HEADERS)
r.raise_for_status()
Requires Jira REST API to resolve key → issue ID separately. Trace back from cases to requirements via the linked-issues endpoint.
case = requests.get(f"{BASE}/testcases/PROJ-T123", headers=HEADERS).json()
# Paginated list
def list_cases(project_key, max_results=100):
cases = []
start_at = 0
while True:
r = requests.get(f"{BASE}/testcases",
params={"projectKey": project_key,
"startAt": start_at,
"maxResults": max_results},
headers=HEADERS)
r.raise_for_status()
data = r.json()
cases.extend(data["values"])
if data["isLast"]:
break
start_at += max_results
return cases
Response shape: {"values": [...], "startAt", "maxResults", "total", "isLast"}.
import csv
with open("legacy.csv") as f:
for row in csv.DictReader(f):
case = create_test_case(
project_key=row["project"],
name=row["title"],
objective=row.get("objective"),
precondition=row.get("precondition"),
priority=row.get("priority", "Normal"),
labels=row.get("labels", "").split(",") if row.get("labels") else None,
)
steps = [
{"description": s, "expectedResult": e, "testData": d}
for s, e, d in zip(
row["steps"].split("|"),
row["expected"].split("|"),
(row.get("data") or "").split("|"),
)
]
attach_steps(case["key"], steps)
| Source field | Zephyr field |
|---|---|
| Title | name |
| Objective | objective |
| Preconditions | precondition |
| Steps | testScript items |
| Owner | ownerId (Jira user ID) |
| Priority | priorityName (project enum) |
| Status | statusName |
| Labels | labels[] |
| Component | componentId (Jira component) |
| Requirement traceability | /links/issues |
create_test_case returns {"id", "key", "self"}. The key is
the project-prefixed identifier (PROJ-T123). Build permalink:
# Jira Cloud + Zephyr Scale share a tenant
url = f"https://your-tenant.atlassian.net/projects/{project_key}?selectedItem=com.thed.zephyr.tests%3Atestcases#testcase/{key}"
Sync per-spec front-matter to Zephyr Scale:
- name: Sync to Zephyr Scale
env:
ZS_TOKEN: ${{ secrets.ZEPHYR_SCALE_TOKEN }}
run: python scripts/sync-zephyr-scale.py specs/
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Inline steps in test-case body (objective field) | Per-step pass/fail unavailable | Use /teststeps testScript endpoint |
| Hard-coded priority names | Project-specific enum may differ | Discover via project config endpoint |
| Flat folder hierarchy | Hundreds of cases unfindable | Create folders matching feature areas |
Mixing componentId and labels[] randomly | Cross-team queries break | Component for ownership; labels for cross-cutting concerns |
| Bulk-create without rate throttling | 429s on >100 cases / min | Limit to ~60 req / min |
| Storing the Bearer token in repo | Token leak | Environment variable / secret store |
statusName enum is project-scoped. Approved, Draft,
Deprecated are defaults; custom statuses possible.test-case-anatomy-reference.testrail-case-management,
xray-case-management,
allure-testops-case-management,
qase-io-case-management.zephyr-integration - different scope (result sync via test cycles).npx claudepluginhub testland/qa --plugin qa-test-managementProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.