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.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Xray exposes test cases / executions as Jira issue types (Test, Test Set, Test Plan, Test Execution, Pre-Condition) - automated test sync writes results into Test Execution issues.
Xray comes in two flavors:
| Flavor | Auth | Import endpoints |
|---|---|---|
| Xray Cloud | client_id + client_secret → JWT | https://xray.cloud.getxray.app/api/v2/import/... |
| Xray Server / DC | Jira PAT or Basic auth | https://<jira>/rest/raven/2.0/import/... |
This skill covers the Cloud flow as the primary path; Server notes are inline.
The official documentation is at docs.getxray.app. At the
time of authoring (2026-05-05), the Cloud import-results page was
403 to automated WebFetch; the URL is the canonical reference for
real-browser navigation. The well-known endpoints + payload shapes
below are documented in the official xray-junit-extensions
GitHub repo (xray-junit-ext) and the xray-postman-collections
public collections, both first-party Xray-App tools.
client_id + client_secret for a 24h JWT;
Server / DC uses a Jira PAT or Basic auth (Step 1)./junit, /cucumber, /testng, /nunit, /robot, or generic JSON)
(Step 2).@XrayTest(key=...)
(and @Requirement for coverage) so renames don't orphan issues (Step 3).xray-junit-extensions (Step 5),
JavaScript via the official Playwright reporter (references).projectKey=..., adding testExecKey=... to update
an existing Test Execution instead of creating a new one (Step 7).if: always() CI step - full workflow and the non-JVM path
in references/ci-and-non-jvm.md.Per the xray-junit-ext reference, Cloud auth is a two-step flow:
# 1. Exchange credentials for a JWT
JWT=$(curl -X POST 'https://xray.cloud.getxray.app/api/v2/authenticate' \
-H 'Content-Type: application/json' \
-d '{"client_id": "'"$XRAY_CLIENT_ID"'", "client_secret": "'"$XRAY_CLIENT_SECRET"'"}' \
| tr -d '"')
# 2. Use the JWT in subsequent requests
curl -X POST 'https://xray.cloud.getxray.app/api/v2/import/execution/junit' \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/xml' \
--data-binary @junit.xmlThe JWT has a 24-hour validity (well-documented across Xray API clients); refresh per CI run.
For Xray Server / DC, use a Jira PAT or Basic auth instead; the JWT step is skipped.
| Test runner output | Cloud endpoint |
|---|---|
| JUnit XML (Maven, Gradle, Jest, pytest, etc.) | /api/v2/import/execution/junit |
| Cucumber JSON | /api/v2/import/execution/cucumber |
| TestNG XML | /api/v2/import/execution/testng |
| NUnit XML (.NET) | /api/v2/import/execution/nunit |
| xUnit XML (.NET) | /api/v2/import/execution/xunit |
| Robot Framework XML | /api/v2/import/execution/robot |
| Generic JSON (Xray-shape) | /api/v2/import/execution |
Each endpoint accepts the format's native output and parses it server-side; no per-test pre-mapping is needed.
For the generic JSON endpoint, payload shape:
{
"info": {
"summary": "CI run for ABC-123",
"description": "Automated regression run",
"user": "ci-runner",
"version": "1.4.5",
"revision": "abc1234",
"testPlanKey": "PROJ-100",
"testEnvironments": ["staging", "chrome"]
},
"tests": [
{
"testKey": "PROJ-1234",
"start": "2026-05-05T14:00:00Z",
"finish": "2026-05-05T14:00:12Z",
"comment": "Test passed cleanly",
"status": "PASSED"
}
]
}Per xray-junit-ext, the JUnit 5/6 extension provides two annotations:
@XrayTest"enforce mapping of result to specific, existing Test identified by issue key, using the key attribute" (xray-junit-ext)
@Test
@XrayTest(key = "CALC-1000")
public void canAddNumbers() { /* ... */ }Without @XrayTest, the extension auto-creates a Test issue per
JUnit method on first run (auto-provisioning). Pinning with key
prevents drift across renames.
@Requirement"identify the covered requirement(s) ... it's possible to identify one covered issue or more" (xray-junit-ext)
@Test
@Requirement("CALC-1234")
public void canAddNumbers() { /* ... */ }This populates the Jira-side coverage link from the test back to the requirement issue.
XrayTestReporter for evidencePer xray-junit-ext, the XrayTestReporterParameterResolver
extension injects an XrayTestReporter into test methods:
"Add comments to Test Runs / Define Test Run custom field values / Attach evidence files" (xray-junit-ext)
@Test
@ExtendWith(XrayTestReporterParameterResolver.class)
@XrayTest(key = "CALC-1000")
public void canAddNumbers(XrayTestReporter reporter) {
// ... test logic ...
reporter.addComment("Calculator returned correct sum");
reporter.addEvidence("screenshot.png");
}Evidence files are attached to the Test Run inside the Test Execution issue - useful for failure debugging from Jira.
Per xray-junit-ext, the extension reads xray-junit-extensions.properties
for output config:
# xray-junit-extensions.properties (place on the test classpath)
report_filename=TEST-results
report_directory=target/xray-reports
add_timestamp_to_report_filename=falseThe output is JUnit XML augmented with Xray-specific metadata; pass this enriched XML to the import endpoint (Step 2).
Run the import as an if: always() step after the test step so failed
runs still reach Xray. Fetch a fresh JWT (Step 1), mask it
(::add-mask::), then POST the Xray-extended JUnit XML to
/api/v2/import/execution/junit?projectKey=<KEY> - projectKey (the
Jira project key) is the critical query param; without it the import
fails or lands in the wrong project. JVM suites emit the enriched XML via
xray-junit-extensions (Step 5); non-JVM teams use the official
Playwright reporter. The full GitHub Actions workflow and the Playwright
reporter setup are in
references/ci-and-non-jvm.md.
Verify after the POST: assert a 2xx whose body carries the Test
Execution issue key the import created or updated. A 4xx (bad
projectKey, expired JWT, or malformed XML) or a body with no key
means nothing landed - fix that cause and re-run; retry once on a 5xx
(transient), and never treat a non-2xx as success.
By default, each import creates a new Test Execution issue.
For "update an existing execution per build" (e.g. one execution per
release branch), pass testExecKey=PROJ-XYZ in the query string:
POST /api/v2/import/execution/junit?projectKey=CALC&testExecKey=CALC-9999Pattern:
testExecKey).A Maven + JUnit 5 suite syncing one release run to Jira project CALC:
@Test
@XrayTest(key = "CALC-1000")
public void canAddNumbers() { /* ... */ }xray-junit-extensions.properties (Step 5) writes the enriched XML to
target/xray-reports/TEST-results.xml.curl -X POST 'https://xray.cloud.getxray.app/api/v2/import/execution/junit?projectKey=CALC&testExecKey=CALC-9999' \
-H "Authorization: Bearer $JWT" \
-H 'Content-Type: application/xml' \
--data-binary @target/xray-reports/TEST-results.xmlThe result lands in Test Execution CALC-9999 with CALC-1000 marked
Passed. PR runs omit testExecKey to get a fresh execution per push
(Step 7).
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Auto-provisioning Test issues without @XrayTest(key=...) | Renaming a test method creates a new Test issue; old one orphans. | Pin every test method to an existing issue with @XrayTest(key="...") (Step 3). |
| Storing JWT secret in repo / log | Cloud secret leak; immediate quota abuse. | Mask in CI; fetch fresh per run; never log (Step 6 ::add-mask::). |
Importing without projectKey | Import lands in default project; other teams see your tests. | Always pass projectKey (Step 6). |
| New Test Execution per PR push | 100+ Jira issues per active PR; project clutter. | Reuse testExecKey per PR; create new only on push to main (Step 7). |
| Using regular JUnit XML reporter (not the Xray-extended one) | Loses @XrayTest / @Requirement annotations; mapping fails. | Use xray-junit-extensions for JVM (Step 5) or the official Playwright reporter (references). |
| Long-lived JWT cache (>24h) | Auth fails; CI runs broken silently. | Fetch JWT per run; respect the 24h validity. |
| Importing 5,000 results in one request | Server times out; partial state. | Split per suite or per Test Execution; Xray Cloud's import endpoints are sized for typical CI batches. |
xray-postman-collections for a
pre-seeded suite.cypress-xray-junit-reporter is the de-facto
choice but isn't officially supported.https://github.com/Xray-App/ are the most reliable
programmatically-fetchable sources.xray-junit-extensions repo:
@XrayTest(key=...), @Requirement(...), XrayTestReporter
injection, xray-junit-extensions.properties config.https://github.com/Xray-App/xray-postman-collections - official
Postman collections for every Xray Cloud public API endpoint
(including /api/v2/authenticate and /api/v2/import/execution/*).https://github.com/Xray-App/xray-maven-plugin - Maven-side
integration with the same import + auth shape.https://github.com/Xray-App/playwright-junit-reporter - official
Playwright reporter for Xray-compatible JUnit XML.https://docs.getxray.app/ - canonical doc portal (auth/region-gated;
consult in a real browser).junit-xml-analysis - same
upstream as the JUnit-flavored Xray import.testrail-integration,
zephyr-integration - sibling
test-management integrations.