@testomatio/reporter 2.0.0-beta.4-gaxios → 2.0.0-beta.4-xml
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/debug.js +1 -1
- package/src/pipe/gitlab.js +8 -27
- package/src/pipe/testomatio.js +95 -95
- package/src/utils/utils.js +16 -1
- package/src/xmlReader.js +56 -13
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,15 @@ 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
|
+
// @ts-ignore - import.meta is only available in ESM
|
|
14
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const __dirname = typeof global.__dirname !== 'undefined' ? global.__dirname : currentDir;
|
|
16
|
+
|
|
11
17
|
/**
|
|
12
18
|
* @param {String} testTitle - Test title
|
|
13
19
|
*
|
|
@@ -107,7 +113,7 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
107
113
|
.join('\n');
|
|
108
114
|
};
|
|
109
115
|
|
|
110
|
-
const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
116
|
+
export const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
111
117
|
|
|
112
118
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
113
119
|
const comments = code
|
|
@@ -150,6 +156,9 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
150
156
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
151
157
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
152
158
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
159
|
+
} else if (opts.lang === 'csharp') {
|
|
160
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
161
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
153
162
|
} else {
|
|
154
163
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
155
164
|
}
|
|
@@ -353,6 +362,12 @@ function formatStep(step, shift = 0) {
|
|
|
353
362
|
return lines;
|
|
354
363
|
}
|
|
355
364
|
|
|
365
|
+
export function getPackageVersion() {
|
|
366
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
367
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
368
|
+
return packageJson.version;
|
|
369
|
+
}
|
|
370
|
+
|
|
356
371
|
export {
|
|
357
372
|
ansiRegExp,
|
|
358
373
|
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 = '';
|
|
@@ -489,7 +495,7 @@ function reduceTestCases(prev, item) {
|
|
|
489
495
|
const preferClassname = reduceOptions.preferClassname || isParametrized;
|
|
490
496
|
|
|
491
497
|
// SpecFlow config
|
|
492
|
-
let { title, tags } = fetchProperties(isParametrized ? item : testCaseItem);
|
|
498
|
+
let { title, tags, testId } = fetchProperties(isParametrized ? item : testCaseItem);
|
|
493
499
|
let example = null;
|
|
494
500
|
const suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
|
|
495
501
|
|
|
@@ -505,15 +511,39 @@ 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
|
+
|
|
515
|
+
if (!testId) testId = fetchIdFromOutput(stack);
|
|
516
|
+
|
|
517
|
+
if (tags?.length && !testId) {
|
|
518
|
+
testId = tags.filter(t => t.startsWith('T')).map(t => `@${t}`).find(t => t.match(TEST_ID_REGEX))?.slice(2);
|
|
519
|
+
}
|
|
509
520
|
|
|
510
521
|
let status = STATUS.PASSED.toString();
|
|
511
522
|
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
512
523
|
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
524
|
+
if (testCaseItem.result && Object.values(STATUS).includes(testCaseItem.result.toLowerCase())) {
|
|
525
|
+
status = testCaseItem.result.toLowerCase();
|
|
526
|
+
}
|
|
513
527
|
|
|
514
528
|
let rid = null;
|
|
515
529
|
if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
|
|
516
530
|
|
|
531
|
+
// Extract attachments
|
|
532
|
+
let files = [];
|
|
533
|
+
if (testCaseItem.attachments) {
|
|
534
|
+
const attachments = Array.isArray(testCaseItem.attachments.attachment)
|
|
535
|
+
? testCaseItem.attachments.attachment
|
|
536
|
+
: [testCaseItem.attachments.attachment];
|
|
537
|
+
|
|
538
|
+
files = attachments
|
|
539
|
+
.filter(a => a && a.filePath)
|
|
540
|
+
.map(a => a.filePath);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Extract files from stack trace using existing utility
|
|
544
|
+
const stackFiles = fetchFilesFromStackTrace(stack);
|
|
545
|
+
files = [...new Set([...files, ...stackFiles])]; // Remove duplicates
|
|
546
|
+
|
|
517
547
|
prev.push({
|
|
518
548
|
rid,
|
|
519
549
|
file,
|
|
@@ -528,7 +558,9 @@ function reduceTestCases(prev, item) {
|
|
|
528
558
|
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
529
559
|
status,
|
|
530
560
|
title,
|
|
561
|
+
root_suite_id: TESTOMATIO_SUITE,
|
|
531
562
|
suite_title: suiteTitle,
|
|
563
|
+
files,
|
|
532
564
|
});
|
|
533
565
|
});
|
|
534
566
|
return prev;
|
|
@@ -555,11 +587,22 @@ function fetchProperties(item) {
|
|
|
555
587
|
|
|
556
588
|
if (!item.properties) return {};
|
|
557
589
|
|
|
558
|
-
|
|
590
|
+
// Handle both single property and array of properties
|
|
591
|
+
const properties = Array.isArray(item.properties.property)
|
|
592
|
+
? item.properties.property
|
|
593
|
+
: [item.properties.property].filter(Boolean);
|
|
594
|
+
|
|
595
|
+
const prop = properties.find(p => p.name === 'Description');
|
|
559
596
|
if (prop) title = prop.value;
|
|
560
|
-
|
|
561
|
-
|
|
597
|
+
|
|
598
|
+
let testId = properties.find(p => p.name === 'ID')?.value;
|
|
599
|
+
|
|
600
|
+
if (testId?.startsWith('@')) testId = testId.slice(1);
|
|
601
|
+
if (testId?.startsWith('T')) testId = testId.slice(1);
|
|
602
|
+
|
|
603
|
+
properties
|
|
562
604
|
.filter(p => p.name === 'Category')
|
|
563
605
|
.forEach(p => tags.push(p.value));
|
|
564
|
-
|
|
606
|
+
|
|
607
|
+
return { title, tags, testId };
|
|
565
608
|
}
|