xray-case-management
Author and manage Xray test cases (Jira issues with Test issue type) via the GraphQL + REST APIs - create tests, attach steps, link preconditions, set testType (Manual / Cucumber / Generic), associate with requirements, bulk import via JSON. Covers OAuth client_id/client_secret auth, the GraphQL createTest mutation, the REST /api/v2/import/test/bulk endpoint, and the Cucumber-style scenario authoring path. Use for pre-execution case authoring in Jira-anchored teams using Xray. Distinct from Xray's test-execution / test-run features which post results.
Install with skills.sh (any agent)
npx skills add testland/qa --skill xray-case-managementxray-case-management
Overview
In Xray, tests are first-class Jira issues with issuetype: Test. Xray augments them with test-specific data (steps, type, preconditions) accessible via either the REST v2 API or a GraphQL endpoint.
Per docs.getxray.app/display/XRAYCLOUD/REST+API (Cloudflare- protected; cite by stable URL).
For canonical anatomy this skill operates on, see test-case-anatomy-reference.
When to use
Authoring
Authentication - OAuth client credentials
Xray Cloud requires OAuth client credentials (different from Jira auth):
# From Xray Global Settings → API Keys, create a client
export XRAY_CLIENT_ID="..."
export XRAY_CLIENT_SECRET="..."
export JIRA_BASE="https://your-tenant.atlassian.net"import requests, os
# Exchange client credentials for a JWT
def get_token():
r = requests.post("https://xray.cloud.getxray.app/api/v2/authenticate",
json={"client_id": os.environ["XRAY_CLIENT_ID"],
"client_secret": os.environ["XRAY_CLIENT_SECRET"]})
r.raise_for_status()
return r.text.strip('"') # response is a quoted string
XRAY_BASE = "https://xray.cloud.getxray.app/api/v2"
GRAPHQL = "https://xray.cloud.getxray.app/api/v2/graphql"
def xray_headers(token):
return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}JWT expires in 24 hours; cache + refresh.
Create a Manual test (GraphQL)
CREATE_TEST_MUTATION = """
mutation CreateTest($input: CreateTestInput!) {
createTest(input: $input) {
test { issueId jira(fields: ["key", "summary"]) }
warnings
}
}
"""
def create_manual_test(token, project_key, summary, steps,
preconditions_issue_id=None):
"""
steps: list of {"action": "...", "data": "...", "result": "..."}
"""
variables = {
"input": {
"testType": {"name": "Manual"},
"jira": {
"fields": {
"summary": summary,
"project": {"key": project_key},
"issuetype": {"name": "Test"},
}
},
"steps": steps,
"preconditionIssueIds": [preconditions_issue_id] if preconditions_issue_id else [],
}
}
r = requests.post(GRAPHQL,
json={"query": CREATE_TEST_MUTATION, "variables": variables},
headers=xray_headers(token))
r.raise_for_status()
return r.json()Test types
Per Xray docs, the three canonical types:
| testType | Steps storage | When to use |
|---|---|---|
Manual | Structured step list with action / data / result | Default; hand-executed cases |
Cucumber | Gherkin scenario text | BDD-driven tests (composes with qa-bdd) |
Generic | Single free-text definition | Automated tests where the script is the spec |
Bulk import (REST)
POST /api/v2/import/test/bulk accepts JSON arrays of tests:
def bulk_import(token, project_key, tests):
"""
tests: list of {"testtype": "Manual", "fields": {...}, "steps": [...]}
"""
r = requests.post(
f"{XRAY_BASE}/import/test/bulk",
json={"projectKey": project_key, "tests": tests},
headers=xray_headers(token),
)
r.raise_for_status()
return r.json() # returns job id + status poll URLBulk import returns a job ID - poll /api/v2/import/test/bulk/{jobId}/status until complete.
Cucumber feature import
# Upload .feature file via /api/v2/import/feature
with open("checkout.feature", "rb") as f:
r = requests.post(
f"{XRAY_BASE}/import/feature",
files={"file": f},
params={"projectKey": "ENG"},
headers={"Authorization": f"Bearer {token}"},
)One scenario = one Test issue; one feature = one Pre-Condition (if Background present).
Link to a requirement
Requirements in Xray are arbitrary Jira issues marked with a Requirement issue type or in a configured project. Link tests to requirements via Jira issue links:
def link_test_to_requirement(jira_base, jira_email, jira_token,
test_key, req_key, link_type="Tests"):
r = requests.post(
f"{jira_base}/rest/api/3/issueLink",
json={
"type": {"name": link_type},
"inwardIssue": {"key": req_key},
"outwardIssue": {"key": test_key},
},
auth=(jira_email, jira_token),
)
r.raise_for_status()The Tests/TestedBy link type is Xray-recommended; configure per project.
Preconditions as separate issues
Xray stores preconditions as Jira issues with type Pre-Condition. Reuse a precondition across tests via issue linking:
def create_precondition(project_key, summary, definition):
# POST a Jira issue with issuetype = "Pre-Condition"
pass # via Jira REST API v3Then attach via preconditionIssueIds: [...] in the create-test mutation.
Running
Migration from another TCM
Map source TCM concepts → Xray:
| Source field | Xray field |
|---|---|
| Title | Jira summary |
| Steps | steps array |
| Preconditions text | Linked Pre-Condition issue |
| Type | testType.name (Manual / Cucumber / Generic) |
| Refs | Jira issue links to requirement issues |
Bulk import N tests via /api/v2/import/test/bulk; expect ~50 ms per test.
Updating a test
GraphQL updateTest mutation (signature similar to createTest). For step changes:
UPDATE_STEPS_MUTATION = """
mutation UpdateTestSteps($issueId: String!, $steps: [UpdateStepInput!]!) {
updateTestSteps(issueId: $issueId, steps: $steps) {
test { issueId }
warnings
}
}
"""Parsing results
createTest response includes test.issueId (internal Jira ID) and test.jira (the requested Jira fields, e.g., key). Build permalink:
url = f"{os.environ['JIRA_BASE']}/browse/{jira_key}"CI integration
Sync Cucumber .feature files on every PR merge:
- name: Sync feature files to Xray
env:
XRAY_CLIENT_ID: ${{ secrets.XRAY_CLIENT_ID }}
XRAY_CLIENT_SECRET: ${{ secrets.XRAY_CLIENT_SECRET }}
run: |
TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \
-d '{"client_id":"'$XRAY_CLIENT_ID'","client_secret":"'$XRAY_CLIENT_SECRET'"}' \
https://xray.cloud.getxray.app/api/v2/authenticate | tr -d '"')
for f in features/*.feature; do
curl -X POST -H "Authorization: Bearer $TOKEN" \
-F "file=@$f" \
"https://xray.cloud.getxray.app/api/v2/import/feature?projectKey=ENG"
doneAnti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Storing preconditions in step 1 of every test | Duplication; preconditions evolve and tests drift | Use Pre-Condition issues + preconditionIssueIds |
| Manual tests as Generic (single text blob) | Per-step results lost | Use testType: Manual with structured steps |
| Skipping the OAuth client setup | Xray Cloud rejects Jira-auth-only requests | Always use Xray's OAuth credentials |
| Hard-coding the JWT | Expires in 24 h | Cache + refresh on 401 |
| Creating tests via Jira REST without Xray-specific fields | Tests look right in Jira but Xray's data layer (steps, type) is empty | Use GraphQL createTest or REST /import/test/bulk |
| Polling job status without backoff | Rate-limited | Exponential backoff |
| One feature = many tests imported individually | Slow | Import via /import/feature (single call) |
Limitations
References
Related skills
allure-testops-case-management
Author and manage Allure TestOps test cases via the REST API - create cases, manage projects + suites, attach scenarios with nested steps, link to Jira / GitHub issues, sync with allure-results from CI runs. Covers Bearer-token auth, /api/rs/testcase CRUD endpoints, nested-step `scenario` shape, and the unique Allure TestOps feature of linking automated results back to manual case definitions. Use for pre-execution case authoring in teams using Allure TestOps as the canonical TCM.
qase-io-case-management
Author and manage Qase.io test cases via the Public API v1 - create cases, organise into suites, attach structured steps, link to Jira/Linear/GitHub, manage shared steps, and bulk-import via JSON. Covers Token header auth, /case/{project_code} CRUD endpoints, the steps array with action / expected_result / data shape, and shared-step reuse. Use for pre-execution case authoring in teams using Qase as a modern lightweight TCM.
test-case-anatomy-reference
Pure-reference catalog of test-case anatomy - what fields a well-formed test case must have and what each field means. Enumerates the ISO/IEC/IEEE 29119-3:2021 test-case template fields (identifier, objective, preconditions, inputs, steps, expected results, postconditions, environment, traceability) and the ISTQB CTAL-TM specification-technique-driven additions (equivalence partition, boundary value, decision table, state transition). Maps the canonical anatomy to five tracker-specific schemas (TestRail, Xray, Zephyr Scale, Allure TestOps, Qase). Use as the authoritative source when authoring a case template, reviewing case quality, or migrating between tools.
test-case-review-rubric
Scores an already-written test case against six per-case quality axes (objective specificity, precondition executability, step granularity, step abstraction level, expected-result observability, traceability validity) and six set-level axes (partition coverage, boundary coverage, duplication, orphan and uncovered requirements, tier shape, identifier consistency). Derives a per-case PASS / WEAK / FAIL verdict and a set verdict from it without averaging, and marks every threshold as either standard-backed (ISTQB glossary, ISTQB CTFL v4.0, ISO/IEC/IEEE 29119-3:2021) or practitioner convention (step-count ceiling, tier bands, provenance threshold). Assumes the case field list and field cardinality are already defined by a test-case anatomy reference and judges content quality only. Use when reviewing a batch of hand-written test cases before promoting them to a release suite or handing them to an automation engineer.
testrail-case-management
Author and manage test cases in TestRail via REST API v2 - create cases, organise into suites + sections, update steps + expected results, bulk import from CSV/JSON, set automation status, link to references (Jira / requirements). Covers the Steps / Text / Exploratory templates, custom-field discovery (`get_case_fields`), and pagination on `get_cases`. Use for pre-execution case authoring and repository management. Do NOT use for submitting test-run results (pass/fail, status updates): posting results via add_results_for_cases is a separate post-execution concern.
traceability-matrix-builder
Build-an-X workflow that produces a requirements-to-tests traceability matrix from a TCM case repository + a requirements source (Jira / Linear / GitHub Issues). Walks the author through (1) extracting requirements with stable IDs, (2) extracting cases + their refs, (3) computing coverage (which requirements have at least one test, which tests verify which requirements, orphaned cases / orphaned requirements), (4) emitting a CSV / Markdown / HTML matrix, and (5) producing an executive summary (X% requirement coverage, Y orphans, Z over-tested). Use for test coverage audits, finding requirements-coverage gaps, sprint-end coverage reviews, compliance documentation, and traceability in regulated industries.
zephyr-scale-case-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.