@testomatio/reporter 2.3.7-beta.1-xml-import → 2.3.7-beta.10-stack-artifacts
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 -1
- package/lib/adapter/codecept.js +22 -2
- package/lib/bin/cli.js +0 -0
- package/lib/bin/reportXml.js +1 -4
- package/lib/bin/startTest.js +0 -0
- package/lib/bin/uploadArtifacts.js +0 -0
- package/lib/client.js +32 -21
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +7 -36
- package/lib/pipe/debug.js +1 -1
- package/lib/pipe/testomatio.js +14 -19
- package/lib/reporter.d.ts +19 -9
- package/lib/reporter.js +40 -5
- package/lib/uploader.js +0 -4
- package/lib/utils/utils.d.ts +1 -0
- package/lib/utils/utils.js +23 -35
- package/lib/xmlReader.d.ts +26 -11
- package/lib/xmlReader.js +1 -50
- package/package.json +1 -1
- package/src/adapter/codecept.js +27 -3
- package/src/bin/reportXml.js +1 -4
- package/src/client.js +57 -27
- package/src/junit-adapter/csharp.js +6 -40
- package/src/pipe/debug.js +3 -2
- package/src/pipe/testomatio.js +80 -76
- package/src/reporter.js +7 -4
- package/src/uploader.js +0 -5
- package/src/utils/utils.js +21 -35
- package/src/xmlReader.js +1 -62
- package/lib/junit-adapter/nunit-parser.d.ts +0 -82
- package/lib/junit-adapter/nunit-parser.js +0 -357
- package/src/junit-adapter/nunit-parser.js +0 -391
package/src/pipe/testomatio.js
CHANGED
|
@@ -20,7 +20,7 @@ if (process.env.TESTOMATIO_RUN) process.env.runId = process.env.TESTOMATIO_RUN;
|
|
|
20
20
|
class TestomatioPipe {
|
|
21
21
|
constructor(params, store) {
|
|
22
22
|
this.batch = {
|
|
23
|
-
isEnabled: params?.isBatchEnabled ??
|
|
23
|
+
isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
24
24
|
intervalFunction: null, // will be created in createRun by setInterval function
|
|
25
25
|
intervalTime: 5000, // how often tests are sent
|
|
26
26
|
tests: [], // array of tests in batch
|
|
@@ -60,8 +60,8 @@ class TestomatioPipe {
|
|
|
60
60
|
retryConfig: {
|
|
61
61
|
retry: REPORTER_REQUEST_RETRIES.retriesPerRequest,
|
|
62
62
|
retryDelay: REPORTER_REQUEST_RETRIES.retryTimeout,
|
|
63
|
-
httpMethodsToRetry: ['GET',
|
|
64
|
-
shouldRetry: error => {
|
|
63
|
+
httpMethodsToRetry: ['GET','PUT','HEAD','OPTIONS','DELETE','POST'],
|
|
64
|
+
shouldRetry: (error) => {
|
|
65
65
|
if (!error.response) return false;
|
|
66
66
|
switch (error.response?.status) {
|
|
67
67
|
case 400: // Bad request (probably wrong API key)
|
|
@@ -73,8 +73,8 @@ class TestomatioPipe {
|
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
76
|
-
}
|
|
77
|
-
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
this.isEnabled = true;
|
|
@@ -104,6 +104,7 @@ class TestomatioPipe {
|
|
|
104
104
|
// add test ID + run ID
|
|
105
105
|
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
|
106
106
|
|
|
107
|
+
|
|
107
108
|
if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
|
|
108
109
|
data.stack = null;
|
|
109
110
|
}
|
|
@@ -119,6 +120,7 @@ class TestomatioPipe {
|
|
|
119
120
|
return data;
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
|
|
122
124
|
/**
|
|
123
125
|
* Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
|
|
124
126
|
* @param {Object} opts - The options for preparing the test grepList.
|
|
@@ -213,7 +215,7 @@ class TestomatioPipe {
|
|
|
213
215
|
method: 'PUT',
|
|
214
216
|
url: `/api/reporter/${this.runId}`,
|
|
215
217
|
data: runParams,
|
|
216
|
-
responseType: 'json'
|
|
218
|
+
responseType: 'json'
|
|
217
219
|
});
|
|
218
220
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
219
221
|
return;
|
|
@@ -226,7 +228,7 @@ class TestomatioPipe {
|
|
|
226
228
|
url: '/api/reporter',
|
|
227
229
|
data: runParams,
|
|
228
230
|
maxContentLength: Infinity,
|
|
229
|
-
responseType: 'json'
|
|
231
|
+
responseType: 'json'
|
|
230
232
|
});
|
|
231
233
|
|
|
232
234
|
this.runId = resp.data.uid;
|
|
@@ -285,44 +287,44 @@ class TestomatioPipe {
|
|
|
285
287
|
|
|
286
288
|
debug('Adding test', json);
|
|
287
289
|
|
|
288
|
-
return this.client
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (err.response.status >= 400) {
|
|
303
|
-
const responseData = err.response.data || { message: '' };
|
|
304
|
-
console.log(
|
|
305
|
-
APP_PREFIX,
|
|
306
|
-
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
307
|
-
pc.gray(data?.title || ''),
|
|
308
|
-
);
|
|
309
|
-
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
310
|
-
this.hasUnmatchedTests = true;
|
|
311
|
-
}
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
290
|
+
return this.client.request({
|
|
291
|
+
method: 'POST',
|
|
292
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
293
|
+
data: json,
|
|
294
|
+
headers: {
|
|
295
|
+
'Content-Type': 'application/json',
|
|
296
|
+
},
|
|
297
|
+
maxContentLength: Infinity
|
|
298
|
+
}).catch(err => {
|
|
299
|
+
this.requestFailures++;
|
|
300
|
+
this.notReportedTestsCount++;
|
|
301
|
+
if (err.response) {
|
|
302
|
+
if (err.response.status >= 400) {
|
|
303
|
+
const responseData = err.response.data || { message: '' };
|
|
314
304
|
console.log(
|
|
315
305
|
APP_PREFIX,
|
|
316
|
-
pc.yellow(`Warning: ${
|
|
317
|
-
|
|
306
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
307
|
+
pc.gray(data?.title || ''),
|
|
318
308
|
);
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
309
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
310
|
+
this.hasUnmatchedTests = true;
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
322
313
|
}
|
|
323
|
-
|
|
314
|
+
console.log(
|
|
315
|
+
APP_PREFIX,
|
|
316
|
+
pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
317
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
318
|
+
);
|
|
319
|
+
printCreateIssue(err);
|
|
320
|
+
} else {
|
|
321
|
+
console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
324
|
};
|
|
325
325
|
|
|
326
|
+
|
|
327
|
+
|
|
326
328
|
/**
|
|
327
329
|
* Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
|
|
328
330
|
*/
|
|
@@ -347,42 +349,43 @@ class TestomatioPipe {
|
|
|
347
349
|
const testsToSend = this.batch.tests.splice(0);
|
|
348
350
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
349
351
|
|
|
350
|
-
return this.client
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (err.response.status >= 400) {
|
|
369
|
-
const responseData = err.response.data || { message: '' };
|
|
370
|
-
console.log(APP_PREFIX, pc.yellow(`Warning: ${responseData.message} (${err.response.status})`));
|
|
371
|
-
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
372
|
-
this.hasUnmatchedTests = true;
|
|
373
|
-
}
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
352
|
+
return this.client.request({
|
|
353
|
+
method: 'POST',
|
|
354
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
355
|
+
data: {
|
|
356
|
+
api_key: this.apiKey,
|
|
357
|
+
tests: testsToSend,
|
|
358
|
+
batch_index: this.batch.batchIndex
|
|
359
|
+
},
|
|
360
|
+
headers: {
|
|
361
|
+
'Content-Type': 'application/json',
|
|
362
|
+
},
|
|
363
|
+
maxContentLength: Infinity
|
|
364
|
+
}).catch(err => {
|
|
365
|
+
this.requestFailures++;
|
|
366
|
+
this.notReportedTestsCount += testsToSend.length;
|
|
367
|
+
if (err.response) {
|
|
368
|
+
if (err.response.status >= 400) {
|
|
369
|
+
const responseData = err.response.data || { message: '' };
|
|
376
370
|
console.log(
|
|
377
371
|
APP_PREFIX,
|
|
378
|
-
pc.yellow(`Warning: (${err.response
|
|
379
|
-
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
372
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
380
373
|
);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
374
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
375
|
+
this.hasUnmatchedTests = true;
|
|
376
|
+
}
|
|
377
|
+
return;
|
|
384
378
|
}
|
|
385
|
-
|
|
379
|
+
console.log(
|
|
380
|
+
APP_PREFIX,
|
|
381
|
+
pc.yellow(`Warning: (${err.response?.status})`),
|
|
382
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
383
|
+
);
|
|
384
|
+
printCreateIssue(err);
|
|
385
|
+
} else {
|
|
386
|
+
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
386
389
|
};
|
|
387
390
|
|
|
388
391
|
/**
|
|
@@ -405,9 +408,9 @@ class TestomatioPipe {
|
|
|
405
408
|
else this.batch.tests.push(data);
|
|
406
409
|
|
|
407
410
|
// if test is added after run which is already finished
|
|
408
|
-
|
|
411
|
+
if (!this.batch.intervalFunction) uploading = this.#batchUpload();
|
|
409
412
|
|
|
410
|
-
|
|
413
|
+
// return promise to be able to wait for it
|
|
411
414
|
return uploading;
|
|
412
415
|
}
|
|
413
416
|
|
|
@@ -456,11 +459,9 @@ class TestomatioPipe {
|
|
|
456
459
|
status_event,
|
|
457
460
|
detach: params.detach,
|
|
458
461
|
tests: params.tests,
|
|
459
|
-
}
|
|
462
|
+
}
|
|
460
463
|
});
|
|
461
464
|
|
|
462
|
-
debug(APP_PREFIX, '✅ Testrun finished');
|
|
463
|
-
|
|
464
465
|
if (this.runUrl) {
|
|
465
466
|
console.log(APP_PREFIX, '📊 Report Saved. Report URL:', pc.magenta(this.runUrl));
|
|
466
467
|
}
|
|
@@ -524,6 +525,9 @@ function printCreateIssue(err) {
|
|
|
524
525
|
console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
|
|
525
526
|
console.log('```');
|
|
526
527
|
});
|
|
528
|
+
|
|
527
529
|
}
|
|
528
530
|
|
|
531
|
+
|
|
532
|
+
|
|
529
533
|
export default TestomatioPipe;
|
package/src/reporter.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import Client from './client.js';
|
|
2
|
+
import * as TestomatioConstants from './constants.js';
|
|
3
3
|
import { services } from './services/index.js';
|
|
4
4
|
import reporterFunctions from './reporter-functions.js';
|
|
5
5
|
|
|
6
|
+
export { Client };
|
|
7
|
+
export const STATUS = TestomatioConstants.STATUS;
|
|
6
8
|
export const artifact = reporterFunctions.artifact;
|
|
7
9
|
export const log = reporterFunctions.log;
|
|
8
10
|
export const logger = services.logger;
|
|
@@ -35,6 +37,7 @@ export default {
|
|
|
35
37
|
linkTest: reporterFunctions.linkTest,
|
|
36
38
|
linkJira: reporterFunctions.linkJira,
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
TestomatioClient: Client,
|
|
41
|
+
STATUS,
|
|
42
|
+
|
|
40
43
|
};
|
package/src/uploader.js
CHANGED
|
@@ -194,11 +194,6 @@ export class S3Uploader {
|
|
|
194
194
|
filePath = path.join(process.cwd(), filePath);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
// Normalize path separators for cross-platform compatibility
|
|
198
|
-
if (typeof filePath === 'string') {
|
|
199
|
-
filePath = filePath.replace(/\\/g, '/');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
197
|
const data = { rid, file: filePath, uploaded };
|
|
203
198
|
const jsonLine = `${JSON.stringify(data)}\n`;
|
|
204
199
|
fs.appendFileSync(tempFilePath, jsonLine);
|
package/src/utils/utils.js
CHANGED
|
@@ -83,8 +83,12 @@ const fetchFilesFromStackTrace = (stack = '', checkExists = true) => {
|
|
|
83
83
|
.map(f => f[1].trim())
|
|
84
84
|
.map(f => f.replace(/^\/+/, '/').replace(/^\/([A-Za-z]:)/, '$1')) // Remove extra slashes, handle Windows paths
|
|
85
85
|
.map(f => {
|
|
86
|
-
//
|
|
87
|
-
|
|
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;
|
|
88
92
|
});
|
|
89
93
|
|
|
90
94
|
debug('Found files in stack trace: ', files);
|
|
@@ -135,8 +139,6 @@ export const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
|
135
139
|
export const SUITE_ID_REGEX = /@S([\w\d]{8})/;
|
|
136
140
|
|
|
137
141
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
138
|
-
if (!code) return null;
|
|
139
|
-
|
|
140
142
|
const comments = code
|
|
141
143
|
.split('\n')
|
|
142
144
|
.map(l => l.trim())
|
|
@@ -178,32 +180,8 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
178
180
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
179
181
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
180
182
|
} else if (opts.lang === 'csharp') {
|
|
181
|
-
|
|
182
|
-
lineIndex = lines.findIndex(l => l.includes(
|
|
183
|
-
|
|
184
|
-
if (lineIndex === -1) {
|
|
185
|
-
lineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (lineIndex === -1) {
|
|
189
|
-
lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Look for TestCase or Test attributes above the method
|
|
193
|
-
if (lineIndex === -1) {
|
|
194
|
-
const testAttributeIndex = lines.findIndex((l, index) => {
|
|
195
|
-
if (l.includes('[TestCase') || l.includes('[Test')) {
|
|
196
|
-
// Check next few lines for the method
|
|
197
|
-
const nextLines = lines.slice(index, Math.min(lines.length, index + 5));
|
|
198
|
-
const hasMethod = nextLines.some(nextLine => nextLine.includes(`${title}(`));
|
|
199
|
-
return hasMethod;
|
|
200
|
-
}
|
|
201
|
-
return false;
|
|
202
|
-
});
|
|
203
|
-
if (testAttributeIndex !== -1) {
|
|
204
|
-
lineIndex = testAttributeIndex;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
183
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
184
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
207
185
|
} else {
|
|
208
186
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
209
187
|
}
|
|
@@ -213,7 +191,7 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
213
191
|
lineIndex -= opts.prepend;
|
|
214
192
|
}
|
|
215
193
|
|
|
216
|
-
if (lineIndex
|
|
194
|
+
if (lineIndex) {
|
|
217
195
|
const result = [];
|
|
218
196
|
for (let i = lineIndex; i < lineIndex + limit; i++) {
|
|
219
197
|
if (lines[i] === undefined) continue;
|
|
@@ -238,10 +216,6 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
238
216
|
if (opts.lang === 'java' && lines[i].trim().match(/^@\w+/)) break;
|
|
239
217
|
if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
|
|
240
218
|
if (opts.lang === 'java' && lines[i].includes(' class ')) break;
|
|
241
|
-
if (opts.lang === 'csharp' && lines[i].trim().match(/^\[Test/)) break;
|
|
242
|
-
if (opts.lang === 'csharp' && lines[i].includes(' public void ')) break;
|
|
243
|
-
if (opts.lang === 'csharp' && lines[i].includes(' public async Task ')) break;
|
|
244
|
-
if (opts.lang === 'csharp' && lines[i].includes(' class ') && lines[i].includes('public')) break;
|
|
245
219
|
}
|
|
246
220
|
result.push(lines[i]);
|
|
247
221
|
}
|
|
@@ -454,8 +428,20 @@ function transformEnvVarToBoolean(value) {
|
|
|
454
428
|
return Boolean(value);
|
|
455
429
|
}
|
|
456
430
|
|
|
431
|
+
function truncate(s, size = 255) {
|
|
432
|
+
if (s === undefined || s === null) {
|
|
433
|
+
return '';
|
|
434
|
+
}
|
|
435
|
+
const str = s.toString();
|
|
436
|
+
if (str.trim().length < size) {
|
|
437
|
+
return str;
|
|
438
|
+
}
|
|
439
|
+
return `${str.substring(0, size)}...`;
|
|
440
|
+
}
|
|
441
|
+
|
|
457
442
|
export {
|
|
458
443
|
ansiRegExp,
|
|
444
|
+
truncate,
|
|
459
445
|
cleanLatestRunId,
|
|
460
446
|
isSameTest,
|
|
461
447
|
fetchSourceCode,
|
package/src/xmlReader.js
CHANGED
|
@@ -6,7 +6,6 @@ import { XMLParser } from 'fast-xml-parser';
|
|
|
6
6
|
import { APP_PREFIX, STATUS } from './constants.js';
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
|
-
import { NUnitXmlParser } from './junit-adapter/nunit-parser.js';
|
|
10
9
|
import {
|
|
11
10
|
fetchFilesFromStackTrace,
|
|
12
11
|
fetchIdFromOutput,
|
|
@@ -76,10 +75,6 @@ class XmlReader {
|
|
|
76
75
|
this.stats.language = opts.lang?.toLowerCase();
|
|
77
76
|
this.uploader = new S3Uploader();
|
|
78
77
|
|
|
79
|
-
// Enhanced NUnit parsing - enabled by default for NUnit XML
|
|
80
|
-
this.enhancedNunit = opts.enhancedNunit !== false; // Default true, can be disabled
|
|
81
|
-
this.groupParameterized = opts.groupParameterized !== false; // Default true, can be disabled
|
|
82
|
-
|
|
83
78
|
// @ts-ignore
|
|
84
79
|
const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
|
|
85
80
|
this.version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
|
|
@@ -131,8 +126,7 @@ class XmlReader {
|
|
|
131
126
|
}
|
|
132
127
|
|
|
133
128
|
processJUnit(jsonSuite) {
|
|
134
|
-
const { testsuite, name, failures, errors } = jsonSuite;
|
|
135
|
-
const tests = testsuite?.tests || jsonSuite.tests;
|
|
129
|
+
const { testsuite, name, tests, failures, errors } = jsonSuite;
|
|
136
130
|
|
|
137
131
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
138
132
|
const resultTests = processTestSuite(testsuite);
|
|
@@ -163,14 +157,6 @@ class XmlReader {
|
|
|
163
157
|
}
|
|
164
158
|
|
|
165
159
|
processNUnit(jsonSuite) {
|
|
166
|
-
// Use enhanced NUnit parser if enabled and this is actually NUnit XML
|
|
167
|
-
if (this.enhancedNunit && this.isNUnitXml(jsonSuite)) {
|
|
168
|
-
debug('Using enhanced NUnit parser');
|
|
169
|
-
return this.processNUnitEnhanced(jsonSuite);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Fallback to legacy parser for backward compatibility
|
|
173
|
-
debug('Using legacy NUnit parser');
|
|
174
160
|
const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
|
|
175
161
|
|
|
176
162
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
@@ -189,53 +175,6 @@ class XmlReader {
|
|
|
189
175
|
};
|
|
190
176
|
}
|
|
191
177
|
|
|
192
|
-
/**
|
|
193
|
-
* Check if the XML is actually NUnit format (has test-suite hierarchy)
|
|
194
|
-
* @param {Object} jsonSuite - Parsed XML suite object
|
|
195
|
-
* @returns {boolean} - True if this is NUnit XML format
|
|
196
|
-
*/
|
|
197
|
-
isNUnitXml(jsonSuite) {
|
|
198
|
-
// NUnit XML has test-suite elements with type attributes
|
|
199
|
-
if (jsonSuite['test-suite']) {
|
|
200
|
-
const testSuite = Array.isArray(jsonSuite['test-suite']) ? jsonSuite['test-suite'][0] : jsonSuite['test-suite'];
|
|
201
|
-
|
|
202
|
-
// Check for NUnit-specific test-suite types
|
|
203
|
-
return (
|
|
204
|
-
testSuite &&
|
|
205
|
-
testSuite.type &&
|
|
206
|
-
['Assembly', 'TestSuite', 'TestFixture', 'ParameterizedMethod'].includes(testSuite.type)
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
processNUnitEnhanced(jsonSuite) {
|
|
213
|
-
debug('Processing NUnit XML with enhanced parser');
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const nunitParser = new NUnitXmlParser({
|
|
217
|
-
groupParameterized: this.groupParameterized,
|
|
218
|
-
...this.opts,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
const result = nunitParser.parseTestRun(jsonSuite);
|
|
222
|
-
|
|
223
|
-
// Add parsed tests to our collection
|
|
224
|
-
this.tests = this.tests.concat(result.tests);
|
|
225
|
-
|
|
226
|
-
debug(`Enhanced NUnit parser processed ${result.tests.length} tests`);
|
|
227
|
-
|
|
228
|
-
return result;
|
|
229
|
-
} catch (error) {
|
|
230
|
-
debug('Enhanced NUnit parser failed, falling back to legacy parser:', error.message);
|
|
231
|
-
console.warn(`${APP_PREFIX} Enhanced NUnit parsing failed, using legacy parser: ${error.message}`);
|
|
232
|
-
|
|
233
|
-
// Fallback to legacy parser
|
|
234
|
-
this.enhancedNunit = false;
|
|
235
|
-
return this.processNUnit(jsonSuite);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
178
|
processTRX(jsonSuite) {
|
|
240
179
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
241
180
|
if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Enhanced NUnit XML Parser that properly handles test-suite hierarchy
|
|
3
|
-
* and parameterized tests
|
|
4
|
-
*/
|
|
5
|
-
export class NUnitXmlParser {
|
|
6
|
-
constructor(options?: {});
|
|
7
|
-
options: {};
|
|
8
|
-
tests: any[];
|
|
9
|
-
stats: {
|
|
10
|
-
total: number;
|
|
11
|
-
passed: number;
|
|
12
|
-
failed: number;
|
|
13
|
-
skipped: number;
|
|
14
|
-
inconclusive: number;
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* Parse NUnit XML test-run structure
|
|
18
|
-
* @param {Object} testRun - Parsed XML test-run object
|
|
19
|
-
* @returns {Object} - Parsed test results
|
|
20
|
-
*/
|
|
21
|
-
parseTestRun(testRun: any): any;
|
|
22
|
-
/**
|
|
23
|
-
* Recursively parse test-suite elements based on their type
|
|
24
|
-
* @param {Object|Array} testSuite - Test suite object or array
|
|
25
|
-
* @param {Array} parentPath - Current path in the hierarchy
|
|
26
|
-
*/
|
|
27
|
-
parseTestSuite(testSuite: any | any[], parentPath?: any[]): void;
|
|
28
|
-
/**
|
|
29
|
-
* Process child elements of a test suite
|
|
30
|
-
* @param {Object} testSuite - Test suite object
|
|
31
|
-
* @param {Array} currentPath - Current path in hierarchy
|
|
32
|
-
*/
|
|
33
|
-
processChildren(testSuite: any, currentPath: any[]): void;
|
|
34
|
-
/**
|
|
35
|
-
* Parse test-case elements (actual tests)
|
|
36
|
-
* @param {Object|Array} testCases - Test case object or array
|
|
37
|
-
* @param {Array} suitePath - Path to the test suite
|
|
38
|
-
* @param {Object} parentSuite - Parent test suite for context
|
|
39
|
-
*/
|
|
40
|
-
parseTestCases(testCases: any | any[], suitePath: any[], parentSuite: any): void;
|
|
41
|
-
/**
|
|
42
|
-
* Parse individual test case
|
|
43
|
-
* @param {Object} testCase - Test case object
|
|
44
|
-
* @param {Array} suitePath - Path to the test suite
|
|
45
|
-
* @param {Object} parentSuite - Parent test suite for context
|
|
46
|
-
* @returns {Object|null} - Parsed test object
|
|
47
|
-
*/
|
|
48
|
-
parseTestCase(testCase: any, suitePath: any[], parentSuite: any): any | null;
|
|
49
|
-
/**
|
|
50
|
-
* Extract method name and parameters from test name
|
|
51
|
-
* @param {string} testName - Full test name
|
|
52
|
-
* @returns {Object} - Extracted information
|
|
53
|
-
*/
|
|
54
|
-
extractParameters(testName: string): any;
|
|
55
|
-
/**
|
|
56
|
-
* Parse parameter string into array of parameters
|
|
57
|
-
* @param {string} paramString - Parameter string
|
|
58
|
-
* @returns {Array} - Array of parameters
|
|
59
|
-
*/
|
|
60
|
-
parseParameterString(paramString: string): any[];
|
|
61
|
-
/**
|
|
62
|
-
* Extract method name from test name (fallback)
|
|
63
|
-
* @param {string} testName - Test name
|
|
64
|
-
* @returns {string} - Method name
|
|
65
|
-
*/
|
|
66
|
-
extractMethodName(testName: string): string;
|
|
67
|
-
/**
|
|
68
|
-
* Build file path from suite path and class name
|
|
69
|
-
* @param {Array} suitePath - Suite path array
|
|
70
|
-
* @param {string} className - Class name
|
|
71
|
-
* @param {Object} parentSuite - Parent suite for context
|
|
72
|
-
* @returns {string} - File path
|
|
73
|
-
*/
|
|
74
|
-
buildFilePath(suitePath: any[], className: string, parentSuite: any): string;
|
|
75
|
-
/**
|
|
76
|
-
* Group parameterized tests by base method name
|
|
77
|
-
* @param {Array} tests - Array of parsed tests
|
|
78
|
-
* @returns {Object} - Grouped tests
|
|
79
|
-
*/
|
|
80
|
-
groupParameterizedTests(tests: any[]): any;
|
|
81
|
-
}
|
|
82
|
-
export default NUnitXmlParser;
|