@msalaam/xray-qe-toolkit 1.3.0 → 1.3.2
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 +16 -0
- package/bin/cli.js +1 -1
- package/commands/importResults.js +63 -2
- package/lib/playwrightConverter.js +28 -7
- package/lib/xrayClient.js +45 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -861,6 +861,22 @@ npx playwright show-report
|
|
|
861
861
|
npx xqt import-results --file playwright-results.json --testExecKey APIEE-6811
|
|
862
862
|
```
|
|
863
863
|
|
|
864
|
+
**What happens:**
|
|
865
|
+
- ✅ Tests WITH annotations (`test.info().annotations.push(...)`) → Updates existing Xray tests
|
|
866
|
+
- ⏭️ Tests WITHOUT annotations → **Automatically skipped** (won't create duplicates)
|
|
867
|
+
- 📊 Summary shows: Passed, Failed, Skipped counts
|
|
868
|
+
- 🔗 Direct link to view results in Xray
|
|
869
|
+
|
|
870
|
+
**Verbose mode** (see exactly what's being uploaded):
|
|
871
|
+
```bash
|
|
872
|
+
npx xqt import-results \
|
|
873
|
+
--file playwright-results.json \
|
|
874
|
+
--testExecKey APIEE-6811 \
|
|
875
|
+
--verbose
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
This saves `playwright-results-xray-debug.json` for inspection.
|
|
879
|
+
|
|
864
880
|
#### Step 6: Configure CI/CD
|
|
865
881
|
|
|
866
882
|
**Azure Pipelines:**
|
package/bin/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ export default async function importResults(opts = {}) {
|
|
|
48
48
|
|
|
49
49
|
if (fileExt === '.json') {
|
|
50
50
|
// Playwright JSON format
|
|
51
|
-
logger.send(`
|
|
51
|
+
logger.send(`Converting Playwright results to Xray format...`);
|
|
52
52
|
|
|
53
53
|
const playwrightJson = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
54
54
|
|
|
@@ -58,11 +58,38 @@ export default async function importResults(opts = {}) {
|
|
|
58
58
|
projectKey: cfg.jiraProjectKey,
|
|
59
59
|
summary: opts.summary || `Playwright Test Execution - ${new Date().toLocaleString()}`,
|
|
60
60
|
description: opts.description,
|
|
61
|
+
skipWithoutAnnotations: !!opts.testExecKey, // Skip tests without keys when updating existing execution
|
|
61
62
|
});
|
|
62
63
|
|
|
63
|
-
|
|
64
|
+
// Check if we have tests to upload
|
|
65
|
+
if (xrayJson.tests.length === 0) {
|
|
66
|
+
logger.warn('⚠️ No tests with Xray annotations found.');
|
|
67
|
+
logger.info('\nAdd annotations to your tests:');
|
|
68
|
+
logger.info(` test.info().annotations.push({ type: 'xray', description: 'APIEE-XXXX' });\n`);
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const testsWithKeys = xrayJson.tests.filter(t => t.testKey).length;
|
|
73
|
+
const testsWithoutKeys = xrayJson.tests.filter(t => !t.testKey).length;
|
|
74
|
+
|
|
75
|
+
logger.step(`Found ${testsWithKeys} test(s) with Xray annotations`);
|
|
76
|
+
|
|
77
|
+
if (testsWithoutKeys > 0 && !opts.testExecKey) {
|
|
78
|
+
logger.info(` ${testsWithoutKeys} test(s) without annotations will create new tests`);
|
|
79
|
+
} else if (testsWithoutKeys > 0 && opts.testExecKey) {
|
|
80
|
+
logger.warn(` Skipped ${testsWithoutKeys} test(s) without annotations`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Save debug output if verbose
|
|
84
|
+
if (opts.verbose) {
|
|
85
|
+
const debugPath = filePath.replace('.json', '-xray-debug.json');
|
|
86
|
+
fs.writeFileSync(debugPath, JSON.stringify(xrayJson, null, 2));
|
|
87
|
+
logger.info(` Debug: Xray JSON saved to ${path.basename(debugPath)}`);
|
|
88
|
+
}
|
|
89
|
+
logger.blank();
|
|
64
90
|
|
|
65
91
|
// Import to Xray
|
|
92
|
+
logger.send(`Uploading ${xrayJson.tests.length} test result(s) to Xray...`);
|
|
66
93
|
result = await importResultsXrayJson(cfg, xrayToken, xrayJson);
|
|
67
94
|
} else {
|
|
68
95
|
// JUnit XML format (default)
|
|
@@ -73,12 +100,46 @@ export default async function importResults(opts = {}) {
|
|
|
73
100
|
}
|
|
74
101
|
|
|
75
102
|
logger.success("Results imported successfully");
|
|
103
|
+
logger.blank();
|
|
76
104
|
|
|
105
|
+
// Display summary for JSON results
|
|
106
|
+
if (fileExt === '.json' && xrayJson) {
|
|
107
|
+
const summaryStats = calculateTestSummary(xrayJson.tests);
|
|
108
|
+
|
|
109
|
+
logger.info('Summary:');
|
|
110
|
+
logger.success(` ✓ Passed: ${summaryStats.passed}`)
|
|
111
|
+
;
|
|
112
|
+
if (summaryStats.failed > 0) {
|
|
113
|
+
logger.error(` ✗ Failed: ${summaryStats.failed}`);
|
|
114
|
+
}
|
|
115
|
+
if (summaryStats.skipped > 0) {
|
|
116
|
+
logger.warn(` ⊘ Skipped: ${summaryStats.skipped}`);
|
|
117
|
+
}
|
|
118
|
+
logger.blank();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Show link to Test Execution
|
|
77
122
|
if (result?.key) {
|
|
78
123
|
logger.link(`View: ${cfg.jiraUrl}/browse/${result.key}`);
|
|
79
124
|
} else if (result?.testExecIssue?.key) {
|
|
80
125
|
logger.link(`View: ${cfg.jiraUrl}/browse/${result.testExecIssue.key}`);
|
|
126
|
+
} else if (opts.testExecKey) {
|
|
127
|
+
logger.link(`View: ${cfg.jiraUrl}/browse/${opts.testExecKey}`);
|
|
81
128
|
}
|
|
82
129
|
|
|
83
130
|
logger.blank();
|
|
84
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Calculate test result summary
|
|
135
|
+
* @param {array} tests - Xray test results
|
|
136
|
+
* @returns {object} Summary stats
|
|
137
|
+
*/
|
|
138
|
+
function calculateTestSummary(tests) {
|
|
139
|
+
return {
|
|
140
|
+
passed: tests.filter(t => t.status === 'PASSED').length,
|
|
141
|
+
failed: tests.filter(t => t.status === 'FAILED').length,
|
|
142
|
+
skipped: tests.filter(t => t.status === 'SKIPPED').length,
|
|
143
|
+
total: tests.length,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -19,12 +19,16 @@
|
|
|
19
19
|
* @param {string} options.projectKey - JIRA project key for test creation
|
|
20
20
|
* @param {string} options.summary - Test Execution summary (optional)
|
|
21
21
|
* @param {string} options.description - Test Execution description (optional)
|
|
22
|
+
* @param {boolean} options.skipWithoutAnnotations - Skip tests without Xray annotations (default: false)
|
|
22
23
|
* @returns {object} Xray JSON format
|
|
23
24
|
*/
|
|
24
25
|
export function convertPlaywrightToXray(playwrightJson, options = {}) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
26
|
+
// Use current time as fallback for timestamps
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
const totalDuration = getTotalDuration(playwrightJson);
|
|
29
|
+
|
|
30
|
+
const startTime = new Date(now - totalDuration).toISOString();
|
|
31
|
+
const finishTime = new Date(now).toISOString();
|
|
28
32
|
|
|
29
33
|
const xrayJson = {
|
|
30
34
|
info: {
|
|
@@ -147,6 +151,11 @@ function convertTest(test, spec, suite, options) {
|
|
|
147
151
|
// Extract Xray test key from annotations or title
|
|
148
152
|
const testKey = extractTestKey(spec.title, test.annotations);
|
|
149
153
|
|
|
154
|
+
// Skip tests without annotations if requested
|
|
155
|
+
if (options.skipWithoutAnnotations && !testKey) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
150
159
|
// Map Playwright status to Xray status
|
|
151
160
|
const status = mapStatus(result.status);
|
|
152
161
|
|
|
@@ -179,10 +188,22 @@ function convertTest(test, spec, suite, options) {
|
|
|
179
188
|
|
|
180
189
|
// Add start/finish times if available
|
|
181
190
|
if (result.startTime) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
191
|
+
try {
|
|
192
|
+
const startDate = new Date(result.startTime);
|
|
193
|
+
if (!isNaN(startDate.getTime())) {
|
|
194
|
+
xrayTest.start = startDate.toISOString();
|
|
195
|
+
|
|
196
|
+
// Calculate finish time if we have duration
|
|
197
|
+
if (result.duration) {
|
|
198
|
+
const finishDate = new Date(startDate.getTime() + result.duration);
|
|
199
|
+
if (!isNaN(finishDate.getTime())) {
|
|
200
|
+
xrayTest.finish = finishDate.toISOString();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
// Skip invalid timestamps silently
|
|
206
|
+
}
|
|
186
207
|
}
|
|
187
208
|
|
|
188
209
|
// Add error details if test failed
|
package/lib/xrayClient.js
CHANGED
|
@@ -371,15 +371,51 @@ export async function importResults(cfg, xrayToken, xmlBuffer, testExecKey) {
|
|
|
371
371
|
export async function importResultsXrayJson(cfg, xrayToken, xrayJson) {
|
|
372
372
|
const url = "https://xray.cloud.getxray.app/api/v2/import/execution";
|
|
373
373
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
374
|
+
try {
|
|
375
|
+
const response = await axios.post(url, xrayJson, {
|
|
376
|
+
httpsAgent,
|
|
377
|
+
headers: {
|
|
378
|
+
Authorization: `Bearer ${xrayToken}`,
|
|
379
|
+
"Content-Type": "application/json",
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
return response.data;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
// Enhanced error reporting for Xray API issues
|
|
386
|
+
if (error.response) {
|
|
387
|
+
const status = error.response.status;
|
|
388
|
+
const data = error.response.data;
|
|
389
|
+
|
|
390
|
+
let errorMsg = `Xray API returned ${status} error`;
|
|
391
|
+
|
|
392
|
+
if (typeof data === 'string') {
|
|
393
|
+
errorMsg += `: ${data}`;
|
|
394
|
+
} else if (data?.error) {
|
|
395
|
+
errorMsg += `: ${data.error}`;
|
|
396
|
+
} else if (data?.message) {
|
|
397
|
+
errorMsg += `: ${data.message}`;
|
|
398
|
+
} else if (data) {
|
|
399
|
+
errorMsg += `: ${JSON.stringify(data)}`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
logger.error(errorMsg);
|
|
403
|
+
|
|
404
|
+
// Common issues help
|
|
405
|
+
if (status === 400) {
|
|
406
|
+
logger.warn('\n💡 Common causes of 400 errors:');
|
|
407
|
+
logger.warn(' • Missing test annotations in Playwright tests');
|
|
408
|
+
logger.warn(' • Invalid test key format (should be PROJ-123)');
|
|
409
|
+
logger.warn(' • Test execution key not found');
|
|
410
|
+
logger.warn(' • Malformed JSON structure');
|
|
411
|
+
logger.warn(' • Test keys reference non-existent tests\n');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
throw new Error(errorMsg);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
383
419
|
}
|
|
384
420
|
|
|
385
421
|
// ─── Retry wrapper ─────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@msalaam/xray-qe-toolkit",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"bin": {
|