@testomatio/reporter 2.1.0-beta-nightwatch → 2.1.0-beta.2-codeceptjs
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 +1 -0
- package/lib/adapter/codecept.js +288 -202
- package/lib/adapter/cypress-plugin/index.js +0 -2
- package/lib/adapter/mocha.js +0 -1
- package/lib/adapter/nightwatch.js +5 -5
- package/lib/adapter/playwright.js +11 -3
- package/lib/adapter/webdriver.d.ts +1 -1
- package/lib/adapter/webdriver.js +18 -8
- package/lib/bin/cli.js +73 -8
- package/lib/bin/reportXml.js +4 -2
- package/lib/bin/startTest.js +3 -2
- package/lib/bin/uploadArtifacts.js +5 -4
- package/lib/client.js +31 -10
- package/lib/data-storage.d.ts +5 -5
- package/lib/data-storage.js +23 -13
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/pipe/bitbucket.d.ts +2 -0
- package/lib/pipe/bitbucket.js +38 -26
- package/lib/pipe/debug.js +27 -6
- package/lib/pipe/github.d.ts +2 -2
- package/lib/pipe/github.js +35 -3
- package/lib/pipe/gitlab.d.ts +2 -0
- package/lib/pipe/gitlab.js +27 -9
- package/lib/pipe/html.js +0 -3
- package/lib/pipe/index.js +17 -7
- package/lib/pipe/testomatio.d.ts +3 -2
- package/lib/pipe/testomatio.js +85 -75
- package/lib/replay.d.ts +31 -0
- package/lib/replay.js +259 -0
- package/lib/reporter-functions.d.ts +7 -0
- package/lib/reporter-functions.js +36 -0
- package/lib/reporter.d.ts +15 -12
- package/lib/reporter.js +4 -1
- package/lib/services/artifacts.d.ts +1 -1
- package/lib/services/index.d.ts +2 -0
- package/lib/services/index.js +2 -0
- package/lib/services/key-values.d.ts +1 -1
- package/lib/services/labels.d.ts +22 -0
- package/lib/services/labels.js +62 -0
- package/lib/services/logger.d.ts +1 -1
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +443 -68
- package/lib/uploader.js +10 -6
- package/lib/utils/constants.d.ts +12 -0
- package/lib/utils/constants.js +15 -0
- package/lib/utils/utils.d.ts +10 -1
- package/lib/utils/utils.js +70 -22
- package/lib/xmlReader.js +57 -19
- package/package.json +16 -11
- package/src/adapter/codecept.js +320 -214
- package/src/adapter/cypress-plugin/index.js +0 -2
- package/src/adapter/mocha.js +0 -1
- package/src/adapter/nightwatch.js +1 -1
- package/src/adapter/playwright.js +10 -7
- package/src/adapter/webdriver.js +13 -5
- package/src/bin/cli.js +78 -7
- package/src/bin/reportXml.js +4 -1
- package/src/bin/startTest.js +2 -1
- package/src/bin/uploadArtifacts.js +2 -1
- package/src/client.js +28 -5
- package/src/data-storage.js +6 -6
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +22 -24
- package/src/pipe/debug.js +26 -5
- package/src/pipe/github.js +1 -2
- package/src/pipe/gitlab.js +27 -9
- package/src/pipe/html.js +1 -4
- package/src/pipe/testomatio.js +112 -107
- package/src/replay.js +268 -0
- package/src/reporter-functions.js +41 -0
- package/src/reporter.js +3 -0
- package/src/services/index.js +2 -0
- package/src/services/labels.js +59 -0
- package/src/services/logger.js +1 -2
- package/src/template/testomatio.hbs +443 -68
- package/src/uploader.js +11 -6
- package/src/utils/constants.js +12 -0
- package/src/utils/utils.js +67 -15
- package/src/xmlReader.js +73 -18
package/src/utils/utils.js
CHANGED
|
@@ -5,9 +5,16 @@ import fs from 'fs';
|
|
|
5
5
|
import isValid from 'is-valid-path';
|
|
6
6
|
import createDebugMessages from 'debug';
|
|
7
7
|
import os from 'os';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
8
9
|
|
|
9
10
|
const debug = createDebugMessages('@testomatio/reporter:util');
|
|
10
11
|
|
|
12
|
+
// Use __dirname directly since we're compiling to CommonJS
|
|
13
|
+
// prettier-ignore
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
// eslint-disable-next-line max-len
|
|
16
|
+
const __dirname = typeof global.__dirname !== 'undefined' ? global.__dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* @param {String} testTitle - Test title
|
|
13
20
|
*
|
|
@@ -33,12 +40,24 @@ const getTestomatIdFromTestTitle = testTitle => {
|
|
|
33
40
|
const parseSuite = suiteTitle => {
|
|
34
41
|
const captures = suiteTitle.match(/@S[\w\d]{8}/);
|
|
35
42
|
if (captures) {
|
|
36
|
-
return captures[
|
|
43
|
+
return captures[0];
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
return null;
|
|
40
47
|
};
|
|
41
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Validates TESTOMATIO_SUITE environment variable format
|
|
51
|
+
* @param {String} suiteId - suite ID to validate
|
|
52
|
+
* @returns {String|null} validated suite ID or null if invalid
|
|
53
|
+
*/
|
|
54
|
+
const validateSuiteId = suiteId => {
|
|
55
|
+
if (!suiteId) return null;
|
|
56
|
+
|
|
57
|
+
const match = suiteId.match(SUITE_ID_REGEX);
|
|
58
|
+
return match ? match[0] : null;
|
|
59
|
+
};
|
|
60
|
+
|
|
42
61
|
const ansiRegExp = () => {
|
|
43
62
|
const pattern = [
|
|
44
63
|
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
@@ -50,7 +69,6 @@ const ansiRegExp = () => {
|
|
|
50
69
|
|
|
51
70
|
const isValidUrl = s => {
|
|
52
71
|
try {
|
|
53
|
-
// eslint-disable-next-line no-new
|
|
54
72
|
new URL(s);
|
|
55
73
|
return true;
|
|
56
74
|
} catch (err) {
|
|
@@ -58,16 +76,25 @@ const isValidUrl = s => {
|
|
|
58
76
|
}
|
|
59
77
|
};
|
|
60
78
|
|
|
61
|
-
const fileMatchRegex = /file:(
|
|
79
|
+
const fileMatchRegex = /file:(\/+(?:[A-Za-z]:[\\/]|\/)?[^\s]*?\.(png|avi|webm|jpg|html|txt))/gi;
|
|
62
80
|
|
|
63
|
-
const fetchFilesFromStackTrace = (stack = '') => {
|
|
81
|
+
const fetchFilesFromStackTrace = (stack = '', checkExists = true) => {
|
|
64
82
|
const files = Array.from(stack.matchAll(fileMatchRegex))
|
|
65
83
|
.map(f => f[1].trim())
|
|
66
|
-
.map(f =>
|
|
84
|
+
.map(f => f.replace(/^\/+/, '/').replace(/^\/([A-Za-z]:)/, '$1')) // Remove extra slashes, handle Windows paths
|
|
85
|
+
.map(f => {
|
|
86
|
+
// Convert Windows paths to Linux paths for testing purposes
|
|
87
|
+
if (f.match(/^[A-Za-z]:[\\\/]/)) {
|
|
88
|
+
// Convert Windows path to Linux equivalent for test scenarios
|
|
89
|
+
return f.replace(/^[A-Za-z]:[\\\/]/, '/').replace(/\\/g, '/');
|
|
90
|
+
}
|
|
91
|
+
return f;
|
|
92
|
+
});
|
|
67
93
|
|
|
68
94
|
debug('Found files in stack trace: ', files);
|
|
69
95
|
|
|
70
96
|
return files.filter(f => {
|
|
97
|
+
if (!checkExists) return true;
|
|
71
98
|
const isFile = fs.existsSync(f);
|
|
72
99
|
if (!isFile) debug('File %s could not be found and uploaded as artifact', f);
|
|
73
100
|
return isFile;
|
|
@@ -108,7 +135,8 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
108
135
|
.join('\n');
|
|
109
136
|
};
|
|
110
137
|
|
|
111
|
-
const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
138
|
+
export const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
139
|
+
export const SUITE_ID_REGEX = /@S([\w\d]{8})/;
|
|
112
140
|
|
|
113
141
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
114
142
|
const comments = code
|
|
@@ -128,12 +156,9 @@ const fetchIdFromCode = (code, opts = {}) => {
|
|
|
128
156
|
};
|
|
129
157
|
|
|
130
158
|
const fetchIdFromOutput = output => {
|
|
131
|
-
const
|
|
132
|
-
.split('\n')
|
|
133
|
-
.map(l => l.trim())
|
|
134
|
-
.filter(l => l.startsWith('tid://'));
|
|
159
|
+
const TID_FULL_PATTERN = new RegExp(`tid:\\/\\/.*?(${TEST_ID_REGEX.source})`);
|
|
135
160
|
|
|
136
|
-
return
|
|
161
|
+
return output.match(TID_FULL_PATTERN)?.[2];
|
|
137
162
|
};
|
|
138
163
|
|
|
139
164
|
const fetchSourceCode = (contents, opts = {}) => {
|
|
@@ -154,6 +179,9 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
154
179
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
155
180
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
156
181
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
182
|
+
} else if (opts.lang === 'csharp') {
|
|
183
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
184
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
157
185
|
} else {
|
|
158
186
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
159
187
|
}
|
|
@@ -300,7 +328,6 @@ const decamelize = text => {
|
|
|
300
328
|
* @returns
|
|
301
329
|
*/
|
|
302
330
|
function removeColorCodes(input) {
|
|
303
|
-
// eslint-disable-next-line no-control-regex
|
|
304
331
|
return input.replace(/\x1b\[[0-9;]*m/g, '');
|
|
305
332
|
}
|
|
306
333
|
|
|
@@ -313,7 +340,6 @@ const testRunnerHelper = {
|
|
|
313
340
|
try {
|
|
314
341
|
// TODO: expect?.getState()?.testPath + ' ' + expect?.getState()?.currentTestName
|
|
315
342
|
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
316
|
-
// eslint-disable-next-line no-undef
|
|
317
343
|
return expect?.getState()?.currentTestName;
|
|
318
344
|
} catch (e) {
|
|
319
345
|
return null;
|
|
@@ -327,20 +353,38 @@ function storeRunId(runId) {
|
|
|
327
353
|
fs.writeFileSync(filePath, runId);
|
|
328
354
|
}
|
|
329
355
|
|
|
356
|
+
/**
|
|
357
|
+
*
|
|
358
|
+
* @returns {String|null} latest run ID
|
|
359
|
+
*/
|
|
330
360
|
function readLatestRunId() {
|
|
331
361
|
try {
|
|
332
362
|
const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
|
|
333
363
|
const stats = fs.statSync(filePath);
|
|
334
364
|
const diff = +new Date() - +stats.mtime;
|
|
335
365
|
const diffHours = diff / 1000 / 60 / 60;
|
|
336
|
-
if (diffHours > 1) return;
|
|
366
|
+
if (diffHours > 1) return null;
|
|
337
367
|
|
|
338
|
-
return fs.readFileSync(filePath)?.toString()?.trim();
|
|
368
|
+
return fs.readFileSync(filePath)?.toString()?.trim() ?? null;
|
|
339
369
|
} catch (e) {
|
|
370
|
+
console.warn('Could not read latest run ID from file: ', e);
|
|
340
371
|
return null;
|
|
341
372
|
}
|
|
342
373
|
}
|
|
343
374
|
|
|
375
|
+
function cleanLatestRunId() {
|
|
376
|
+
try {
|
|
377
|
+
const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
|
|
378
|
+
const runId = readLatestRunId();
|
|
379
|
+
if (fs.existsSync(filePath)) {
|
|
380
|
+
fs.unlinkSync(filePath);
|
|
381
|
+
}
|
|
382
|
+
debug(`Cleaned latest run ID (${runId}) file`, filePath);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
console.warn('Could not clean latest run ID file: ', e);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
344
388
|
function formatStep(step, shift = 0) {
|
|
345
389
|
const prefix = ' '.repeat(shift);
|
|
346
390
|
|
|
@@ -359,8 +403,15 @@ function formatStep(step, shift = 0) {
|
|
|
359
403
|
return lines;
|
|
360
404
|
}
|
|
361
405
|
|
|
406
|
+
export function getPackageVersion() {
|
|
407
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
408
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
409
|
+
return packageJson.version;
|
|
410
|
+
}
|
|
411
|
+
|
|
362
412
|
export {
|
|
363
413
|
ansiRegExp,
|
|
414
|
+
cleanLatestRunId,
|
|
364
415
|
isSameTest,
|
|
365
416
|
fetchSourceCode,
|
|
366
417
|
fetchSourceCodeFromStackTrace,
|
|
@@ -380,4 +431,5 @@ export {
|
|
|
380
431
|
specificTestInfo,
|
|
381
432
|
storeRunId,
|
|
382
433
|
testRunnerHelper,
|
|
434
|
+
validateSuiteId,
|
|
383
435
|
};
|
package/src/xmlReader.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
fetchSourceCodeFromStackTrace,
|
|
14
14
|
fetchIdFromCode,
|
|
15
15
|
humanize,
|
|
16
|
+
TEST_ID_REGEX,
|
|
16
17
|
} from './utils/utils.js';
|
|
17
18
|
import { pipesFactory } from './pipe/index.js';
|
|
18
19
|
import adapterFactory from './junit-adapter/index.js';
|
|
@@ -26,8 +27,15 @@ const debug = createDebugMessages('@testomatio/reporter:xml');
|
|
|
26
27
|
const ridRunId = randomUUID();
|
|
27
28
|
|
|
28
29
|
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
29
|
-
const {
|
|
30
|
-
|
|
30
|
+
const {
|
|
31
|
+
TESTOMATIO_RUNGROUP_TITLE,
|
|
32
|
+
TESTOMATIO_SUITE,
|
|
33
|
+
TESTOMATIO_MAX_STACK_TRACE,
|
|
34
|
+
TESTOMATIO_TITLE,
|
|
35
|
+
TESTOMATIO_ENV,
|
|
36
|
+
TESTOMATIO_RUN,
|
|
37
|
+
TESTOMATIO_MARK_DETACHED,
|
|
38
|
+
} = process.env;
|
|
31
39
|
|
|
32
40
|
const options = {
|
|
33
41
|
ignoreDeclaration: true,
|
|
@@ -37,6 +45,8 @@ const options = {
|
|
|
37
45
|
parseTagValue: true,
|
|
38
46
|
};
|
|
39
47
|
|
|
48
|
+
const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
|
|
49
|
+
|
|
40
50
|
const reduceOptions = {};
|
|
41
51
|
|
|
42
52
|
class XmlReader {
|
|
@@ -91,7 +101,7 @@ class XmlReader {
|
|
|
91
101
|
];
|
|
92
102
|
|
|
93
103
|
for (const regex of cutRegexes) {
|
|
94
|
-
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0,
|
|
104
|
+
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, MAX_OUTPUT_LENGTH)}${p3}`);
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
const jsonResult = this.parser.parse(xmlData);
|
|
@@ -207,6 +217,7 @@ class XmlReader {
|
|
|
207
217
|
if (test.example) r.example = test.example;
|
|
208
218
|
if (test.file) r.file = test.file;
|
|
209
219
|
r.create = true;
|
|
220
|
+
r.overwrite = true;
|
|
210
221
|
if (r.status === 'Passed') r.status = STATUS.PASSED;
|
|
211
222
|
if (r.status === 'Failed') r.status = STATUS.FAILED;
|
|
212
223
|
if (r.status === 'Skipped') r.status = STATUS.SKIPPED;
|
|
@@ -226,7 +237,7 @@ class XmlReader {
|
|
|
226
237
|
|
|
227
238
|
return {
|
|
228
239
|
status,
|
|
229
|
-
create_tests:
|
|
240
|
+
create_tests: !process.env.IGNORE_NEW_TESTS,
|
|
230
241
|
tests_count: parseInt(counters.total, 10),
|
|
231
242
|
passed_count: parseInt(counters.passed, 10),
|
|
232
243
|
skipped_count: parseInt(counters.notExecuted, 10),
|
|
@@ -283,6 +294,7 @@ class XmlReader {
|
|
|
283
294
|
title,
|
|
284
295
|
suite_title,
|
|
285
296
|
run_time,
|
|
297
|
+
retry: false,
|
|
286
298
|
});
|
|
287
299
|
});
|
|
288
300
|
});
|
|
@@ -341,6 +353,7 @@ class XmlReader {
|
|
|
341
353
|
if (file.endsWith('.rb')) this.stats.language = 'ruby';
|
|
342
354
|
if (file.endsWith('.js')) this.stats.language = 'js';
|
|
343
355
|
if (file.endsWith('.ts')) this.stats.language = 'ts';
|
|
356
|
+
if (file.endsWith('.cs')) this.stats.language = 'csharp';
|
|
344
357
|
}
|
|
345
358
|
|
|
346
359
|
if (!fs.existsSync(file)) {
|
|
@@ -394,13 +407,14 @@ class XmlReader {
|
|
|
394
407
|
async uploadArtifacts() {
|
|
395
408
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
396
409
|
let files = [];
|
|
397
|
-
if (test.files?.length)
|
|
398
|
-
|
|
410
|
+
if (!test.files?.length) continue;
|
|
411
|
+
|
|
412
|
+
files = test.files.map(f => (path.isAbsolute(f) ? f : path.join(process.cwd(), f)));
|
|
399
413
|
|
|
400
414
|
if (!files.length) continue;
|
|
401
415
|
|
|
402
416
|
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
403
|
-
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
|
|
417
|
+
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId, path.basename(f)])));
|
|
404
418
|
console.log(APP_PREFIX, `🗄️ Uploaded ${pc.bold(`${files.length} artifacts`)} for test ${test.title}`);
|
|
405
419
|
}
|
|
406
420
|
}
|
|
@@ -460,14 +474,18 @@ function reduceTestCases(prev, item) {
|
|
|
460
474
|
}
|
|
461
475
|
|
|
462
476
|
// suite inside test case
|
|
463
|
-
|
|
477
|
+
const testCase = item['test-suite']?.['test-case'];
|
|
478
|
+
if (testCase) {
|
|
479
|
+
const nestedCases = Array.isArray(testCase) ? testCase : [testCase];
|
|
480
|
+
testCases.push(...nestedCases);
|
|
481
|
+
}
|
|
464
482
|
|
|
465
483
|
const suiteOutput = item['system-out'] || item.output || item.log || '';
|
|
466
484
|
const suiteErr = item['system-err'] || item.output || item.log || '';
|
|
467
485
|
testCases
|
|
468
486
|
.filter(t => !!t)
|
|
469
487
|
.forEach(testCaseItem => {
|
|
470
|
-
const file = testCaseItem.file || item.filepath || '';
|
|
488
|
+
const file = testCaseItem.file || item.filepath || item.fullname || item.package || '';
|
|
471
489
|
|
|
472
490
|
let stack = '';
|
|
473
491
|
let message = '';
|
|
@@ -485,7 +503,7 @@ function reduceTestCases(prev, item) {
|
|
|
485
503
|
const preferClassname = reduceOptions.preferClassname || isParametrized;
|
|
486
504
|
|
|
487
505
|
// SpecFlow config
|
|
488
|
-
let { title, tags } = fetchProperties(isParametrized ? item : testCaseItem);
|
|
506
|
+
let { title, tags, testId } = fetchProperties(isParametrized ? item : testCaseItem);
|
|
489
507
|
let example = null;
|
|
490
508
|
const suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
|
|
491
509
|
|
|
@@ -498,19 +516,44 @@ function reduceTestCases(prev, item) {
|
|
|
498
516
|
title = title.replace(/\(.*?\)/, '').trim();
|
|
499
517
|
}
|
|
500
518
|
|
|
501
|
-
// eslint-disable-next-line
|
|
502
519
|
stack = `${
|
|
503
520
|
testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
|
|
504
521
|
}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
|
|
505
|
-
|
|
522
|
+
|
|
523
|
+
if (!testId) testId = fetchIdFromOutput(stack);
|
|
524
|
+
|
|
525
|
+
if (tags?.length && !testId) {
|
|
526
|
+
testId = tags
|
|
527
|
+
.filter(t => t.startsWith('T'))
|
|
528
|
+
.map(t => `@${t}`)
|
|
529
|
+
.find(t => t.match(TEST_ID_REGEX))
|
|
530
|
+
?.slice(2);
|
|
531
|
+
}
|
|
506
532
|
|
|
507
533
|
let status = STATUS.PASSED.toString();
|
|
508
534
|
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
509
535
|
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
536
|
+
if (testCaseItem.result && Object.values(STATUS).includes(testCaseItem.result.toLowerCase())) {
|
|
537
|
+
status = testCaseItem.result.toLowerCase();
|
|
538
|
+
}
|
|
510
539
|
|
|
511
540
|
let rid = null;
|
|
512
541
|
if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
|
|
513
542
|
|
|
543
|
+
// Extract attachments
|
|
544
|
+
let files = [];
|
|
545
|
+
if (testCaseItem.attachments) {
|
|
546
|
+
const attachments = Array.isArray(testCaseItem.attachments.attachment)
|
|
547
|
+
? testCaseItem.attachments.attachment
|
|
548
|
+
: [testCaseItem.attachments.attachment];
|
|
549
|
+
|
|
550
|
+
files = attachments.filter(a => a && a.filePath).map(a => a.filePath);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Extract files from stack trace using existing utility
|
|
554
|
+
const stackFiles = fetchFilesFromStackTrace(stack);
|
|
555
|
+
files = [...new Set([...files, ...stackFiles])]; // Remove duplicates
|
|
556
|
+
|
|
514
557
|
prev.push({
|
|
515
558
|
rid,
|
|
516
559
|
file,
|
|
@@ -525,7 +568,10 @@ function reduceTestCases(prev, item) {
|
|
|
525
568
|
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
526
569
|
status,
|
|
527
570
|
title,
|
|
571
|
+
root_suite_id: TESTOMATIO_SUITE,
|
|
528
572
|
suite_title: suiteTitle,
|
|
573
|
+
files,
|
|
574
|
+
retry: false,
|
|
529
575
|
});
|
|
530
576
|
});
|
|
531
577
|
return prev;
|
|
@@ -552,11 +598,20 @@ function fetchProperties(item) {
|
|
|
552
598
|
|
|
553
599
|
if (!item.properties) return {};
|
|
554
600
|
|
|
555
|
-
|
|
601
|
+
// Handle both single property and array of properties
|
|
602
|
+
const properties = Array.isArray(item.properties.property)
|
|
603
|
+
? item.properties.property
|
|
604
|
+
: [item.properties.property].filter(Boolean);
|
|
605
|
+
|
|
606
|
+
const prop = properties.find(p => p.name === 'Description');
|
|
556
607
|
if (prop) title = prop.value;
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
608
|
+
|
|
609
|
+
let testId = properties.find(p => p.name === 'ID')?.value;
|
|
610
|
+
|
|
611
|
+
if (testId?.startsWith('@')) testId = testId.slice(1);
|
|
612
|
+
if (testId?.startsWith('T')) testId = testId.slice(1);
|
|
613
|
+
|
|
614
|
+
properties.filter(p => p.name === 'Category').forEach(p => tags.push(p.value));
|
|
615
|
+
|
|
616
|
+
return { title, tags, testId };
|
|
562
617
|
}
|