@msalaam/xray-qe-toolkit 1.1.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/.env.example +16 -0
- package/README.md +893 -0
- package/bin/cli.js +137 -0
- package/commands/createExecution.js +46 -0
- package/commands/editJson.js +165 -0
- package/commands/genPipeline.js +42 -0
- package/commands/genPostman.js +70 -0
- package/commands/genTests.js +138 -0
- package/commands/importResults.js +50 -0
- package/commands/init.js +141 -0
- package/commands/pushTests.js +114 -0
- package/lib/config.js +108 -0
- package/lib/index.js +31 -0
- package/lib/logger.js +43 -0
- package/lib/postmanGenerator.js +305 -0
- package/lib/testCaseBuilder.js +202 -0
- package/lib/xrayClient.js +416 -0
- package/package.json +61 -0
- package/schema/tests.schema.json +112 -0
- package/templates/README.template.md +161 -0
- package/templates/azure-pipelines.yml +65 -0
- package/templates/knowledge-README.md +121 -0
- package/templates/tests.json +72 -0
- package/templates/xray-mapping.json +1 -0
- package/ui/editor/editor.js +484 -0
- package/ui/editor/index.html +150 -0
- package/ui/editor/styles.css +550 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @msalaam/xray-qe-toolkit — Xray + JIRA API Client
|
|
3
|
+
*
|
|
4
|
+
* Consolidated API layer that replaces the duplicated code from the original
|
|
5
|
+
* index.js and scripts/linkTests.js. Handles:
|
|
6
|
+
* • Xray Cloud authentication (JWT)
|
|
7
|
+
* • JIRA REST v3 issue CRUD
|
|
8
|
+
* • Xray GraphQL mutations (test type, steps)
|
|
9
|
+
* • Issue linking (Test ↔ Execution / Plan)
|
|
10
|
+
* • Exponential-backoff retry for Xray indexing race conditions
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import axios from "axios";
|
|
14
|
+
import https from "node:https";
|
|
15
|
+
import logger from "./logger.js";
|
|
16
|
+
|
|
17
|
+
// Shared HTTPS agent — rejectUnauthorized: false for corporate proxy compat
|
|
18
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
19
|
+
|
|
20
|
+
// ─── Internal helpers ──────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build the Basic Auth header value for JIRA REST calls.
|
|
24
|
+
* @param {object} cfg Config from loadConfig()
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
function jiraAuth(cfg) {
|
|
28
|
+
return Buffer.from(`${cfg.jiraEmail}:${cfg.jiraApiToken}`).toString("base64");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Standard JIRA REST headers.
|
|
33
|
+
* @param {object} cfg
|
|
34
|
+
* @returns {object}
|
|
35
|
+
*/
|
|
36
|
+
function jiraHeaders(cfg) {
|
|
37
|
+
return {
|
|
38
|
+
Authorization: `Basic ${jiraAuth(cfg)}`,
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert a plain-text description to Atlassian Document Format (ADF).
|
|
45
|
+
* @param {string} text
|
|
46
|
+
* @returns {object} ADF document
|
|
47
|
+
*/
|
|
48
|
+
export function toAdf(text) {
|
|
49
|
+
return {
|
|
50
|
+
type: "doc",
|
|
51
|
+
version: 1,
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "paragraph",
|
|
55
|
+
content: [{ type: "text", text: text || "" }],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sleep for `ms` milliseconds.
|
|
63
|
+
* @param {number} ms
|
|
64
|
+
* @returns {Promise<void>}
|
|
65
|
+
*/
|
|
66
|
+
function sleep(ms) {
|
|
67
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Xray Cloud Authentication ─────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Authenticate with Xray Cloud and return a JWT bearer token.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} cfg Config from loadConfig()
|
|
76
|
+
* @returns {Promise<string>} JWT token string
|
|
77
|
+
*/
|
|
78
|
+
export async function authenticate(cfg) {
|
|
79
|
+
logger.auth("Authenticating with Xray Cloud...");
|
|
80
|
+
const response = await axios.post(
|
|
81
|
+
cfg.xrayAuthUrl,
|
|
82
|
+
{ client_id: cfg.xrayId, client_secret: cfg.xraySecret },
|
|
83
|
+
{ httpsAgent }
|
|
84
|
+
);
|
|
85
|
+
logger.success("Authenticated with Xray Cloud");
|
|
86
|
+
return response.data; // JWT string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ─── JIRA REST v3 — Issue operations ───────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Create a JIRA issue (generic).
|
|
93
|
+
*
|
|
94
|
+
* @param {object} cfg Config
|
|
95
|
+
* @param {string} issueType e.g. "Test", "Test Execution", "Test Plan"
|
|
96
|
+
* @param {object} fields { summary, description, priority, labels, ... }
|
|
97
|
+
* @returns {Promise<{key: string, id: string}>}
|
|
98
|
+
*/
|
|
99
|
+
export async function createIssue(cfg, issueType, fields) {
|
|
100
|
+
const payload = {
|
|
101
|
+
fields: {
|
|
102
|
+
project: { key: cfg.jiraProjectKey },
|
|
103
|
+
summary: fields.summary,
|
|
104
|
+
description: toAdf(fields.description || fields.summary),
|
|
105
|
+
issuetype: { name: issueType },
|
|
106
|
+
...(fields.priority ? { priority: { name: fields.priority } } : {}),
|
|
107
|
+
...(fields.labels && fields.labels.length > 0 ? { labels: fields.labels } : {}),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const response = await axios.post(
|
|
112
|
+
`${cfg.jiraUrl}/rest/api/3/issue`,
|
|
113
|
+
payload,
|
|
114
|
+
{ httpsAgent, headers: jiraHeaders(cfg) }
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return { key: response.data.key, id: response.data.id };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Update an existing JIRA issue's fields.
|
|
122
|
+
*
|
|
123
|
+
* @param {object} cfg Config
|
|
124
|
+
* @param {string} issueKey e.g. "APIEE-6933"
|
|
125
|
+
* @param {object} fields Fields to update { summary, description, priority, labels }
|
|
126
|
+
* @returns {Promise<void>}
|
|
127
|
+
*/
|
|
128
|
+
export async function updateIssue(cfg, issueKey, fields) {
|
|
129
|
+
const update = {};
|
|
130
|
+
if (fields.summary) update.summary = fields.summary;
|
|
131
|
+
if (fields.description) update.description = toAdf(fields.description);
|
|
132
|
+
if (fields.priority) update.priority = { name: fields.priority };
|
|
133
|
+
if (fields.labels) update.labels = fields.labels;
|
|
134
|
+
|
|
135
|
+
await axios.put(
|
|
136
|
+
`${cfg.jiraUrl}/rest/api/3/issue/${issueKey}`,
|
|
137
|
+
{ fields: update },
|
|
138
|
+
{ httpsAgent, headers: jiraHeaders(cfg) }
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Fetch a JIRA issue by key.
|
|
144
|
+
*
|
|
145
|
+
* @param {object} cfg
|
|
146
|
+
* @param {string} issueKey e.g. "APIEE-6933"
|
|
147
|
+
* @returns {Promise<object>} Full issue payload
|
|
148
|
+
*/
|
|
149
|
+
export async function getIssue(cfg, issueKey) {
|
|
150
|
+
const response = await axios.get(
|
|
151
|
+
`${cfg.jiraUrl}/rest/api/3/issue/${issueKey}`,
|
|
152
|
+
{ httpsAgent, headers: jiraHeaders(cfg) }
|
|
153
|
+
);
|
|
154
|
+
return response.data;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ─── Xray GraphQL — Test type & steps ──────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Set a test issue's type to "Automated" via Xray GraphQL.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} cfg
|
|
163
|
+
* @param {string} xrayToken JWT from authenticate()
|
|
164
|
+
* @param {string} issueId Numeric JIRA issue ID (NOT the key)
|
|
165
|
+
*/
|
|
166
|
+
export async function setTestTypeAutomated(cfg, xrayToken, issueId) {
|
|
167
|
+
const headers = {
|
|
168
|
+
Authorization: `Bearer ${xrayToken}`,
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const response = await axios.post(
|
|
173
|
+
cfg.xrayGraphqlUrl,
|
|
174
|
+
{
|
|
175
|
+
query: `mutation ($issueId: String!, $testType: UpdateTestTypeInput!) {
|
|
176
|
+
updateTestType(issueId: $issueId, testType: $testType) {
|
|
177
|
+
issueId
|
|
178
|
+
testType { name kind }
|
|
179
|
+
}
|
|
180
|
+
}`,
|
|
181
|
+
variables: { issueId: String(issueId), testType: { name: "Automated" } },
|
|
182
|
+
},
|
|
183
|
+
{ httpsAgent, headers }
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
if (response.data.errors) {
|
|
187
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(response.data.errors)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Add a single test step via Xray GraphQL.
|
|
193
|
+
*
|
|
194
|
+
* @param {object} cfg
|
|
195
|
+
* @param {string} xrayToken JWT
|
|
196
|
+
* @param {string} issueId Numeric JIRA issue ID
|
|
197
|
+
* @param {object} step { action, data, expected_result }
|
|
198
|
+
*/
|
|
199
|
+
export async function addTestStep(cfg, xrayToken, issueId, step) {
|
|
200
|
+
const headers = {
|
|
201
|
+
Authorization: `Bearer ${xrayToken}`,
|
|
202
|
+
"Content-Type": "application/json",
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const response = await axios.post(
|
|
206
|
+
cfg.xrayGraphqlUrl,
|
|
207
|
+
{
|
|
208
|
+
query: `mutation ($issueId: String!, $step: CreateStepInput!) {
|
|
209
|
+
addTestStep(issueId: $issueId, step: $step) {
|
|
210
|
+
id
|
|
211
|
+
action
|
|
212
|
+
}
|
|
213
|
+
}`,
|
|
214
|
+
variables: {
|
|
215
|
+
issueId: String(issueId),
|
|
216
|
+
step: {
|
|
217
|
+
action: step.action,
|
|
218
|
+
data: step.data || "",
|
|
219
|
+
result: step.expected_result || "",
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
{ httpsAgent, headers }
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
if (response.data.errors) {
|
|
227
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(response.data.errors)}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return response.data.data.addTestStep;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Remove all existing test steps via Xray GraphQL (for update/replace flow).
|
|
235
|
+
*
|
|
236
|
+
* @param {object} cfg
|
|
237
|
+
* @param {string} xrayToken
|
|
238
|
+
* @param {string} issueId
|
|
239
|
+
*/
|
|
240
|
+
export async function removeAllTestSteps(cfg, xrayToken, issueId) {
|
|
241
|
+
const headers = {
|
|
242
|
+
Authorization: `Bearer ${xrayToken}`,
|
|
243
|
+
"Content-Type": "application/json",
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// First, get existing steps
|
|
247
|
+
const getResponse = await axios.post(
|
|
248
|
+
cfg.xrayGraphqlUrl,
|
|
249
|
+
{
|
|
250
|
+
query: `query ($issueId: String!) {
|
|
251
|
+
getTest(issueId: $issueId) {
|
|
252
|
+
steps {
|
|
253
|
+
id
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}`,
|
|
257
|
+
variables: { issueId: String(issueId) },
|
|
258
|
+
},
|
|
259
|
+
{ httpsAgent, headers }
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const steps = getResponse.data?.data?.getTest?.steps || [];
|
|
263
|
+
|
|
264
|
+
// Remove each step
|
|
265
|
+
for (const step of steps) {
|
|
266
|
+
await axios.post(
|
|
267
|
+
cfg.xrayGraphqlUrl,
|
|
268
|
+
{
|
|
269
|
+
query: `mutation ($issueId: String!, $stepId: String!) {
|
|
270
|
+
removeTestStep(issueId: $issueId, stepId: $stepId)
|
|
271
|
+
}`,
|
|
272
|
+
variables: { issueId: String(issueId), stepId: String(step.id) },
|
|
273
|
+
},
|
|
274
|
+
{ httpsAgent, headers }
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return steps.length;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ─── Issue linking ─────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Link a test issue to a container (Test Execution or Test Plan) via JIRA issue links.
|
|
285
|
+
*
|
|
286
|
+
* @param {object} cfg
|
|
287
|
+
* @param {string} testKey Inward issue key (Test)
|
|
288
|
+
* @param {string} containerKey Outward issue key (Execution / Plan)
|
|
289
|
+
* @returns {Promise<boolean>} true if linked successfully
|
|
290
|
+
*/
|
|
291
|
+
export async function linkIssues(cfg, testKey, containerKey) {
|
|
292
|
+
const payload = {
|
|
293
|
+
type: { name: "Test" },
|
|
294
|
+
inwardIssue: { key: testKey },
|
|
295
|
+
outwardIssue: { key: containerKey },
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
await axios.post(
|
|
299
|
+
`${cfg.jiraUrl}/rest/api/3/issueLink`,
|
|
300
|
+
payload,
|
|
301
|
+
{ httpsAgent, headers: jiraHeaders(cfg) }
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Link multiple test keys to a container key.
|
|
309
|
+
*
|
|
310
|
+
* @param {object} cfg
|
|
311
|
+
* @param {string[]} testKeys
|
|
312
|
+
* @param {string} containerKey Test Execution or Test Plan key
|
|
313
|
+
* @returns {Promise<{linked: string[], failed: string[]}>}
|
|
314
|
+
*/
|
|
315
|
+
export async function linkMultiple(cfg, testKeys, containerKey) {
|
|
316
|
+
const linked = [];
|
|
317
|
+
const failed = [];
|
|
318
|
+
|
|
319
|
+
for (const testKey of testKeys) {
|
|
320
|
+
try {
|
|
321
|
+
await linkIssues(cfg, testKey, containerKey);
|
|
322
|
+
linked.push(testKey);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
logger.warn(`Failed to link ${testKey}: ${err.response?.data?.errorMessages?.[0] || err.message}`);
|
|
325
|
+
failed.push(testKey);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return { linked, failed };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ─── Xray import endpoint ──────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Import JUnit/XUnit XML results into Xray Cloud.
|
|
336
|
+
*
|
|
337
|
+
* @param {object} cfg
|
|
338
|
+
* @param {string} xrayToken JWT
|
|
339
|
+
* @param {Buffer} xmlBuffer Raw XML file content
|
|
340
|
+
* @param {string} testExecKey Test Execution key to associate results with
|
|
341
|
+
* @returns {Promise<object>}
|
|
342
|
+
*/
|
|
343
|
+
export async function importResults(cfg, xrayToken, xmlBuffer, testExecKey) {
|
|
344
|
+
const url = "https://xray.cloud.getxray.app/api/v2/import/execution/junit";
|
|
345
|
+
|
|
346
|
+
const params = {};
|
|
347
|
+
if (testExecKey) params.testExecKey = testExecKey;
|
|
348
|
+
if (cfg.jiraProjectKey) params.projectKey = cfg.jiraProjectKey;
|
|
349
|
+
|
|
350
|
+
const response = await axios.post(url, xmlBuffer, {
|
|
351
|
+
httpsAgent,
|
|
352
|
+
headers: {
|
|
353
|
+
Authorization: `Bearer ${xrayToken}`,
|
|
354
|
+
"Content-Type": "application/xml",
|
|
355
|
+
},
|
|
356
|
+
params,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
return response.data;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ─── Retry wrapper ─────────────────────────────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Retry an async function with exponential backoff.
|
|
366
|
+
* Used to handle the Xray GraphQL indexing delay after new issue creation.
|
|
367
|
+
*
|
|
368
|
+
* Backoff sequence: 2s → 4s → 8s → 16s → 32s (baseDelay × 2^attempt)
|
|
369
|
+
*
|
|
370
|
+
* @param {Function} fn Async function to retry
|
|
371
|
+
* @param {object} [opts] Options
|
|
372
|
+
* @param {number} [opts.maxRetries=5] Max attempts
|
|
373
|
+
* @param {number} [opts.baseDelay=2000] Base delay in ms
|
|
374
|
+
* @param {string} [opts.retryOn] Error substring to match for retry
|
|
375
|
+
* @returns {Promise<*>}
|
|
376
|
+
*/
|
|
377
|
+
export async function withRetry(fn, opts = {}) {
|
|
378
|
+
const maxRetries = opts.maxRetries ?? 5;
|
|
379
|
+
const baseDelay = opts.baseDelay ?? 2000;
|
|
380
|
+
const retryOn = opts.retryOn ?? "issueId provided is not valid";
|
|
381
|
+
|
|
382
|
+
let lastError;
|
|
383
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
384
|
+
try {
|
|
385
|
+
const delay = baseDelay * Math.pow(2, attempt);
|
|
386
|
+
if (attempt > 0) {
|
|
387
|
+
logger.wait(`Retry ${attempt}/${maxRetries - 1} after ${delay}ms...`);
|
|
388
|
+
}
|
|
389
|
+
await sleep(delay);
|
|
390
|
+
return await fn();
|
|
391
|
+
} catch (err) {
|
|
392
|
+
lastError = err;
|
|
393
|
+
const msg = err.message || "";
|
|
394
|
+
if (!msg.includes(retryOn)) {
|
|
395
|
+
// Non-retryable error — detect impersonation issues
|
|
396
|
+
if (msg.includes("disallowed to impersonate") || msg.includes("no valid active user exists")) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`Xray user authentication mismatch detected.\n\n` +
|
|
399
|
+
`This error occurs when:\n` +
|
|
400
|
+
`1. Your JIRA_EMAIL doesn't match the Xray API Key owner\n` +
|
|
401
|
+
`2. The user doesn't have an active Xray license\n` +
|
|
402
|
+
`3. The Xray API Key was created by a different user\n\n` +
|
|
403
|
+
`Solutions:\n` +
|
|
404
|
+
`- Ensure JIRA_EMAIL matches the Xray API Key owner's email\n` +
|
|
405
|
+
`- Verify you have an active Xray license assigned\n` +
|
|
406
|
+
`- Regenerate Xray API Key with the same user as JIRA_API_TOKEN\n` +
|
|
407
|
+
`- Contact your Xray administrator\n\n` +
|
|
408
|
+
`Original error: ${msg}`
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
throw err;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
throw lastError;
|
|
416
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@msalaam/xray-qe-toolkit",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Full QE workflow toolkit for Xray Cloud integration — test management, Postman generation, CI pipeline scaffolding, and browser-based review gates for API regression projects.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"xqt": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "lib/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/cli.js",
|
|
12
|
+
"test": "node --test tests/",
|
|
13
|
+
"lint": "echo \"No linter configured\" && exit 0"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"xray",
|
|
17
|
+
"jira",
|
|
18
|
+
"testing",
|
|
19
|
+
"automation",
|
|
20
|
+
"test-management",
|
|
21
|
+
"ci-cd",
|
|
22
|
+
"quality-assurance",
|
|
23
|
+
"xray-cloud",
|
|
24
|
+
"test-automation",
|
|
25
|
+
"jira-integration",
|
|
26
|
+
"postman",
|
|
27
|
+
"newman",
|
|
28
|
+
"qe-toolkit",
|
|
29
|
+
"regression"
|
|
30
|
+
],
|
|
31
|
+
"author": "@Muhaymien96 <muhaymien96@gmail.com>",
|
|
32
|
+
"license": "UNLICENSED",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"axios": "^1.13.4",
|
|
35
|
+
"commander": "^13.1.0",
|
|
36
|
+
"dotenv": "^17.2.3",
|
|
37
|
+
"express": "^5.1.0",
|
|
38
|
+
"open": "^10.1.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"ajv": "^8.17.1"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0",
|
|
45
|
+
"npm": ">=8.0.0"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"bin/",
|
|
49
|
+
"commands/",
|
|
50
|
+
"lib/",
|
|
51
|
+
"ui/",
|
|
52
|
+
"templates/",
|
|
53
|
+
"schema/",
|
|
54
|
+
"README.md",
|
|
55
|
+
".env.example"
|
|
56
|
+
],
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"registry": "https://registry.npmjs.org/",
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://oldmutual.com/schemas/xray-qe-toolkit/tests.json",
|
|
4
|
+
"title": "XQT Test Configuration",
|
|
5
|
+
"description": "Schema for @msalaam/xray-qe-toolkit tests.json files. Defines test cases to push to Xray Cloud.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["tests"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"testExecution": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"description": "Configuration for the Test Execution issue created in JIRA.",
|
|
12
|
+
"required": ["summary"],
|
|
13
|
+
"properties": {
|
|
14
|
+
"summary": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"minLength": 1,
|
|
17
|
+
"description": "Summary/title for the Test Execution issue."
|
|
18
|
+
},
|
|
19
|
+
"description": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Description for the Test Execution issue."
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"tests": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"minItems": 1,
|
|
28
|
+
"description": "Array of test case definitions.",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"required": ["test_id", "xray"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"test_id": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"minLength": 1,
|
|
36
|
+
"pattern": "^[A-Za-z0-9_\\-]+$",
|
|
37
|
+
"description": "Unique internal test identifier. Used as the key in xray-mapping.json. Must be alphanumeric with hyphens/underscores."
|
|
38
|
+
},
|
|
39
|
+
"skip": {
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"default": false,
|
|
42
|
+
"description": "If true, this test will be excluded from push-tests."
|
|
43
|
+
},
|
|
44
|
+
"tags": {
|
|
45
|
+
"type": "array",
|
|
46
|
+
"items": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"enum": ["regression", "smoke", "edge", "critical", "integration", "e2e", "security", "performance"]
|
|
49
|
+
},
|
|
50
|
+
"description": "QE-assigned tags for categorisation. Set via edit-json UI."
|
|
51
|
+
},
|
|
52
|
+
"type": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"enum": ["api", "ui", "e2e"],
|
|
55
|
+
"default": "api",
|
|
56
|
+
"description": "Test type: 'api' for API tests (Postman), 'ui' for UI tests (Playwright), 'e2e' for end-to-end. Determines which generator applies."
|
|
57
|
+
},
|
|
58
|
+
"xray": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"required": ["summary"],
|
|
61
|
+
"description": "Xray/JIRA test case fields.",
|
|
62
|
+
"properties": {
|
|
63
|
+
"summary": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"minLength": 1,
|
|
66
|
+
"description": "JIRA issue summary (title) for the test."
|
|
67
|
+
},
|
|
68
|
+
"description": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "JIRA issue description."
|
|
71
|
+
},
|
|
72
|
+
"priority": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"enum": ["Highest", "High", "Medium", "Low", "Lowest"],
|
|
75
|
+
"description": "JIRA priority level."
|
|
76
|
+
},
|
|
77
|
+
"labels": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"items": { "type": "string" },
|
|
80
|
+
"description": "JIRA labels for the test issue."
|
|
81
|
+
},
|
|
82
|
+
"steps": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"description": "Ordered test steps.",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"required": ["action", "expected_result"],
|
|
88
|
+
"properties": {
|
|
89
|
+
"action": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"minLength": 1,
|
|
92
|
+
"description": "What action to perform."
|
|
93
|
+
},
|
|
94
|
+
"data": {
|
|
95
|
+
"type": "string",
|
|
96
|
+
"description": "Input data or parameters for the step."
|
|
97
|
+
},
|
|
98
|
+
"expected_result": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"minLength": 1,
|
|
101
|
+
"description": "Expected outcome of the step."
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|