@msalaam/xray-qe-toolkit 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -20
- package/bin/cli.js +4 -1
- package/commands/createExecution.js +1 -0
- package/commands/importResults.js +2 -0
- package/commands/init.js +2 -0
- package/commands/pushTests.js +6 -7
- package/lib/config.js +7 -0
- package/lib/jsonFile.js +12 -0
- package/lib/playwrightConverter.js +68 -45
- package/lib/testCaseBuilder.js +305 -99
- package/lib/xrayClient.js +119 -18
- package/package.json +2 -1
- package/schema/tests.schema.json +21 -2
- package/templates/README.template.md +132 -23
- package/templates/tests.json +5341 -103
- package/templates/SPEC-DRIVEN-APPROACH.md +0 -372
package/lib/xrayClient.js
CHANGED
|
@@ -90,6 +90,9 @@ export async function createIssue(cfg, issueType, fields) {
|
|
|
90
90
|
issuetype: { name: issueType },
|
|
91
91
|
...(fields.priority ? { priority: { name: fields.priority } } : {}),
|
|
92
92
|
...(fields.labels && fields.labels.length > 0 ? { labels: fields.labels } : {}),
|
|
93
|
+
...(fields.fixVersions && fields.fixVersions.length > 0
|
|
94
|
+
? { fixVersions: fields.fixVersions.map((v) => ({ name: v })) }
|
|
95
|
+
: {}),
|
|
93
96
|
},
|
|
94
97
|
};
|
|
95
98
|
|
|
@@ -147,11 +150,25 @@ export async function getIssue(cfg, issueKey) {
|
|
|
147
150
|
* @returns {Promise<object>} data object
|
|
148
151
|
*/
|
|
149
152
|
async function graphql(cfg, xrayToken, query, variables = {}) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
let response;
|
|
154
|
+
try {
|
|
155
|
+
response = await axios.post(
|
|
156
|
+
cfg.xrayGraphqlUrl,
|
|
157
|
+
{ query, variables },
|
|
158
|
+
{ httpsAgent, headers: xrayHeaders(xrayToken) }
|
|
159
|
+
);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
// Extract the actual response body from axios HTTP errors (4xx/5xx)
|
|
162
|
+
if (err.response) {
|
|
163
|
+
const body = err.response.data
|
|
164
|
+
? (typeof err.response.data === "string"
|
|
165
|
+
? err.response.data
|
|
166
|
+
: JSON.stringify(err.response.data))
|
|
167
|
+
: "(no body)";
|
|
168
|
+
throw new Error(`HTTP ${err.response.status} from Xray GraphQL: ${body}`);
|
|
169
|
+
}
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
155
172
|
|
|
156
173
|
if (response.data.errors) {
|
|
157
174
|
throw new Error(`GraphQL errors: ${JSON.stringify(response.data.errors)}`);
|
|
@@ -219,19 +236,22 @@ export async function getTests(cfg, xrayToken, opts = {}) {
|
|
|
219
236
|
}
|
|
220
237
|
|
|
221
238
|
/**
|
|
222
|
-
* Get a Test Plan by issue ID (includes its tests).
|
|
239
|
+
* Get a Test Plan by issue ID (includes its tests, paginated).
|
|
223
240
|
* @param {object} cfg
|
|
224
241
|
* @param {string} xrayToken
|
|
225
242
|
* @param {string} issueId
|
|
243
|
+
* @param {object} [opts] { limit?: number, start?: number }
|
|
226
244
|
* @returns {Promise<object>}
|
|
227
245
|
*/
|
|
228
|
-
export async function getTestPlan(cfg, xrayToken, issueId) {
|
|
246
|
+
export async function getTestPlan(cfg, xrayToken, issueId, opts = {}) {
|
|
247
|
+
const limit = opts.limit ?? 100;
|
|
248
|
+
const start = opts.start ?? 0;
|
|
229
249
|
const data = await graphql(cfg, xrayToken, `
|
|
230
|
-
query ($issueId: String
|
|
250
|
+
query ($issueId: String!, $limit: Int!, $start: Int) {
|
|
231
251
|
getTestPlan(issueId: $issueId) {
|
|
232
252
|
issueId
|
|
233
253
|
projectId
|
|
234
|
-
tests(limit:
|
|
254
|
+
tests(limit: $limit, start: $start) {
|
|
235
255
|
total
|
|
236
256
|
results {
|
|
237
257
|
issueId
|
|
@@ -246,7 +266,7 @@ export async function getTestPlan(cfg, xrayToken, issueId) {
|
|
|
246
266
|
}
|
|
247
267
|
}
|
|
248
268
|
}
|
|
249
|
-
`, { issueId: String(issueId) });
|
|
269
|
+
`, { issueId: String(issueId), limit, start });
|
|
250
270
|
return data.getTestPlan;
|
|
251
271
|
}
|
|
252
272
|
|
|
@@ -373,8 +393,58 @@ export async function getProjectSettings(cfg, xrayToken, projectId) {
|
|
|
373
393
|
return data.getProjectSettings;
|
|
374
394
|
}
|
|
375
395
|
|
|
376
|
-
// ───
|
|
396
|
+
// ─── JIRA REST — Issue search ──────────────────────────────────────────────────
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Search JIRA issues via JQL.
|
|
400
|
+
* @param {object} cfg
|
|
401
|
+
* @param {string} jql
|
|
402
|
+
* @param {string[]} [fields] Fields to return (default: summary, id, key)
|
|
403
|
+
* @param {number} [maxResults]
|
|
404
|
+
* @returns {Promise<object[]>} Array of issue objects
|
|
405
|
+
*/
|
|
406
|
+
export async function searchIssues(cfg, jql, fields = ["summary", "id", "key"], maxResults = 10) {
|
|
407
|
+
const response = await axios.get(
|
|
408
|
+
`${cfg.jiraUrl}/rest/api/3/search`,
|
|
409
|
+
{
|
|
410
|
+
httpsAgent,
|
|
411
|
+
headers: jiraHeaders(cfg),
|
|
412
|
+
params: { jql, fields: fields.join(","), maxResults },
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
return response.data.issues || [];
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// ─── Xray GraphQL — Test Set queries ──────────────────────────────────────────
|
|
377
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Get Test Sets in a project (paginated).
|
|
422
|
+
* @param {object} cfg
|
|
423
|
+
* @param {string} xrayToken
|
|
424
|
+
* @param {object} [opts] { projectId, jql, limit, start }
|
|
425
|
+
* @returns {Promise<{total, results}>}
|
|
426
|
+
*/
|
|
427
|
+
export async function getTestSets(cfg, xrayToken, opts = {}) {
|
|
428
|
+
const data = await graphql(cfg, xrayToken, `
|
|
429
|
+
query ($projectId: String, $jql: String, $limit: Int!, $start: Int) {
|
|
430
|
+
getTestSets(projectId: $projectId, jql: $jql, limit: $limit, start: $start) {
|
|
431
|
+
total
|
|
432
|
+
results {
|
|
433
|
+
issueId
|
|
434
|
+
projectId
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
`, {
|
|
439
|
+
projectId: opts.projectId || null,
|
|
440
|
+
jql: opts.jql || null,
|
|
441
|
+
limit: Math.min(opts.limit || 100, 100),
|
|
442
|
+
start: opts.start || 0,
|
|
443
|
+
});
|
|
444
|
+
return data.getTestSets;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ─── Xray GraphQL — Test mutations ────────────────────────────────────────────
|
|
378
448
|
/**
|
|
379
449
|
* Set a test issue's type via Xray GraphQL.
|
|
380
450
|
* @param {object} cfg
|
|
@@ -495,6 +565,31 @@ export async function updateTestFolder(cfg, xrayToken, issueId, folderPath) {
|
|
|
495
565
|
`, { issueId: String(issueId), folderPath });
|
|
496
566
|
}
|
|
497
567
|
|
|
568
|
+
// ─── Xray GraphQL — Precondition mutations ────────────────────────────────────
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Link precondition issues to a test.
|
|
572
|
+
* @param {object} cfg
|
|
573
|
+
* @param {string} xrayToken
|
|
574
|
+
* @param {string} testIssueId Numeric Xray issue ID of the test
|
|
575
|
+
* @param {string[]} preconditionIssueIds Numeric Xray issue IDs of preconditions
|
|
576
|
+
* @returns {Promise<{addedPreconditions: string[], warnings: string[]}>}
|
|
577
|
+
*/
|
|
578
|
+
export async function addPreconditionsToTest(cfg, xrayToken, testIssueId, preconditionIssueIds) {
|
|
579
|
+
const data = await graphql(cfg, xrayToken, `
|
|
580
|
+
mutation ($issueId: String!, $preconditionIssueIds: [String]!) {
|
|
581
|
+
addPreconditionsToTest(issueId: $issueId, preconditionIssueIds: $preconditionIssueIds) {
|
|
582
|
+
addedPreconditions
|
|
583
|
+
warnings
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
`, {
|
|
587
|
+
issueId: String(testIssueId),
|
|
588
|
+
preconditionIssueIds: preconditionIssueIds.map(String),
|
|
589
|
+
});
|
|
590
|
+
return data.addPreconditionsToTest;
|
|
591
|
+
}
|
|
592
|
+
|
|
498
593
|
// ─── Xray GraphQL — Test Plan mutations ───────────────────────────────────────
|
|
499
594
|
|
|
500
595
|
/**
|
|
@@ -978,21 +1073,20 @@ export async function getAttachment(cfg, xrayToken, attachmentId) {
|
|
|
978
1073
|
export async function withRetry(fn, opts = {}) {
|
|
979
1074
|
const maxRetries = opts.maxRetries ?? 5;
|
|
980
1075
|
const baseDelay = opts.baseDelay ?? 2000;
|
|
981
|
-
|
|
1076
|
+
// retryOn accepts a string or array of strings — retry if the error matches any
|
|
1077
|
+
const retryPatterns = Array.isArray(opts.retryOn)
|
|
1078
|
+
? opts.retryOn
|
|
1079
|
+
: [opts.retryOn ?? "issueId provided is not valid"];
|
|
982
1080
|
|
|
983
1081
|
let lastError;
|
|
984
1082
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
985
1083
|
try {
|
|
986
|
-
const delay = baseDelay * Math.pow(2, attempt);
|
|
987
|
-
if (attempt > 0) {
|
|
988
|
-
logger.wait(`Retry ${attempt}/${maxRetries - 1} after ${delay}ms...`);
|
|
989
|
-
}
|
|
990
|
-
await sleep(delay);
|
|
991
1084
|
return await fn();
|
|
992
1085
|
} catch (err) {
|
|
993
1086
|
lastError = err;
|
|
994
1087
|
const msg = err.message || "";
|
|
995
|
-
|
|
1088
|
+
const shouldRetry = retryPatterns.some((p) => msg.includes(p));
|
|
1089
|
+
if (!shouldRetry) {
|
|
996
1090
|
if (msg.includes("disallowed to impersonate") || msg.includes("no valid active user exists")) {
|
|
997
1091
|
throw new Error(
|
|
998
1092
|
`Xray user authentication mismatch.\n\n` +
|
|
@@ -1002,6 +1096,11 @@ export async function withRetry(fn, opts = {}) {
|
|
|
1002
1096
|
}
|
|
1003
1097
|
throw err;
|
|
1004
1098
|
}
|
|
1099
|
+
if (attempt < maxRetries - 1) {
|
|
1100
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
1101
|
+
logger.wait(`Retry ${attempt + 1}/${maxRetries - 1} after ${delay}ms...`);
|
|
1102
|
+
await sleep(delay);
|
|
1103
|
+
}
|
|
1005
1104
|
}
|
|
1006
1105
|
}
|
|
1007
1106
|
throw lastError;
|
|
@@ -1082,12 +1181,14 @@ export async function createTestExecution(cfg, xrayToken, opts) {
|
|
|
1082
1181
|
environments = [],
|
|
1083
1182
|
testIssueIds = [],
|
|
1084
1183
|
testPlanKey,
|
|
1184
|
+
fixVersion,
|
|
1085
1185
|
} = opts;
|
|
1086
1186
|
|
|
1087
1187
|
// 1. Create JIRA issue
|
|
1088
1188
|
const issue = await createIssue(cfg, "Test Execution", {
|
|
1089
1189
|
summary: summary || `Test Execution — ${new Date().toLocaleString()}`,
|
|
1090
1190
|
description: description || summary || "",
|
|
1191
|
+
...(fixVersion ? { fixVersions: [fixVersion] } : {}),
|
|
1091
1192
|
});
|
|
1092
1193
|
|
|
1093
1194
|
logger.step(`Test Execution created: ${issue.key}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@msalaam/xray-qe-toolkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "QE toolkit for Xray Cloud — test management, tests.json standardisation, Playwright result import, and CI pipeline integration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"license": "UNLICENSED",
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
33
|
+
"@msalaam/xray-qe-toolkit": "^1.6.0",
|
|
33
34
|
"axios": "^1.13.4",
|
|
34
35
|
"commander": "^13.1.0",
|
|
35
36
|
"dotenv": "^17.2.3",
|
package/schema/tests.schema.json
CHANGED
|
@@ -60,8 +60,27 @@
|
|
|
60
60
|
"pattern": "^/"
|
|
61
61
|
},
|
|
62
62
|
"testSet": {
|
|
63
|
-
"
|
|
64
|
-
|
|
63
|
+
"oneOf": [
|
|
64
|
+
{
|
|
65
|
+
"type": "string",
|
|
66
|
+
"description": "Name of a single Xray Test Set this test belongs to."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "array",
|
|
70
|
+
"items": { "type": "string" },
|
|
71
|
+
"minItems": 1,
|
|
72
|
+
"description": "Names of multiple Xray Test Sets this test belongs to."
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"description": "Xray Test Set name(s) — string or array of strings. Tests are grouped into Test Set issues in JIRA. xqt push creates sets automatically and adds the test to each."
|
|
76
|
+
},
|
|
77
|
+
"preconditions": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"items": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"pattern": "^[A-Z][A-Z0-9_]+-[0-9]+$"
|
|
82
|
+
},
|
|
83
|
+
"description": "JIRA issue keys of Xray Precondition issues linked to this test (e.g. [\"PROJ-5\"]). xqt push calls addPreconditionsToTest on each push."
|
|
65
84
|
},
|
|
66
85
|
"requirementKeys": {
|
|
67
86
|
"type": "array",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
10. [CI/CD Integration](#10-cicd-integration)
|
|
22
22
|
11. [xray-mapping.json](#11-xray-mappingjson)
|
|
23
23
|
12. [Configuration (.xrayrc)](#12-configuration-xrayrc)
|
|
24
|
+
13. [Troubleshooting](#13-troubleshooting)
|
|
24
25
|
|
|
25
26
|
> **This file covers the xqt toolkit commands and Xray configuration.**
|
|
26
27
|
> For the full QE process — spec-driven workflow, greenfield/brownfield steps, AI agent
|
|
@@ -152,6 +153,7 @@ After running `npx xqt init`:
|
|
|
152
153
|
"tags": ["smoke", "regression"],
|
|
153
154
|
"folder": "/{{SERVICE_NAME}}/HealthCheck/Validation",
|
|
154
155
|
"testSet": "Health Check",
|
|
156
|
+
"preconditions": ["APIEE-50"],
|
|
155
157
|
"requirementKeys": [],
|
|
156
158
|
"xray": {
|
|
157
159
|
"summary": "Service returns 200 OK when healthy",
|
|
@@ -175,7 +177,8 @@ After running `npx xqt init`:
|
|
|
175
177
|
| `skip` | boolean | — | `true` to exclude from push-tests |
|
|
176
178
|
| `tags` | string[] | — | QE tags for categorisation and filtering |
|
|
177
179
|
| `folder` | string | — | Xray repository folder path — must start with `/` |
|
|
178
|
-
| `testSet` | string | — | Test Set name in Jira —
|
|
180
|
+
| `testSet` | string **or** string[] | — | Test Set name(s) in Jira — use a string or an array to assign to multiple sets |
|
|
181
|
+
| `preconditions` | string[] | — | JIRA keys of Xray Precondition issues to link (e.g. `["APIEE-50"]`) |
|
|
179
182
|
| `requirementKeys` | string[] | — | JIRA issue keys this test covers (creates coverage links) |
|
|
180
183
|
| `xray.summary` | string | Yes | Test case title (JIRA issue summary) |
|
|
181
184
|
| `xray.description` | string | — | Detailed description |
|
|
@@ -213,14 +216,20 @@ npx xqt create-plan --summary "{{SERVICE_NAME}} v2 Regression"
|
|
|
213
216
|
npx xqt create-plan --summary "Sprint 12 Smoke Tests" --version "2024.12"
|
|
214
217
|
```
|
|
215
218
|
|
|
216
|
-
### `xqt push-tests`
|
|
219
|
+
### `xqt push-tests` (alias: `push`)
|
|
217
220
|
|
|
218
221
|
Create or update tests in Xray Cloud, then sync the Test Plan membership and folder structure.
|
|
219
222
|
|
|
223
|
+
- New tests are **bulk-imported** via the Xray REST API when there are ≥ `bulkImportThreshold` (default: 50) to create — significantly faster for large suites. Force it at any count with `--bulk`
|
|
224
|
+
- Test Plan membership is **bi-directionally synced**: new tests are added and tests removed from `tests.json` are removed from the plan
|
|
225
|
+
- Test Sets are **deduplicated by name** — safe to run on a fresh clone, existing sets are found in JIRA before creating new ones
|
|
226
|
+
- `preconditions` are linked after every create or update
|
|
227
|
+
|
|
220
228
|
```bash
|
|
221
|
-
npx xqt push
|
|
222
|
-
npx xqt push
|
|
223
|
-
npx xqt push
|
|
229
|
+
npx xqt push
|
|
230
|
+
npx xqt push --plan APIEE-1234 # override plan key
|
|
231
|
+
npx xqt push --bulk # force bulk REST import regardless of test count
|
|
232
|
+
npx xqt push --verbose
|
|
224
233
|
```
|
|
225
234
|
|
|
226
235
|
### `xqt pull-tests`
|
|
@@ -232,15 +241,15 @@ npx xqt pull-tests --plan APIEE-1234
|
|
|
232
241
|
npx xqt pull-tests --project APIEE --limit 200
|
|
233
242
|
```
|
|
234
243
|
|
|
235
|
-
### `xqt import-results`
|
|
244
|
+
### `xqt import-results` (alias: `import`)
|
|
236
245
|
|
|
237
246
|
Import test execution results into Xray. Creates a **new** Test Execution each time.
|
|
238
247
|
|
|
239
248
|
```bash
|
|
240
|
-
npx xqt import
|
|
241
|
-
npx xqt import
|
|
242
|
-
npx xqt import
|
|
243
|
-
--plan APIEE-1234 --version "
|
|
249
|
+
npx xqt import --file test-results/results.json --env IOP-QA
|
|
250
|
+
npx xqt import --file test-results/results.xml --env IOP-PROD
|
|
251
|
+
npx xqt import --file test-results/results.json --env IOP-DEV \
|
|
252
|
+
--plan APIEE-1234 --fix-version "2.5.0" --revision "a1b2c3d"
|
|
244
253
|
```
|
|
245
254
|
|
|
246
255
|
**Options:**
|
|
@@ -250,10 +259,31 @@ npx xqt import-results --file test-results/results.json --env IOP-DEV \
|
|
|
250
259
|
| `--file <path>` | Path to results file (`.json` = Playwright, `.xml` = JUnit) |
|
|
251
260
|
| `--env <label>` | Environment label: `IOP-DEV`, `IOP-QA`, `IOP-PROD` |
|
|
252
261
|
| `--plan <key>` | Test Plan key (overrides `.xrayrc`) |
|
|
253
|
-
| `--
|
|
254
|
-
| `--
|
|
262
|
+
| `--exec <key>` | Import INTO an existing execution (from `xqt exec`) |
|
|
263
|
+
| `--fix-version <ver>` | JIRA Fix Version name to stamp on the execution (e.g. `2.5.0`) |
|
|
264
|
+
| `--version <ver>` | Xray version label (free-form, separate from Fix Version) |
|
|
265
|
+
| `--revision <sha>` | Build number or git SHA (enables traceability to a specific commit) |
|
|
255
266
|
| `--summary <text>` | Custom execution summary |
|
|
256
267
|
|
|
268
|
+
### `xqt create-execution` (alias: `exec`)
|
|
269
|
+
|
|
270
|
+
Pre-create a Test Execution before running tests (for controlled test selection).
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
EXEC_KEY=$(npx xqt exec --env IOP-QA --quiet)
|
|
274
|
+
npx playwright test
|
|
275
|
+
npx xqt import --file test-results/results.json --exec $EXEC_KEY
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
| Flag | Description |
|
|
279
|
+
|---|---|
|
|
280
|
+
| `--env <label>` | Environment label |
|
|
281
|
+
| `--plan <key>` | Test Plan to link to |
|
|
282
|
+
| `--tests <ids>` | Comma-separated testIds or JIRA keys |
|
|
283
|
+
| `--summary <text>` | Custom execution title |
|
|
284
|
+
| `--fix-version <ver>` | JIRA Fix Version to stamp on the execution |
|
|
285
|
+
| `--quiet` | Print only the execution key |
|
|
286
|
+
|
|
257
287
|
### `xqt sync-folders`
|
|
258
288
|
|
|
259
289
|
Sync the Xray repository folder structure from `folder` fields in tests.json.
|
|
@@ -306,7 +336,10 @@ npx xqt gen-pipeline --output .azure/ci.yml
|
|
|
306
336
|
|
|
307
337
|
Tests live in **Test Sets** in Jira, grouped by feature (matching the feature name in `business-rules.yaml`).
|
|
308
338
|
|
|
309
|
-
-
|
|
339
|
+
- Assign a test to one set: `"testSet": "Health Check"`
|
|
340
|
+
- Assign to multiple sets: `"testSet": ["Health Check", "Smoke"]`
|
|
341
|
+
- Created automatically by `push` from the `testSet` field in `tests.json`
|
|
342
|
+
- **Deduplicated by name** — if `xray-mapping.json` is lost (e.g. fresh clone), sets are found by searching JIRA before creating a new one
|
|
310
343
|
- Persistent across sprints — they represent what tests exist
|
|
311
344
|
- Link Test Sets to Test Plans when entering a sprint
|
|
312
345
|
|
|
@@ -314,17 +347,20 @@ Tests live in **Test Sets** in Jira, grouped by feature (matching the feature na
|
|
|
314
347
|
|
|
315
348
|
A Test Plan scopes testing for a specific sprint or release.
|
|
316
349
|
|
|
317
|
-
- Create per sprint: `npx xqt
|
|
350
|
+
- Create per sprint: `npx xqt plan --summary "Sprint 12 — {{SERVICE_NAME}}"`
|
|
318
351
|
- Link relevant Test Sets to the Test Plan in Jira
|
|
319
352
|
- The key is stored in `.xrayrc` (`testPlanKey`)
|
|
320
|
-
- `import
|
|
353
|
+
- `import` links executions to the active plan
|
|
354
|
+
- **`xqt push` performs a bi-directional sync** — new tests are added and tests removed from `tests.json` are removed from the plan
|
|
321
355
|
|
|
322
356
|
### Test Executions (ephemeral)
|
|
323
357
|
|
|
324
358
|
A Test Execution represents one CI run.
|
|
325
|
-
- Created automatically by `xqt import
|
|
359
|
+
- Created automatically by `xqt import`
|
|
360
|
+
- **Pre-created** by `xqt exec` for controlled test selection (filter which tests run)
|
|
326
361
|
- Tagged with the environment (`IOP-DEV`, `IOP-QA`, `IOP-PROD`)
|
|
327
362
|
- Linked to the Test Plan
|
|
363
|
+
- Stamped with `--fix-version` and `--revision` for release traceability
|
|
328
364
|
|
|
329
365
|
### Summary
|
|
330
366
|
|
|
@@ -348,7 +384,7 @@ Set the default environment and allowed values in `.xrayrc`:
|
|
|
348
384
|
Override per run with `--env`:
|
|
349
385
|
|
|
350
386
|
```bash
|
|
351
|
-
npx xqt import
|
|
387
|
+
npx xqt import --file results.json --env IOP-PROD
|
|
352
388
|
```
|
|
353
389
|
|
|
354
390
|
---
|
|
@@ -387,12 +423,12 @@ The generated `playwright.config.ts` configures three reporters:
|
|
|
387
423
|
|
|
388
424
|
```bash
|
|
389
425
|
# After running: npx playwright test
|
|
390
|
-
npx xqt import
|
|
426
|
+
npx xqt import --file test-results/results.json --env IOP-QA
|
|
391
427
|
```
|
|
392
428
|
|
|
393
429
|
### Test steps in Xray
|
|
394
430
|
|
|
395
|
-
Steps defined with `test.step()` are automatically mapped to Xray step results
|
|
431
|
+
Steps defined with `test.step()` are automatically mapped to Xray step results. **Screenshots captured on a failing step are attached directly to that step's evidence** in Xray — giving per-step failure screenshots in the Xray UI. Traces and other attachments are attached at the test level.
|
|
396
432
|
|
|
397
433
|
```typescript
|
|
398
434
|
test('Create and retrieve user', async ({ request }) => {
|
|
@@ -481,9 +517,9 @@ Key import step (runs even on test failure):
|
|
|
481
517
|
```yaml
|
|
482
518
|
- script: |
|
|
483
519
|
REVISION=$(echo "$(Build.SourceVersion)" | cut -c1-8)
|
|
484
|
-
npx xqt import
|
|
520
|
+
npx xqt import \
|
|
485
521
|
--file test-results/results.json \
|
|
486
|
-
--version "$(Build.BuildNumber)" \
|
|
522
|
+
--fix-version "$(Build.BuildNumber)" \
|
|
487
523
|
--revision "$REVISION"
|
|
488
524
|
condition: succeededOrFailed()
|
|
489
525
|
env:
|
|
@@ -551,11 +587,34 @@ Auto-managed by `push-tests` — do not edit manually.
|
|
|
551
587
|
"defaultEnvironment": "IOP-DEV",
|
|
552
588
|
"environments": ["IOP-DEV", "IOP-QA", "IOP-PROD"],
|
|
553
589
|
"folderRoot": "/{{SERVICE_NAME}}",
|
|
554
|
-
"xrayRegion": "us"
|
|
590
|
+
"xrayRegion": "us",
|
|
591
|
+
"bulkImportThreshold": 50,
|
|
592
|
+
"statusMapping": {
|
|
593
|
+
"interrupted": "ABORTED",
|
|
594
|
+
"skipped": "TODO"
|
|
595
|
+
}
|
|
555
596
|
}
|
|
556
597
|
```
|
|
557
598
|
|
|
558
|
-
|
|
599
|
+
| Field | Description |
|
|
600
|
+
|---|---|
|
|
601
|
+
| `testPlanKey` | Default Test Plan key |
|
|
602
|
+
| `defaultEnvironment` | Fallback environment when `--env` is not provided |
|
|
603
|
+
| `environments` | Allowed environment labels |
|
|
604
|
+
| `folderRoot` | Base folder path in Xray Test Repository |
|
|
605
|
+
| `xrayRegion` | `us` (default), `eu`, or `au` |
|
|
606
|
+
| `bulkImportThreshold` | Min new-test count to switch to bulk REST import (default: `50`) |
|
|
607
|
+
| `statusMapping` | Override default Playwright → Xray status mapping |
|
|
608
|
+
|
|
609
|
+
#### Playwright status mapping
|
|
610
|
+
|
|
611
|
+
By default: `passed→PASSED`, `failed→FAILED`, `timedOut→FAILED`, `interrupted→ABORTED`, `skipped→TODO`. Override any value:
|
|
612
|
+
|
|
613
|
+
```json
|
|
614
|
+
{ "statusMapping": { "interrupted": "FAILED", "skipped": "TODO" } }
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
You can also set `XQT_BULK_THRESHOLD=<n>` as an environment variable to override the bulk threshold at runtime.
|
|
559
618
|
|
|
560
619
|
| Value | GraphQL endpoint |
|
|
561
620
|
|---|---|
|
|
@@ -567,4 +626,54 @@ You can also set `XRAY_REGION=eu` in `.env`.
|
|
|
567
626
|
|
|
568
627
|
---
|
|
569
628
|
|
|
629
|
+
## 13. Troubleshooting
|
|
630
|
+
|
|
631
|
+
**Missing credentials**
|
|
632
|
+
```
|
|
633
|
+
Error: Missing required config: xrayId, xraySecret
|
|
634
|
+
```
|
|
635
|
+
Ensure `.env` exists and all required fields are populated. Copy from `.env.example`.
|
|
636
|
+
|
|
637
|
+
**Test not found in mapping after push**
|
|
638
|
+
```bash
|
|
639
|
+
npx xqt status
|
|
640
|
+
npx xqt push --verbose
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
**Test Set creation returns HTTP 400**
|
|
644
|
+
Ensure the **Test Set** issue type exists in your JIRA project and the user can create it.
|
|
645
|
+
> **Project Settings → Issue Types** — "Test Set" must appear in the list.
|
|
646
|
+
|
|
647
|
+
**Folder sync — permission error**
|
|
648
|
+
```
|
|
649
|
+
User doesn't have permissions to view/edit test repository for project
|
|
650
|
+
```
|
|
651
|
+
The `JIRA_EMAIL` user needs **Test Repository** write access in Xray.
|
|
652
|
+
> **Project Settings → Xray → Test Repository** → add your role or user.
|
|
653
|
+
Folder errors are non-fatal — tests are still created/updated correctly without folder assignment.
|
|
654
|
+
|
|
655
|
+
**`updateTestType` "not found" on newly created tests**
|
|
656
|
+
Xray Cloud is eventually consistent. `xqt push` retries automatically with backoff. If a test still fails, rerun `xqt push` — it will update the existing test rather than duplicate it.
|
|
657
|
+
|
|
658
|
+
**Too many tests for serial mode (slow push)**
|
|
659
|
+
```bash
|
|
660
|
+
npx xqt push --bulk # force REST bulk import regardless of count
|
|
661
|
+
# or lower the threshold in .xrayrc:
|
|
662
|
+
{ "bulkImportThreshold": 10 }
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
**Import results — 0 tests matched**
|
|
666
|
+
Playwright tests must be annotated with an Xray key:
|
|
667
|
+
```typescript
|
|
668
|
+
test.info().annotations.push({ type: 'xray', description: 'APIEE-1234' });
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**"disallowed to impersonate" error**
|
|
672
|
+
`JIRA_EMAIL` must match the email of the user who created the Xray API Key.
|
|
673
|
+
|
|
674
|
+
**EU / AU region**
|
|
675
|
+
Set `XRAY_REGION=eu` or `XRAY_REGION=au` in `.env`, or `"xrayRegion": "eu"` in `.xrayrc`.
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
570
679
|
*Generated by @msalaam/xray-qe-toolkit — See [npm package](https://www.npmjs.com/package/@msalaam/xray-qe-toolkit) for updates.*
|