@testomatio/reporter 2.0.0-beta.4-gaxios → 2.0.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/lib/adapter/nightwatch.js +5 -5
- package/lib/adapter/webdriver.d.ts +1 -1
- package/lib/bin/cli.js +7 -6
- package/lib/bin/reportXml.js +4 -2
- package/lib/bin/startTest.js +3 -2
- package/lib/bin/uploadArtifacts.js +5 -4
- package/lib/data-storage.d.ts +1 -1
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/pipe/bitbucket.d.ts +0 -2
- package/lib/pipe/bitbucket.js +19 -21
- package/lib/pipe/gitlab.d.ts +0 -2
- package/lib/pipe/gitlab.js +8 -27
- package/lib/pipe/testomatio.d.ts +1 -2
- package/lib/pipe/testomatio.js +65 -75
- package/lib/reporter.d.ts +12 -12
- package/lib/services/artifacts.d.ts +1 -1
- package/lib/services/key-values.d.ts +1 -1
- package/lib/services/logger.d.ts +1 -1
- package/lib/utils/utils.d.ts +2 -0
- package/lib/utils/utils.js +20 -4
- package/lib/xmlReader.js +38 -11
- package/package.json +6 -5
- package/src/adapter/nightwatch.js +1 -1
- package/src/adapter/webdriver.js +1 -1
- package/src/bin/cli.js +2 -1
- package/src/bin/reportXml.js +4 -1
- package/src/bin/startTest.js +2 -1
- package/src/bin/uploadArtifacts.js +2 -1
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +24 -22
- package/src/pipe/gitlab.js +8 -27
- package/src/pipe/testomatio.js +95 -95
- package/src/utils/utils.js +14 -1
- package/src/xmlReader.js +47 -11
package/src/pipe/testomatio.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import createDebugMessages from 'debug';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
// Retry interceptor function
|
|
5
|
+
import axiosRetry from 'axios-retry';
|
|
6
|
+
|
|
7
|
+
// Default axios instance
|
|
8
|
+
import axios from 'axios';
|
|
9
|
+
|
|
4
10
|
import JsonCycle from 'json-cycle';
|
|
5
11
|
import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
|
|
6
12
|
import { isValidUrl, foundedTestLog } from '../utils/utils.js';
|
|
@@ -51,31 +57,43 @@ class TestomatioPipe {
|
|
|
51
57
|
this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
|
|
52
58
|
this.env = process.env.TESTOMATIO_ENV;
|
|
53
59
|
this.label = process.env.TESTOMATIO_LABEL;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
this.client = new Gaxios({
|
|
60
|
+
// Create a new instance of axios with a custom config
|
|
61
|
+
this.axios = axios.create({
|
|
57
62
|
baseURL: `${this.url.trim()}`,
|
|
58
63
|
timeout: AXIOS_TIMEOUT,
|
|
59
|
-
proxy: proxy
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
httpMethodsToRetry: ['GET','PUT','HEAD','OPTIONS','DELETE','POST'],
|
|
65
|
-
shouldRetry: (error) => {
|
|
66
|
-
if (!error.response) return false;
|
|
67
|
-
switch (error.response?.status) {
|
|
68
|
-
case 400: // Bad request (probably wrong API key)
|
|
69
|
-
case 404: // Test not matched
|
|
70
|
-
case 429: // Rate limit exceeded
|
|
71
|
-
case 500: // Internal server error
|
|
72
|
-
return false;
|
|
73
|
-
default:
|
|
74
|
-
break;
|
|
64
|
+
proxy: proxy
|
|
65
|
+
? {
|
|
66
|
+
host: proxy.hostname,
|
|
67
|
+
port: parseInt(proxy.port, 10),
|
|
68
|
+
protocol: proxy.protocol,
|
|
75
69
|
}
|
|
76
|
-
|
|
70
|
+
: false,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Pass the axios instance to the retry function
|
|
74
|
+
axiosRetry(this.axios, {
|
|
75
|
+
// do not use retries for unit tests
|
|
76
|
+
retries: REPORTER_REQUEST_RETRIES.retriesPerRequest, // Number of retries
|
|
77
|
+
shouldResetTimeout: true,
|
|
78
|
+
retryCondition: error => {
|
|
79
|
+
if (!error.response) return false;
|
|
80
|
+
switch (error.response?.status) {
|
|
81
|
+
case 400: // Bad request (probably wrong API key)
|
|
82
|
+
case 404: // Test not matched
|
|
83
|
+
case 429: // Rate limit exceeded
|
|
84
|
+
case 500: // Internal server error
|
|
85
|
+
return false;
|
|
86
|
+
default:
|
|
87
|
+
break;
|
|
77
88
|
}
|
|
78
|
-
|
|
89
|
+
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
90
|
+
},
|
|
91
|
+
retryDelay: () => REPORTER_REQUEST_RETRIES.retryTimeout, // sum = 15sec
|
|
92
|
+
onRetry: async (retryCount, error) => {
|
|
93
|
+
this.retriesTimestamps.push(Date.now());
|
|
94
|
+
|
|
95
|
+
debug(`${error.message || `Request failed ${error.status}`}. Retry #${retryCount} ...`);
|
|
96
|
+
},
|
|
79
97
|
});
|
|
80
98
|
|
|
81
99
|
this.isEnabled = true;
|
|
@@ -116,15 +134,12 @@ class TestomatioPipe {
|
|
|
116
134
|
return;
|
|
117
135
|
}
|
|
118
136
|
|
|
119
|
-
const resp = await this.
|
|
120
|
-
|
|
121
|
-
url: '/api/test_grep',
|
|
122
|
-
params: q
|
|
123
|
-
});
|
|
137
|
+
const resp = await this.axios.get('/api/test_grep', q);
|
|
138
|
+
const { data } = resp;
|
|
124
139
|
|
|
125
|
-
if (Array.isArray(
|
|
126
|
-
foundedTestLog(APP_PREFIX,
|
|
127
|
-
return
|
|
140
|
+
if (Array.isArray(data?.tests) && data?.tests?.length > 0) {
|
|
141
|
+
foundedTestLog(APP_PREFIX, data.tests);
|
|
142
|
+
return data.tests;
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
|
|
@@ -148,6 +163,7 @@ class TestomatioPipe {
|
|
|
148
163
|
|
|
149
164
|
// GitHub Actions Url
|
|
150
165
|
if (!buildUrl && process.env.GITHUB_RUN_ID) {
|
|
166
|
+
// eslint-disable-next-line max-len
|
|
151
167
|
buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
|
152
168
|
}
|
|
153
169
|
|
|
@@ -183,23 +199,16 @@ class TestomatioPipe {
|
|
|
183
199
|
if (this.runId) {
|
|
184
200
|
this.store.runId = this.runId;
|
|
185
201
|
debug(`Run with id ${this.runId} already created, updating...`);
|
|
186
|
-
const resp = await this.
|
|
187
|
-
method: 'PUT',
|
|
188
|
-
url: `/api/reporter/${this.runId}`,
|
|
189
|
-
data: runParams
|
|
190
|
-
});
|
|
202
|
+
const resp = await this.axios.put(`/api/reporter/${this.runId}`, runParams);
|
|
191
203
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
192
204
|
return;
|
|
193
205
|
}
|
|
194
206
|
|
|
195
207
|
debug('Creating run...');
|
|
196
208
|
try {
|
|
197
|
-
const resp = await this.
|
|
198
|
-
method: 'POST',
|
|
199
|
-
url: '/api/reporter',
|
|
200
|
-
data: runParams,
|
|
209
|
+
const resp = await this.axios.post(`/api/reporter`, runParams, {
|
|
201
210
|
maxContentLength: Infinity,
|
|
202
|
-
|
|
211
|
+
maxBodyLength: Infinity,
|
|
203
212
|
});
|
|
204
213
|
|
|
205
214
|
this.runId = resp.data.uid;
|
|
@@ -216,7 +225,6 @@ class TestomatioPipe {
|
|
|
216
225
|
debug('Run created', this.runId);
|
|
217
226
|
} catch (err) {
|
|
218
227
|
const errorText = err.response?.data?.message || err.message;
|
|
219
|
-
debug('Error creating run', err);
|
|
220
228
|
console.log(errorText || err);
|
|
221
229
|
if (!this.apiKey) console.error('Testomat.io API key is not set');
|
|
222
230
|
if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
|
|
@@ -263,15 +271,7 @@ class TestomatioPipe {
|
|
|
263
271
|
|
|
264
272
|
debug('Adding test', json);
|
|
265
273
|
|
|
266
|
-
return this.
|
|
267
|
-
method: 'POST',
|
|
268
|
-
url: `/api/reporter/${this.runId}/testrun`,
|
|
269
|
-
data: json,
|
|
270
|
-
headers: {
|
|
271
|
-
'Content-Type': 'application/json',
|
|
272
|
-
},
|
|
273
|
-
maxContentLength: Infinity
|
|
274
|
-
}).catch(err => {
|
|
274
|
+
return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, axiosAddTestrunRequestConfig).catch(err => {
|
|
275
275
|
this.requestFailures++;
|
|
276
276
|
this.notReportedTestsCount++;
|
|
277
277
|
if (err.response) {
|
|
@@ -323,43 +323,38 @@ class TestomatioPipe {
|
|
|
323
323
|
const testsToSend = this.batch.tests.splice(0);
|
|
324
324
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
325
325
|
|
|
326
|
-
return this.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
326
|
+
return this.axios
|
|
327
|
+
.post(
|
|
328
|
+
`/api/reporter/${this.runId}/testrun`,
|
|
329
|
+
{ api_key: this.apiKey, tests: testsToSend, batch_index: this.batch.batchIndex },
|
|
330
|
+
axiosAddTestrunRequestConfig,
|
|
331
|
+
)
|
|
332
|
+
.catch(err => {
|
|
333
|
+
this.requestFailures++;
|
|
334
|
+
this.notReportedTestsCount += testsToSend.length;
|
|
335
|
+
if (err.response) {
|
|
336
|
+
if (err.response.status >= 400) {
|
|
337
|
+
const responseData = err.response.data || { message: '' };
|
|
338
|
+
console.log(
|
|
339
|
+
APP_PREFIX,
|
|
340
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
341
|
+
// pc.grey(data?.title || ''),
|
|
342
|
+
);
|
|
343
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
344
|
+
this.hasUnmatchedTests = true;
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
344
348
|
console.log(
|
|
345
349
|
APP_PREFIX,
|
|
346
|
-
pc.yellow(`Warning:
|
|
350
|
+
pc.yellow(`Warning: (${err.response?.status})`),
|
|
351
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
347
352
|
);
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return;
|
|
353
|
+
printCreateIssue(err);
|
|
354
|
+
} else {
|
|
355
|
+
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
352
356
|
}
|
|
353
|
-
|
|
354
|
-
APP_PREFIX,
|
|
355
|
-
pc.yellow(`Warning: (${err.response?.status})`),
|
|
356
|
-
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
357
|
-
);
|
|
358
|
-
printCreateIssue(err);
|
|
359
|
-
} else {
|
|
360
|
-
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
361
|
-
}
|
|
362
|
-
});
|
|
357
|
+
});
|
|
363
358
|
};
|
|
364
359
|
|
|
365
360
|
/**
|
|
@@ -418,16 +413,12 @@ class TestomatioPipe {
|
|
|
418
413
|
|
|
419
414
|
try {
|
|
420
415
|
if (this.runId && !this.proceed) {
|
|
421
|
-
await this.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
status_event,
|
|
428
|
-
detach: params.detach,
|
|
429
|
-
tests: params.tests,
|
|
430
|
-
}
|
|
416
|
+
await this.axios.put(`/api/reporter/${this.runId}`, {
|
|
417
|
+
api_key: this.apiKey,
|
|
418
|
+
duration: params.duration,
|
|
419
|
+
status_event,
|
|
420
|
+
detach: params.detach,
|
|
421
|
+
tests: params.tests,
|
|
431
422
|
});
|
|
432
423
|
if (this.runUrl) {
|
|
433
424
|
console.log(APP_PREFIX, '📊 Report Saved. Report URL:', pc.magenta(this.runUrl));
|
|
@@ -487,11 +478,20 @@ function printCreateIssue(err) {
|
|
|
487
478
|
if (!err.config) return;
|
|
488
479
|
|
|
489
480
|
const time = new Date().toUTCString();
|
|
490
|
-
const {
|
|
481
|
+
const { data, url, baseURL, method } = err?.config || {};
|
|
491
482
|
console.log('```js');
|
|
492
|
-
console.log({
|
|
483
|
+
console.log({ data: data?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
|
|
493
484
|
console.log('```');
|
|
494
485
|
});
|
|
495
486
|
}
|
|
496
487
|
|
|
488
|
+
const axiosAddTestrunRequestConfig = {
|
|
489
|
+
maxContentLength: Infinity,
|
|
490
|
+
maxBodyLength: Infinity,
|
|
491
|
+
headers: {
|
|
492
|
+
// Overwrite Axios's automatically set Content-Type
|
|
493
|
+
'Content-Type': 'application/json',
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
|
|
497
497
|
export default TestomatioPipe;
|
package/src/utils/utils.js
CHANGED
|
@@ -5,9 +5,13 @@ 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
|
+
const __dirname = path.resolve();
|
|
14
|
+
|
|
11
15
|
/**
|
|
12
16
|
* @param {String} testTitle - Test title
|
|
13
17
|
*
|
|
@@ -107,7 +111,7 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
107
111
|
.join('\n');
|
|
108
112
|
};
|
|
109
113
|
|
|
110
|
-
const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
114
|
+
export const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
111
115
|
|
|
112
116
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
113
117
|
const comments = code
|
|
@@ -150,6 +154,9 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
150
154
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
151
155
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
152
156
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
157
|
+
} else if (opts.lang === 'csharp') {
|
|
158
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
159
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
153
160
|
} else {
|
|
154
161
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
155
162
|
}
|
|
@@ -353,6 +360,12 @@ function formatStep(step, shift = 0) {
|
|
|
353
360
|
return lines;
|
|
354
361
|
}
|
|
355
362
|
|
|
363
|
+
export function getPackageVersion() {
|
|
364
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
365
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
366
|
+
return packageJson.version;
|
|
367
|
+
}
|
|
368
|
+
|
|
356
369
|
export {
|
|
357
370
|
ansiRegExp,
|
|
358
371
|
isSameTest,
|
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,9 @@ 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 { TESTOMATIO_RUNGROUP_TITLE,
|
|
30
|
-
|
|
30
|
+
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE,
|
|
31
|
+
TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV,
|
|
32
|
+
TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } = process.env;
|
|
31
33
|
|
|
32
34
|
const options = {
|
|
33
35
|
ignoreDeclaration: true,
|
|
@@ -37,6 +39,8 @@ const options = {
|
|
|
37
39
|
parseTagValue: true,
|
|
38
40
|
};
|
|
39
41
|
|
|
42
|
+
const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
|
|
43
|
+
|
|
40
44
|
const reduceOptions = {};
|
|
41
45
|
|
|
42
46
|
class XmlReader {
|
|
@@ -91,7 +95,7 @@ class XmlReader {
|
|
|
91
95
|
];
|
|
92
96
|
|
|
93
97
|
for (const regex of cutRegexes) {
|
|
94
|
-
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0,
|
|
98
|
+
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, MAX_OUTPUT_LENGTH)}${p3}`);
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
const jsonResult = this.parser.parse(xmlData);
|
|
@@ -341,6 +345,7 @@ class XmlReader {
|
|
|
341
345
|
if (file.endsWith('.rb')) this.stats.language = 'ruby';
|
|
342
346
|
if (file.endsWith('.js')) this.stats.language = 'js';
|
|
343
347
|
if (file.endsWith('.ts')) this.stats.language = 'ts';
|
|
348
|
+
if (file.endsWith('.cs')) this.stats.language = 'csharp';
|
|
344
349
|
}
|
|
345
350
|
|
|
346
351
|
if (!fs.existsSync(file)) {
|
|
@@ -394,13 +399,14 @@ class XmlReader {
|
|
|
394
399
|
async uploadArtifacts() {
|
|
395
400
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
396
401
|
let files = [];
|
|
397
|
-
if (test.files?.length)
|
|
398
|
-
|
|
402
|
+
if (!test.files?.length) continue;
|
|
403
|
+
|
|
404
|
+
files = test.files.map(f => path.isAbsolute(f) ? f : path.join(process.cwd(), f));
|
|
399
405
|
|
|
400
406
|
if (!files.length) continue;
|
|
401
407
|
|
|
402
408
|
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
403
|
-
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
|
|
409
|
+
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId, path.basename(f)])));
|
|
404
410
|
console.log(APP_PREFIX, `🗄️ Uploaded ${pc.bold(`${files.length} artifacts`)} for test ${test.title}`);
|
|
405
411
|
}
|
|
406
412
|
}
|
|
@@ -471,7 +477,7 @@ function reduceTestCases(prev, item) {
|
|
|
471
477
|
testCases
|
|
472
478
|
.filter(t => !!t)
|
|
473
479
|
.forEach(testCaseItem => {
|
|
474
|
-
const file = testCaseItem.file || item.filepath || '';
|
|
480
|
+
const file = testCaseItem.file || item.filepath || item.fullname || '';
|
|
475
481
|
|
|
476
482
|
let stack = '';
|
|
477
483
|
let message = '';
|
|
@@ -505,15 +511,38 @@ function reduceTestCases(prev, item) {
|
|
|
505
511
|
stack = `${
|
|
506
512
|
testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
|
|
507
513
|
}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
|
|
508
|
-
|
|
514
|
+
let testId = fetchIdFromOutput(stack);
|
|
515
|
+
|
|
516
|
+
if (tags?.length && !testId) {
|
|
517
|
+
testId = tags.filter(t => t.startsWith('T')).map(t => `@${t}`).find(t => t.match(TEST_ID_REGEX))?.slice(2);
|
|
518
|
+
}
|
|
509
519
|
|
|
510
520
|
let status = STATUS.PASSED.toString();
|
|
511
521
|
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
512
522
|
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
523
|
+
if (testCaseItem.result && Object.values(STATUS).includes(testCaseItem.result.toLowerCase())) {
|
|
524
|
+
status = testCaseItem.result.toLowerCase();
|
|
525
|
+
}
|
|
513
526
|
|
|
514
527
|
let rid = null;
|
|
515
528
|
if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
|
|
516
529
|
|
|
530
|
+
// Extract attachments
|
|
531
|
+
let files = [];
|
|
532
|
+
if (testCaseItem.attachments) {
|
|
533
|
+
const attachments = Array.isArray(testCaseItem.attachments.attachment)
|
|
534
|
+
? testCaseItem.attachments.attachment
|
|
535
|
+
: [testCaseItem.attachments.attachment];
|
|
536
|
+
|
|
537
|
+
files = attachments
|
|
538
|
+
.filter(a => a && a.filePath)
|
|
539
|
+
.map(a => a.filePath);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Extract files from stack trace using existing utility
|
|
543
|
+
const stackFiles = fetchFilesFromStackTrace(stack);
|
|
544
|
+
files = [...new Set([...files, ...stackFiles])]; // Remove duplicates
|
|
545
|
+
|
|
517
546
|
prev.push({
|
|
518
547
|
rid,
|
|
519
548
|
file,
|
|
@@ -528,7 +557,9 @@ function reduceTestCases(prev, item) {
|
|
|
528
557
|
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
529
558
|
status,
|
|
530
559
|
title,
|
|
560
|
+
root_suite_id: TESTOMATIO_SUITE,
|
|
531
561
|
suite_title: suiteTitle,
|
|
562
|
+
files,
|
|
532
563
|
});
|
|
533
564
|
});
|
|
534
565
|
return prev;
|
|
@@ -555,10 +586,15 @@ function fetchProperties(item) {
|
|
|
555
586
|
|
|
556
587
|
if (!item.properties) return {};
|
|
557
588
|
|
|
558
|
-
|
|
589
|
+
// Handle both single property and array of properties
|
|
590
|
+
const properties = Array.isArray(item.properties.property)
|
|
591
|
+
? item.properties.property
|
|
592
|
+
: [item.properties.property].filter(Boolean);
|
|
593
|
+
|
|
594
|
+
const prop = properties.find(p => p.name === 'Description');
|
|
559
595
|
if (prop) title = prop.value;
|
|
560
|
-
|
|
561
|
-
|
|
596
|
+
|
|
597
|
+
properties
|
|
562
598
|
.filter(p => p.name === 'Category')
|
|
563
599
|
.forEach(p => tags.push(p.value));
|
|
564
600
|
return { title, tags };
|