@msalaam/xray-qe-toolkit 1.4.1 → 1.6.0
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 +157 -71
- package/bin/cli.js +38 -25
- package/commands/createExecution.js +1 -0
- package/commands/createPlan.js +23 -34
- package/commands/importResults.js +2 -0
- package/commands/init.js +2 -0
- package/commands/pushTests.js +65 -7
- package/lib/config.js +7 -0
- package/lib/jsonFile.js +12 -0
- package/lib/playwrightConverter.js +68 -45
- package/lib/testCaseBuilder.js +374 -90
- package/lib/xrayClient.js +168 -11
- package/package.json +2 -1
- package/schema/tests.schema.json +126 -7
- package/templates/README.template.md +80 -23
- package/templates/tests.json +5295 -47
- package/commands/genTests.js +0 -138
- 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
|
|
|
@@ -219,19 +222,22 @@ export async function getTests(cfg, xrayToken, opts = {}) {
|
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
/**
|
|
222
|
-
* Get a Test Plan by issue ID (includes its tests).
|
|
225
|
+
* Get a Test Plan by issue ID (includes its tests, paginated).
|
|
223
226
|
* @param {object} cfg
|
|
224
227
|
* @param {string} xrayToken
|
|
225
228
|
* @param {string} issueId
|
|
229
|
+
* @param {object} [opts] { limit?: number, start?: number }
|
|
226
230
|
* @returns {Promise<object>}
|
|
227
231
|
*/
|
|
228
|
-
export async function getTestPlan(cfg, xrayToken, issueId) {
|
|
232
|
+
export async function getTestPlan(cfg, xrayToken, issueId, opts = {}) {
|
|
233
|
+
const limit = opts.limit ?? 100;
|
|
234
|
+
const start = opts.start ?? 0;
|
|
229
235
|
const data = await graphql(cfg, xrayToken, `
|
|
230
|
-
query ($issueId: String
|
|
236
|
+
query ($issueId: String!, $limit: Int!, $start: Int) {
|
|
231
237
|
getTestPlan(issueId: $issueId) {
|
|
232
238
|
issueId
|
|
233
239
|
projectId
|
|
234
|
-
tests(limit:
|
|
240
|
+
tests(limit: $limit, start: $start) {
|
|
235
241
|
total
|
|
236
242
|
results {
|
|
237
243
|
issueId
|
|
@@ -246,7 +252,7 @@ export async function getTestPlan(cfg, xrayToken, issueId) {
|
|
|
246
252
|
}
|
|
247
253
|
}
|
|
248
254
|
}
|
|
249
|
-
`, { issueId: String(issueId) });
|
|
255
|
+
`, { issueId: String(issueId), limit, start });
|
|
250
256
|
return data.getTestPlan;
|
|
251
257
|
}
|
|
252
258
|
|
|
@@ -373,8 +379,58 @@ export async function getProjectSettings(cfg, xrayToken, projectId) {
|
|
|
373
379
|
return data.getProjectSettings;
|
|
374
380
|
}
|
|
375
381
|
|
|
376
|
-
// ───
|
|
382
|
+
// ─── JIRA REST — Issue search ──────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Search JIRA issues via JQL.
|
|
386
|
+
* @param {object} cfg
|
|
387
|
+
* @param {string} jql
|
|
388
|
+
* @param {string[]} [fields] Fields to return (default: summary, id, key)
|
|
389
|
+
* @param {number} [maxResults]
|
|
390
|
+
* @returns {Promise<object[]>} Array of issue objects
|
|
391
|
+
*/
|
|
392
|
+
export async function searchIssues(cfg, jql, fields = ["summary", "id", "key"], maxResults = 10) {
|
|
393
|
+
const response = await axios.get(
|
|
394
|
+
`${cfg.jiraUrl}/rest/api/3/search`,
|
|
395
|
+
{
|
|
396
|
+
httpsAgent,
|
|
397
|
+
headers: jiraHeaders(cfg),
|
|
398
|
+
params: { jql, fields: fields.join(","), maxResults },
|
|
399
|
+
}
|
|
400
|
+
);
|
|
401
|
+
return response.data.issues || [];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ─── Xray GraphQL — Test Set queries ──────────────────────────────────────────
|
|
377
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Get Test Sets in a project (paginated).
|
|
408
|
+
* @param {object} cfg
|
|
409
|
+
* @param {string} xrayToken
|
|
410
|
+
* @param {object} [opts] { projectId, jql, limit, start }
|
|
411
|
+
* @returns {Promise<{total, results}>}
|
|
412
|
+
*/
|
|
413
|
+
export async function getTestSets(cfg, xrayToken, opts = {}) {
|
|
414
|
+
const data = await graphql(cfg, xrayToken, `
|
|
415
|
+
query ($projectId: String, $jql: String, $limit: Int!, $start: Int) {
|
|
416
|
+
getTestSets(projectId: $projectId, jql: $jql, limit: $limit, start: $start) {
|
|
417
|
+
total
|
|
418
|
+
results {
|
|
419
|
+
issueId
|
|
420
|
+
projectId
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
`, {
|
|
425
|
+
projectId: opts.projectId || null,
|
|
426
|
+
jql: opts.jql || null,
|
|
427
|
+
limit: Math.min(opts.limit || 100, 100),
|
|
428
|
+
start: opts.start || 0,
|
|
429
|
+
});
|
|
430
|
+
return data.getTestSets;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ─── Xray GraphQL — Test mutations ────────────────────────────────────────────
|
|
378
434
|
/**
|
|
379
435
|
* Set a test issue's type via Xray GraphQL.
|
|
380
436
|
* @param {object} cfg
|
|
@@ -495,6 +551,31 @@ export async function updateTestFolder(cfg, xrayToken, issueId, folderPath) {
|
|
|
495
551
|
`, { issueId: String(issueId), folderPath });
|
|
496
552
|
}
|
|
497
553
|
|
|
554
|
+
// ─── Xray GraphQL — Precondition mutations ────────────────────────────────────
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Link precondition issues to a test.
|
|
558
|
+
* @param {object} cfg
|
|
559
|
+
* @param {string} xrayToken
|
|
560
|
+
* @param {string} testIssueId Numeric Xray issue ID of the test
|
|
561
|
+
* @param {string[]} preconditionIssueIds Numeric Xray issue IDs of preconditions
|
|
562
|
+
* @returns {Promise<{addedPreconditions: string[], warnings: string[]}>}
|
|
563
|
+
*/
|
|
564
|
+
export async function addPreconditionsToTest(cfg, xrayToken, testIssueId, preconditionIssueIds) {
|
|
565
|
+
const data = await graphql(cfg, xrayToken, `
|
|
566
|
+
mutation ($issueId: String!, $preconditionIssueIds: [String]!) {
|
|
567
|
+
addPreconditionsToTest(issueId: $issueId, preconditionIssueIds: $preconditionIssueIds) {
|
|
568
|
+
addedPreconditions
|
|
569
|
+
warnings
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
`, {
|
|
573
|
+
issueId: String(testIssueId),
|
|
574
|
+
preconditionIssueIds: preconditionIssueIds.map(String),
|
|
575
|
+
});
|
|
576
|
+
return data.addPreconditionsToTest;
|
|
577
|
+
}
|
|
578
|
+
|
|
498
579
|
// ─── Xray GraphQL — Test Plan mutations ───────────────────────────────────────
|
|
499
580
|
|
|
500
581
|
/**
|
|
@@ -566,6 +647,80 @@ export async function removeTestsFromTestPlan(cfg, xrayToken, planIssueId, testI
|
|
|
566
647
|
});
|
|
567
648
|
}
|
|
568
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Remove tests from a Test Plan.
|
|
652
|
+
* @param {object} cfg
|
|
653
|
+
* @param {string} xrayToken
|
|
654
|
+
* @param {string} planIssueId
|
|
655
|
+
* @param {string[]} testIssueIds
|
|
656
|
+
*/
|
|
657
|
+
export async function removeTestsFromTestPlanById(cfg, xrayToken, planIssueId, testIssueIds) {
|
|
658
|
+
await graphql(cfg, xrayToken, `
|
|
659
|
+
mutation ($issueId: String!, $testIssueIds: [String]!) {
|
|
660
|
+
removeTestsFromTestPlan(issueId: $issueId, testIssueIds: $testIssueIds) {
|
|
661
|
+
warnings
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
`, {
|
|
665
|
+
issueId: String(planIssueId),
|
|
666
|
+
testIssueIds: testIssueIds.map(String),
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ─── Xray GraphQL — Test Set mutations ────────────────────────────────────────
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Create a new Test Set issue via Xray GraphQL.
|
|
674
|
+
*
|
|
675
|
+
* Test Sets are persistent groupings of tests by feature/area.
|
|
676
|
+
* They live across sprints — Test Plans reference Test Sets per sprint.
|
|
677
|
+
*
|
|
678
|
+
* @param {object} cfg
|
|
679
|
+
* @param {string} xrayToken
|
|
680
|
+
* @param {object} opts { summary, projectId? }
|
|
681
|
+
* @returns {Promise<{testSet: {issueId: string, projectId: string}, warnings: string[]}>}
|
|
682
|
+
*/
|
|
683
|
+
export async function createTestSet(cfg, xrayToken, opts) {
|
|
684
|
+
const data = await graphql(cfg, xrayToken, `
|
|
685
|
+
mutation ($projectId: String!, $summary: String!) {
|
|
686
|
+
createTestSet(projectId: $projectId, summary: $summary) {
|
|
687
|
+
testSet {
|
|
688
|
+
issueId
|
|
689
|
+
projectId
|
|
690
|
+
}
|
|
691
|
+
warnings
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
`, {
|
|
695
|
+
projectId: opts.projectId || cfg.jiraProjectKey,
|
|
696
|
+
summary: opts.summary,
|
|
697
|
+
});
|
|
698
|
+
return data.createTestSet;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Add tests to an existing Test Set.
|
|
703
|
+
* @param {object} cfg
|
|
704
|
+
* @param {string} xrayToken
|
|
705
|
+
* @param {string} testSetIssueId Numeric Xray issue ID of the Test Set
|
|
706
|
+
* @param {string[]} testIssueIds Numeric Xray issue IDs of tests to add
|
|
707
|
+
* @returns {Promise<{addedTests: string[], warnings: string[]}>}
|
|
708
|
+
*/
|
|
709
|
+
export async function addTestsToTestSet(cfg, xrayToken, testSetIssueId, testIssueIds) {
|
|
710
|
+
const data = await graphql(cfg, xrayToken, `
|
|
711
|
+
mutation ($issueId: String!, $testIssueIds: [String]!) {
|
|
712
|
+
addTestsToTestSet(issueId: $issueId, testIssueIds: $testIssueIds) {
|
|
713
|
+
addedTests
|
|
714
|
+
warnings
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
`, {
|
|
718
|
+
issueId: String(testSetIssueId),
|
|
719
|
+
testIssueIds: testIssueIds.map(String),
|
|
720
|
+
});
|
|
721
|
+
return data.addTestsToTestSet;
|
|
722
|
+
}
|
|
723
|
+
|
|
569
724
|
// ─── Xray GraphQL — Folder mutations ──────────────────────────────────────────
|
|
570
725
|
|
|
571
726
|
/**
|
|
@@ -909,11 +1064,6 @@ export async function withRetry(fn, opts = {}) {
|
|
|
909
1064
|
let lastError;
|
|
910
1065
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
911
1066
|
try {
|
|
912
|
-
const delay = baseDelay * Math.pow(2, attempt);
|
|
913
|
-
if (attempt > 0) {
|
|
914
|
-
logger.wait(`Retry ${attempt}/${maxRetries - 1} after ${delay}ms...`);
|
|
915
|
-
}
|
|
916
|
-
await sleep(delay);
|
|
917
1067
|
return await fn();
|
|
918
1068
|
} catch (err) {
|
|
919
1069
|
lastError = err;
|
|
@@ -928,6 +1078,11 @@ export async function withRetry(fn, opts = {}) {
|
|
|
928
1078
|
}
|
|
929
1079
|
throw err;
|
|
930
1080
|
}
|
|
1081
|
+
if (attempt < maxRetries - 1) {
|
|
1082
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
1083
|
+
logger.wait(`Retry ${attempt + 1}/${maxRetries - 1} after ${delay}ms...`);
|
|
1084
|
+
await sleep(delay);
|
|
1085
|
+
}
|
|
931
1086
|
}
|
|
932
1087
|
}
|
|
933
1088
|
throw lastError;
|
|
@@ -1008,12 +1163,14 @@ export async function createTestExecution(cfg, xrayToken, opts) {
|
|
|
1008
1163
|
environments = [],
|
|
1009
1164
|
testIssueIds = [],
|
|
1010
1165
|
testPlanKey,
|
|
1166
|
+
fixVersion,
|
|
1011
1167
|
} = opts;
|
|
1012
1168
|
|
|
1013
1169
|
// 1. Create JIRA issue
|
|
1014
1170
|
const issue = await createIssue(cfg, "Test Execution", {
|
|
1015
1171
|
summary: summary || `Test Execution — ${new Date().toLocaleString()}`,
|
|
1016
1172
|
description: description || summary || "",
|
|
1173
|
+
...(fixVersion ? { fixVersions: [fixVersion] } : {}),
|
|
1017
1174
|
});
|
|
1018
1175
|
|
|
1019
1176
|
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.0",
|
|
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.5.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
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"type": "array",
|
|
45
45
|
"items": {
|
|
46
46
|
"type": "string",
|
|
47
|
-
"enum": ["regression", "smoke", "edge", "critical", "integration", "e2e", "security", "performance"]
|
|
47
|
+
"enum": ["regression", "smoke", "edge", "critical", "integration", "e2e", "security", "performance", "contract", "functional", "negative", "positive", "boundary", "acceptance", "sanity", "data-driven", "exploratory", "accessibility"]
|
|
48
48
|
},
|
|
49
49
|
"description": "QE-assigned tags for categorisation. Also used as labels on the Xray Test issue."
|
|
50
50
|
},
|
|
@@ -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",
|
|
@@ -107,7 +126,7 @@
|
|
|
107
126
|
},
|
|
108
127
|
"steps": {
|
|
109
128
|
"type": "array",
|
|
110
|
-
"description": "Ordered
|
|
129
|
+
"description": "Ordered test steps. Dual purpose: (1) uploaded to Xray as Manual test steps visible in JIRA, (2) used by code generators to scaffold Playwright test bodies. Required when testType is Manual.",
|
|
111
130
|
"items": {
|
|
112
131
|
"type": "object",
|
|
113
132
|
"required": ["action", "expected_result"],
|
|
@@ -115,21 +134,121 @@
|
|
|
115
134
|
"action": {
|
|
116
135
|
"type": "string",
|
|
117
136
|
"minLength": 1,
|
|
118
|
-
"description": "
|
|
137
|
+
"description": "Human-readable description of the action (e.g. 'Send GET /v1/path/{param} with a valid value'). Used as the step title in Xray and as a generation hint."
|
|
119
138
|
},
|
|
120
139
|
"data": {
|
|
121
140
|
"type": "string",
|
|
122
|
-
"description": "Input data or
|
|
141
|
+
"description": "Input data or environment variable references for the step (e.g. 'clientNo: env.TEST_CLIENT_NO')."
|
|
123
142
|
},
|
|
124
143
|
"expected_result": {
|
|
125
144
|
"type": "string",
|
|
126
145
|
"minLength": 1,
|
|
127
|
-
"description": "Expected outcome
|
|
146
|
+
"description": "Expected outcome (e.g. 'HTTP 200, Content-Type: application/json, body matches MySchema')."
|
|
128
147
|
}
|
|
129
148
|
}
|
|
130
149
|
}
|
|
131
150
|
}
|
|
132
151
|
}
|
|
152
|
+
},
|
|
153
|
+
"spec": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"description": "Structured test specification used for Playwright test code generation. Contains the operation, expected response values, and input data hints.",
|
|
156
|
+
"properties": {
|
|
157
|
+
"operation": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"required": ["method", "path"],
|
|
160
|
+
"properties": {
|
|
161
|
+
"method": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
|
|
164
|
+
"description": "HTTP method."
|
|
165
|
+
},
|
|
166
|
+
"path": {
|
|
167
|
+
"type": "string",
|
|
168
|
+
"description": "API path, with path parameters in {curly} notation (e.g. /v1/resource/{id})."
|
|
169
|
+
},
|
|
170
|
+
"operationId": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"description": "OpenAPI operationId for traceability."
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"expectations": {
|
|
177
|
+
"type": "object",
|
|
178
|
+
"description": "Expected response values used to generate assertions.",
|
|
179
|
+
"properties": {
|
|
180
|
+
"statusCodes": {
|
|
181
|
+
"type": "array",
|
|
182
|
+
"items": { "type": "integer" },
|
|
183
|
+
"description": "List of acceptable HTTP status codes."
|
|
184
|
+
},
|
|
185
|
+
"contentType": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"description": "Expected Content-Type header value."
|
|
188
|
+
},
|
|
189
|
+
"responseSchemaHints": {
|
|
190
|
+
"type": "array",
|
|
191
|
+
"items": { "type": "string" },
|
|
192
|
+
"description": "OpenAPI schema refs for the expected response body (e.g. ['#/components/schemas/MyResponse'])."
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"dataHints": {
|
|
197
|
+
"type": "object",
|
|
198
|
+
"description": "Input data hints used to scaffold test fixtures and parameter substitution.",
|
|
199
|
+
"properties": {
|
|
200
|
+
"pathParams": {
|
|
201
|
+
"type": "array",
|
|
202
|
+
"items": { "type": "string" },
|
|
203
|
+
"description": "Names of path parameters (matched to env[] values by convention)."
|
|
204
|
+
},
|
|
205
|
+
"queryParams": {
|
|
206
|
+
"type": "array",
|
|
207
|
+
"items": { "type": "string" },
|
|
208
|
+
"description": "Names of query parameters."
|
|
209
|
+
},
|
|
210
|
+
"body": {
|
|
211
|
+
"type": "string",
|
|
212
|
+
"description": "Request body description or JSON schema ref (empty string for no body)."
|
|
213
|
+
},
|
|
214
|
+
"env": {
|
|
215
|
+
"type": "array",
|
|
216
|
+
"items": { "type": "string" },
|
|
217
|
+
"description": "Environment variable names required to run the test (e.g. ['TEST_CLIENT_NO'])."
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
"contract": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"description": "OpenAPI contract validation specifics. Only required when testKind is 'contract'.",
|
|
226
|
+
"properties": {
|
|
227
|
+
"operationId": {
|
|
228
|
+
"type": "string",
|
|
229
|
+
"description": "OpenAPI operationId this contract test covers."
|
|
230
|
+
},
|
|
231
|
+
"expectedStatus": {
|
|
232
|
+
"type": "integer",
|
|
233
|
+
"description": "HTTP status code the contract expects."
|
|
234
|
+
},
|
|
235
|
+
"expectedContentType": {
|
|
236
|
+
"type": "string",
|
|
237
|
+
"description": "Expected Content-Type header."
|
|
238
|
+
},
|
|
239
|
+
"responseSchemaRef": {
|
|
240
|
+
"type": "string",
|
|
241
|
+
"description": "OpenAPI schema $ref for the response body (e.g. '#/components/schemas/MyResponse')."
|
|
242
|
+
},
|
|
243
|
+
"validationScope": {
|
|
244
|
+
"type": "array",
|
|
245
|
+
"items": {
|
|
246
|
+
"type": "string",
|
|
247
|
+
"enum": ["status-code", "content-type", "required-fields", "type-checks", "headers"]
|
|
248
|
+
},
|
|
249
|
+
"description": "Which contract assertions to enforce."
|
|
250
|
+
}
|
|
251
|
+
}
|
|
133
252
|
}
|
|
134
253
|
}
|
|
135
254
|
}
|
|
@@ -152,6 +152,7 @@ After running `npx xqt init`:
|
|
|
152
152
|
"tags": ["smoke", "regression"],
|
|
153
153
|
"folder": "/{{SERVICE_NAME}}/HealthCheck/Validation",
|
|
154
154
|
"testSet": "Health Check",
|
|
155
|
+
"preconditions": ["APIEE-50"],
|
|
155
156
|
"requirementKeys": [],
|
|
156
157
|
"xray": {
|
|
157
158
|
"summary": "Service returns 200 OK when healthy",
|
|
@@ -175,7 +176,8 @@ After running `npx xqt init`:
|
|
|
175
176
|
| `skip` | boolean | — | `true` to exclude from push-tests |
|
|
176
177
|
| `tags` | string[] | — | QE tags for categorisation and filtering |
|
|
177
178
|
| `folder` | string | — | Xray repository folder path — must start with `/` |
|
|
178
|
-
| `testSet` | string | — | Test Set name in Jira —
|
|
179
|
+
| `testSet` | string **or** string[] | — | Test Set name(s) in Jira — use a string or an array to assign to multiple sets |
|
|
180
|
+
| `preconditions` | string[] | — | JIRA keys of Xray Precondition issues to link (e.g. `["APIEE-50"]`) |
|
|
179
181
|
| `requirementKeys` | string[] | — | JIRA issue keys this test covers (creates coverage links) |
|
|
180
182
|
| `xray.summary` | string | Yes | Test case title (JIRA issue summary) |
|
|
181
183
|
| `xray.description` | string | — | Detailed description |
|
|
@@ -213,14 +215,19 @@ npx xqt create-plan --summary "{{SERVICE_NAME}} v2 Regression"
|
|
|
213
215
|
npx xqt create-plan --summary "Sprint 12 Smoke Tests" --version "2024.12"
|
|
214
216
|
```
|
|
215
217
|
|
|
216
|
-
### `xqt push-tests`
|
|
218
|
+
### `xqt push-tests` (alias: `push`)
|
|
217
219
|
|
|
218
220
|
Create or update tests in Xray Cloud, then sync the Test Plan membership and folder structure.
|
|
219
221
|
|
|
222
|
+
- New tests are **bulk-imported** via the Xray REST API when there are ≥ `bulkImportThreshold` (default: 50) to create — significantly faster for large suites
|
|
223
|
+
- Test Plan membership is **bi-directionally synced**: new tests are added and tests removed from `tests.json` are removed from the plan
|
|
224
|
+
- Test Sets are **deduplicated by name** — safe to run on a fresh clone, existing sets are found in JIRA before creating new ones
|
|
225
|
+
- `preconditions` are linked after every create or update
|
|
226
|
+
|
|
220
227
|
```bash
|
|
221
|
-
npx xqt push
|
|
222
|
-
npx xqt push
|
|
223
|
-
npx xqt push
|
|
228
|
+
npx xqt push
|
|
229
|
+
npx xqt push --plan APIEE-1234 # override plan key
|
|
230
|
+
npx xqt push --verbose
|
|
224
231
|
```
|
|
225
232
|
|
|
226
233
|
### `xqt pull-tests`
|
|
@@ -232,15 +239,15 @@ npx xqt pull-tests --plan APIEE-1234
|
|
|
232
239
|
npx xqt pull-tests --project APIEE --limit 200
|
|
233
240
|
```
|
|
234
241
|
|
|
235
|
-
### `xqt import-results`
|
|
242
|
+
### `xqt import-results` (alias: `import`)
|
|
236
243
|
|
|
237
244
|
Import test execution results into Xray. Creates a **new** Test Execution each time.
|
|
238
245
|
|
|
239
246
|
```bash
|
|
240
|
-
npx xqt import
|
|
241
|
-
npx xqt import
|
|
242
|
-
npx xqt import
|
|
243
|
-
--plan APIEE-1234 --version "
|
|
247
|
+
npx xqt import --file test-results/results.json --env IOP-QA
|
|
248
|
+
npx xqt import --file test-results/results.xml --env IOP-PROD
|
|
249
|
+
npx xqt import --file test-results/results.json --env IOP-DEV \
|
|
250
|
+
--plan APIEE-1234 --fix-version "2.5.0" --revision "a1b2c3d"
|
|
244
251
|
```
|
|
245
252
|
|
|
246
253
|
**Options:**
|
|
@@ -250,10 +257,31 @@ npx xqt import-results --file test-results/results.json --env IOP-DEV \
|
|
|
250
257
|
| `--file <path>` | Path to results file (`.json` = Playwright, `.xml` = JUnit) |
|
|
251
258
|
| `--env <label>` | Environment label: `IOP-DEV`, `IOP-QA`, `IOP-PROD` |
|
|
252
259
|
| `--plan <key>` | Test Plan key (overrides `.xrayrc`) |
|
|
253
|
-
| `--
|
|
254
|
-
| `--
|
|
260
|
+
| `--exec <key>` | Import INTO an existing execution (from `xqt exec`) |
|
|
261
|
+
| `--fix-version <ver>` | JIRA Fix Version name to stamp on the execution (e.g. `2.5.0`) |
|
|
262
|
+
| `--version <ver>` | Xray version label (free-form, separate from Fix Version) |
|
|
263
|
+
| `--revision <sha>` | Build number or git SHA (enables traceability to a specific commit) |
|
|
255
264
|
| `--summary <text>` | Custom execution summary |
|
|
256
265
|
|
|
266
|
+
### `xqt create-execution` (alias: `exec`)
|
|
267
|
+
|
|
268
|
+
Pre-create a Test Execution before running tests (for controlled test selection).
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
EXEC_KEY=$(npx xqt exec --env IOP-QA --quiet)
|
|
272
|
+
npx playwright test
|
|
273
|
+
npx xqt import --file test-results/results.json --exec $EXEC_KEY
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
| Flag | Description |
|
|
277
|
+
|---|---|
|
|
278
|
+
| `--env <label>` | Environment label |
|
|
279
|
+
| `--plan <key>` | Test Plan to link to |
|
|
280
|
+
| `--tests <ids>` | Comma-separated testIds or JIRA keys |
|
|
281
|
+
| `--summary <text>` | Custom execution title |
|
|
282
|
+
| `--fix-version <ver>` | JIRA Fix Version to stamp on the execution |
|
|
283
|
+
| `--quiet` | Print only the execution key |
|
|
284
|
+
|
|
257
285
|
### `xqt sync-folders`
|
|
258
286
|
|
|
259
287
|
Sync the Xray repository folder structure from `folder` fields in tests.json.
|
|
@@ -306,7 +334,10 @@ npx xqt gen-pipeline --output .azure/ci.yml
|
|
|
306
334
|
|
|
307
335
|
Tests live in **Test Sets** in Jira, grouped by feature (matching the feature name in `business-rules.yaml`).
|
|
308
336
|
|
|
309
|
-
-
|
|
337
|
+
- Assign a test to one set: `"testSet": "Health Check"`
|
|
338
|
+
- Assign to multiple sets: `"testSet": ["Health Check", "Smoke"]`
|
|
339
|
+
- Created automatically by `push` from the `testSet` field in `tests.json`
|
|
340
|
+
- **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
341
|
- Persistent across sprints — they represent what tests exist
|
|
311
342
|
- Link Test Sets to Test Plans when entering a sprint
|
|
312
343
|
|
|
@@ -314,17 +345,20 @@ Tests live in **Test Sets** in Jira, grouped by feature (matching the feature na
|
|
|
314
345
|
|
|
315
346
|
A Test Plan scopes testing for a specific sprint or release.
|
|
316
347
|
|
|
317
|
-
- Create per sprint: `npx xqt
|
|
348
|
+
- Create per sprint: `npx xqt plan --summary "Sprint 12 — {{SERVICE_NAME}}"`
|
|
318
349
|
- Link relevant Test Sets to the Test Plan in Jira
|
|
319
350
|
- The key is stored in `.xrayrc` (`testPlanKey`)
|
|
320
|
-
- `import
|
|
351
|
+
- `import` links executions to the active plan
|
|
352
|
+
- **`xqt push` performs a bi-directional sync** — new tests are added and tests removed from `tests.json` are removed from the plan
|
|
321
353
|
|
|
322
354
|
### Test Executions (ephemeral)
|
|
323
355
|
|
|
324
356
|
A Test Execution represents one CI run.
|
|
325
|
-
- Created automatically by `xqt import
|
|
357
|
+
- Created automatically by `xqt import`
|
|
358
|
+
- **Pre-created** by `xqt exec` for controlled test selection (filter which tests run)
|
|
326
359
|
- Tagged with the environment (`IOP-DEV`, `IOP-QA`, `IOP-PROD`)
|
|
327
360
|
- Linked to the Test Plan
|
|
361
|
+
- Stamped with `--fix-version` and `--revision` for release traceability
|
|
328
362
|
|
|
329
363
|
### Summary
|
|
330
364
|
|
|
@@ -348,7 +382,7 @@ Set the default environment and allowed values in `.xrayrc`:
|
|
|
348
382
|
Override per run with `--env`:
|
|
349
383
|
|
|
350
384
|
```bash
|
|
351
|
-
npx xqt import
|
|
385
|
+
npx xqt import --file results.json --env IOP-PROD
|
|
352
386
|
```
|
|
353
387
|
|
|
354
388
|
---
|
|
@@ -387,12 +421,12 @@ The generated `playwright.config.ts` configures three reporters:
|
|
|
387
421
|
|
|
388
422
|
```bash
|
|
389
423
|
# After running: npx playwright test
|
|
390
|
-
npx xqt import
|
|
424
|
+
npx xqt import --file test-results/results.json --env IOP-QA
|
|
391
425
|
```
|
|
392
426
|
|
|
393
427
|
### Test steps in Xray
|
|
394
428
|
|
|
395
|
-
Steps defined with `test.step()` are automatically mapped to Xray step results
|
|
429
|
+
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
430
|
|
|
397
431
|
```typescript
|
|
398
432
|
test('Create and retrieve user', async ({ request }) => {
|
|
@@ -481,9 +515,9 @@ Key import step (runs even on test failure):
|
|
|
481
515
|
```yaml
|
|
482
516
|
- script: |
|
|
483
517
|
REVISION=$(echo "$(Build.SourceVersion)" | cut -c1-8)
|
|
484
|
-
npx xqt import
|
|
518
|
+
npx xqt import \
|
|
485
519
|
--file test-results/results.json \
|
|
486
|
-
--version "$(Build.BuildNumber)" \
|
|
520
|
+
--fix-version "$(Build.BuildNumber)" \
|
|
487
521
|
--revision "$REVISION"
|
|
488
522
|
condition: succeededOrFailed()
|
|
489
523
|
env:
|
|
@@ -551,11 +585,34 @@ Auto-managed by `push-tests` — do not edit manually.
|
|
|
551
585
|
"defaultEnvironment": "IOP-DEV",
|
|
552
586
|
"environments": ["IOP-DEV", "IOP-QA", "IOP-PROD"],
|
|
553
587
|
"folderRoot": "/{{SERVICE_NAME}}",
|
|
554
|
-
"xrayRegion": "us"
|
|
588
|
+
"xrayRegion": "us",
|
|
589
|
+
"bulkImportThreshold": 50,
|
|
590
|
+
"statusMapping": {
|
|
591
|
+
"interrupted": "ABORTED",
|
|
592
|
+
"skipped": "TODO"
|
|
593
|
+
}
|
|
555
594
|
}
|
|
556
595
|
```
|
|
557
596
|
|
|
558
|
-
|
|
597
|
+
| Field | Description |
|
|
598
|
+
|---|---|
|
|
599
|
+
| `testPlanKey` | Default Test Plan key |
|
|
600
|
+
| `defaultEnvironment` | Fallback environment when `--env` is not provided |
|
|
601
|
+
| `environments` | Allowed environment labels |
|
|
602
|
+
| `folderRoot` | Base folder path in Xray Test Repository |
|
|
603
|
+
| `xrayRegion` | `us` (default), `eu`, or `au` |
|
|
604
|
+
| `bulkImportThreshold` | Min new-test count to switch to bulk REST import (default: `50`) |
|
|
605
|
+
| `statusMapping` | Override default Playwright → Xray status mapping |
|
|
606
|
+
|
|
607
|
+
#### Playwright status mapping
|
|
608
|
+
|
|
609
|
+
By default: `passed→PASSED`, `failed→FAILED`, `timedOut→FAILED`, `interrupted→ABORTED`, `skipped→TODO`. Override any value:
|
|
610
|
+
|
|
611
|
+
```json
|
|
612
|
+
{ "statusMapping": { "interrupted": "FAILED", "skipped": "TODO" } }
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
You can also set `XQT_BULK_THRESHOLD=<n>` as an environment variable to override the bulk threshold at runtime.
|
|
559
616
|
|
|
560
617
|
| Value | GraphQL endpoint |
|
|
561
618
|
|---|---|
|