@testomatio/reporter 2.3.5-beta-5-xml-import → 2.3.5-beta.8-xml-import
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/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 -18
- package/lib/utils/utils.js +4 -33
- package/lib/xmlReader.d.ts +0 -7
- package/lib/xmlReader.js +10 -312
- package/package.json +1 -1
- package/src/junit-adapter/csharp.js +6 -40
- package/src/pipe/debug.js +3 -2
- package/src/pipe/testomatio.js +80 -74
- package/src/utils/utils.js +3 -33
- package/src/xmlReader.js +10 -360
package/src/xmlReader.js
CHANGED
|
@@ -162,29 +162,7 @@ class XmlReader {
|
|
|
162
162
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
163
163
|
const resultTests = processTestSuite(jsonSuite['test-suite']);
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
debug(
|
|
167
|
-
'Raw tests:',
|
|
168
|
-
resultTests.map(t => ({ title: t.title, example: t.example, file: t.file })),
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
// Optional deduplication for complex NUnit scenarios - can be enabled via options
|
|
172
|
-
let finalTests = resultTests;
|
|
173
|
-
if (this.opts.enableNUnitDeduplication) {
|
|
174
|
-
finalTests = this.deduplicateTestsByFQN(resultTests);
|
|
175
|
-
debug('Tests after deduplication:', finalTests.length);
|
|
176
|
-
debug(
|
|
177
|
-
'Deduplicated tests:',
|
|
178
|
-
finalTests.map(t => ({
|
|
179
|
-
title: t.title,
|
|
180
|
-
examples: t.examples,
|
|
181
|
-
example: t.example,
|
|
182
|
-
file: t.file,
|
|
183
|
-
})),
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
this.tests = this.tests.concat(finalTests);
|
|
165
|
+
this.tests = this.tests.concat(resultTests);
|
|
188
166
|
|
|
189
167
|
return {
|
|
190
168
|
status: result?.toLowerCase(),
|
|
@@ -193,7 +171,7 @@ class XmlReader {
|
|
|
193
171
|
passed_count: parseInt(passed, 10),
|
|
194
172
|
failed_count: parseInt(failed, 10),
|
|
195
173
|
skipped_count: parseInt(inconclusive + skipped, 10),
|
|
196
|
-
tests:
|
|
174
|
+
tests: resultTests,
|
|
197
175
|
};
|
|
198
176
|
}
|
|
199
177
|
|
|
@@ -341,194 +319,6 @@ class XmlReader {
|
|
|
341
319
|
};
|
|
342
320
|
}
|
|
343
321
|
|
|
344
|
-
deduplicateTestsByFQN(tests) {
|
|
345
|
-
const fqnMap = new Map();
|
|
346
|
-
|
|
347
|
-
tests.forEach(test => {
|
|
348
|
-
const fqn = this.generateNormalizedFQN(test);
|
|
349
|
-
|
|
350
|
-
if (fqnMap.has(fqn)) {
|
|
351
|
-
const existingTest = fqnMap.get(fqn);
|
|
352
|
-
|
|
353
|
-
// For parameterized tests, merge as Examples
|
|
354
|
-
if (test.example && Array.isArray(test.example) && test.example.length > 0) {
|
|
355
|
-
// Initialize examples array if it doesn't exist
|
|
356
|
-
if (!existingTest.examples) {
|
|
357
|
-
existingTest.examples = [];
|
|
358
|
-
// Add the existing test's example as the first item if it has parameters
|
|
359
|
-
if (existingTest.example && Array.isArray(existingTest.example) && existingTest.example.length > 0) {
|
|
360
|
-
existingTest.examples.push({
|
|
361
|
-
parameters: existingTest.example,
|
|
362
|
-
status: existingTest.status,
|
|
363
|
-
run_time: existingTest.run_time,
|
|
364
|
-
message: existingTest.message,
|
|
365
|
-
stack: existingTest.stack,
|
|
366
|
-
});
|
|
367
|
-
// Clear the main test's example since it's now in examples array
|
|
368
|
-
delete existingTest.example;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Add this test's execution as an example
|
|
373
|
-
existingTest.examples.push({
|
|
374
|
-
parameters: test.example,
|
|
375
|
-
status: test.status,
|
|
376
|
-
run_time: test.run_time,
|
|
377
|
-
message: test.message,
|
|
378
|
-
stack: test.stack,
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
// Update the main test status to reflect the worst status
|
|
382
|
-
if (test.status === 'failed' || existingTest.status === 'failed') {
|
|
383
|
-
existingTest.status = 'failed';
|
|
384
|
-
} else if (test.status === 'skipped' && existingTest.status !== 'failed') {
|
|
385
|
-
existingTest.status = 'skipped';
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Update total run time
|
|
389
|
-
existingTest.run_time = (existingTest.run_time || 0) + (test.run_time || 0);
|
|
390
|
-
} else {
|
|
391
|
-
// Merge test properties for non-parameterized tests, prioritizing Test Explorer structure
|
|
392
|
-
if (test.test_id && !existingTest.test_id) {
|
|
393
|
-
existingTest.test_id = test.test_id;
|
|
394
|
-
}
|
|
395
|
-
// Keep the most complete test data
|
|
396
|
-
if (test.stack && !existingTest.stack) {
|
|
397
|
-
existingTest.stack = test.stack;
|
|
398
|
-
}
|
|
399
|
-
if (test.message && !existingTest.message) {
|
|
400
|
-
existingTest.message = test.message;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Prefer Test Explorer structure (longer, more complete suite_title)
|
|
405
|
-
if (test.suite_title && test.suite_title.length > existingTest.suite_title.length) {
|
|
406
|
-
existingTest.suite_title = test.suite_title;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Always use the source file path if available
|
|
410
|
-
if (test.file && test.file.endsWith('.cs')) {
|
|
411
|
-
existingTest.file = test.file;
|
|
412
|
-
} else if (!existingTest.file || !existingTest.file.endsWith('.cs')) {
|
|
413
|
-
existingTest.file = this.extractCsFileFromPath(test);
|
|
414
|
-
}
|
|
415
|
-
} else {
|
|
416
|
-
// Fix file path to use proper .cs file names from source paths
|
|
417
|
-
if (!test.file || !test.file.endsWith('.cs')) {
|
|
418
|
-
test.file = this.extractCsFileFromPath(test);
|
|
419
|
-
}
|
|
420
|
-
fqnMap.set(fqn, test);
|
|
421
|
-
}
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
return Array.from(fqnMap.values());
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
generateFQN(test) {
|
|
428
|
-
// Generate Fully Qualified Name: Namespace + Class + Method (standard .NET FQN)
|
|
429
|
-
// Don't include assembly as it can vary between different test structures
|
|
430
|
-
const namespace = this.extractNamespace(test);
|
|
431
|
-
const className = this.extractClassName(test);
|
|
432
|
-
const methodName = test.title;
|
|
433
|
-
|
|
434
|
-
// Use the most complete namespace.class structure available
|
|
435
|
-
if (test.suite_title && test.suite_title.includes('.')) {
|
|
436
|
-
return `${test.suite_title}.${methodName}`;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return `${namespace}.${className}.${methodName}`;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
generateNormalizedFQN(test) {
|
|
443
|
-
// Generate normalized FQN for deduplication by extracting the core namespace.class.method
|
|
444
|
-
// For parameterized tests, we want the SAME FQN so they merge into one test with multiple Examples
|
|
445
|
-
|
|
446
|
-
const fullClassName = test.suite_title || '';
|
|
447
|
-
const methodName = test.title;
|
|
448
|
-
|
|
449
|
-
// Extract the most specific namespace.class pattern
|
|
450
|
-
if (fullClassName.includes('.')) {
|
|
451
|
-
const parts = fullClassName.split('.');
|
|
452
|
-
|
|
453
|
-
if (parts.length >= 2) {
|
|
454
|
-
const className = parts[parts.length - 1];
|
|
455
|
-
|
|
456
|
-
// Look for common .NET namespace patterns and normalize them:
|
|
457
|
-
// TestProject.Tests.MyClass -> Tests.MyClass
|
|
458
|
-
// Tests.MyClass -> Tests.MyClass
|
|
459
|
-
// MyProject.SubNamespace.Tests.MyClass -> Tests.MyClass
|
|
460
|
-
|
|
461
|
-
let normalizedNamespace = '';
|
|
462
|
-
for (let i = parts.length - 2; i >= 0; i--) {
|
|
463
|
-
const part = parts[i];
|
|
464
|
-
|
|
465
|
-
// Build namespace from right to left, excluding project names
|
|
466
|
-
if (part === 'Tests' || part.endsWith('Tests') || part.includes('Test')) {
|
|
467
|
-
// Found a test namespace, use it as the normalized namespace
|
|
468
|
-
normalizedNamespace = part;
|
|
469
|
-
break;
|
|
470
|
-
} else if (i === parts.length - 2) {
|
|
471
|
-
// If no test namespace found, use the immediate parent as namespace
|
|
472
|
-
normalizedNamespace = part;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return `${normalizedNamespace}.${className}.${methodName}`;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Fallback for simple class names
|
|
481
|
-
return `${fullClassName}.${methodName}`;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
extractAssemblyName(test) {
|
|
485
|
-
// Extract assembly name from file path or use default
|
|
486
|
-
if (test.file) {
|
|
487
|
-
const parts = test.file.split(/[/\\]/);
|
|
488
|
-
return parts[0] || 'DefaultAssembly';
|
|
489
|
-
}
|
|
490
|
-
return 'DefaultAssembly';
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
extractNamespace(test) {
|
|
494
|
-
// Extract namespace from suite_title or classname
|
|
495
|
-
if (test.suite_title && test.suite_title.includes('.')) {
|
|
496
|
-
const parts = test.suite_title.split('.');
|
|
497
|
-
return parts.slice(0, -1).join('.');
|
|
498
|
-
}
|
|
499
|
-
return test.suite_title || 'DefaultNamespace';
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
extractClassName(test) {
|
|
503
|
-
// Extract class name from suite_title
|
|
504
|
-
if (test.suite_title && test.suite_title.includes('.')) {
|
|
505
|
-
const parts = test.suite_title.split('.');
|
|
506
|
-
return parts[parts.length - 1];
|
|
507
|
-
}
|
|
508
|
-
return test.suite_title || 'DefaultClass';
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
extractCsFileFromPath(test) {
|
|
512
|
-
// Extract .cs file name from source file path, not namespace
|
|
513
|
-
if (test.file) {
|
|
514
|
-
// Look for actual .cs file path patterns
|
|
515
|
-
const csFileMatch = test.file.match(/([^/\\]+\.cs)$/);
|
|
516
|
-
if (csFileMatch) {
|
|
517
|
-
return test.file;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// If no .cs extension, assume it's a namespace path and convert to likely file name
|
|
521
|
-
const className = this.extractClassName(test);
|
|
522
|
-
const pathParts = test.file.split(/[/\\]/);
|
|
523
|
-
pathParts[pathParts.length - 1] = `${className}.cs`;
|
|
524
|
-
return pathParts.join('/');
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// Fallback to class name
|
|
528
|
-
const className = this.extractClassName(test);
|
|
529
|
-
return `${className}.cs`;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
322
|
calculateStats() {
|
|
533
323
|
this.stats = {
|
|
534
324
|
...this.stats,
|
|
@@ -570,15 +360,9 @@ class XmlReader {
|
|
|
570
360
|
debug('Failed to open file with the source code', file);
|
|
571
361
|
return;
|
|
572
362
|
}
|
|
573
|
-
|
|
574
363
|
const contents = fs.readFileSync(file).toString();
|
|
575
|
-
|
|
576
|
-
// Try original test name first (for parameterized tests), fallback to regular title
|
|
577
|
-
const titleForLookup = t.originalTestName ? t.originalTestName.replace(/\(.*?\)/, '').trim() : t.title;
|
|
578
|
-
|
|
579
|
-
t.code = fetchSourceCode(contents, { ...t, title: titleForLookup, lang: this.stats.language });
|
|
364
|
+
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language });
|
|
580
365
|
if (t.code) debug('Fetched code for test %s', t.title);
|
|
581
|
-
|
|
582
366
|
t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language });
|
|
583
367
|
if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
|
|
584
368
|
} catch (err) {
|
|
@@ -701,13 +485,7 @@ function reduceTestCases(prev, item) {
|
|
|
701
485
|
testCases
|
|
702
486
|
.filter(t => !!t)
|
|
703
487
|
.forEach(testCaseItem => {
|
|
704
|
-
|
|
705
|
-
let file = testCaseItem.file || item.filepath || item.fullname || item.package || '';
|
|
706
|
-
|
|
707
|
-
// If no file found with simple approach and we have enhanced extraction enabled, use it
|
|
708
|
-
if (!file && item.filepath) {
|
|
709
|
-
file = extractSourceFilePath(testCaseItem, item);
|
|
710
|
-
}
|
|
488
|
+
const file = testCaseItem.file || item.filepath || item.fullname || item.package || '';
|
|
711
489
|
|
|
712
490
|
let stack = '';
|
|
713
491
|
let message = '';
|
|
@@ -727,34 +505,15 @@ function reduceTestCases(prev, item) {
|
|
|
727
505
|
// SpecFlow config
|
|
728
506
|
let { title, tags, testId } = fetchProperties(isParametrized ? item : testCaseItem);
|
|
729
507
|
let example = null;
|
|
730
|
-
|
|
731
|
-
// Simple suite title extraction (version 2.1.1 approach) with fallback to enhanced
|
|
732
|
-
let suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
|
|
733
|
-
if (!suiteTitle && item.fullname) {
|
|
734
|
-
suiteTitle = extractTestExplorerSuiteTitle(testCaseItem, item);
|
|
735
|
-
}
|
|
508
|
+
const suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
|
|
736
509
|
|
|
737
510
|
title ||= testCaseItem.name || testCaseItem.methodname || testCaseItem.classname;
|
|
738
511
|
tags ||= [];
|
|
739
512
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (testCaseItem.arguments && testCaseItem.arguments.arg) {
|
|
745
|
-
const args = Array.isArray(testCaseItem.arguments.arg)
|
|
746
|
-
? testCaseItem.arguments.arg
|
|
747
|
-
: [testCaseItem.arguments.arg];
|
|
748
|
-
example = args; // Store as array instead of object
|
|
749
|
-
// Remove parameters from title for NUnit tests
|
|
750
|
-
title = (testCaseItem.methodname || title).replace(/\(.*?\)/, '').trim();
|
|
751
|
-
} else {
|
|
752
|
-
// Simple parameter extraction (version 2.1.1 approach)
|
|
753
|
-
const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
|
|
754
|
-
if (exampleMatches) {
|
|
755
|
-
example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
|
|
756
|
-
title = title.replace(/\(.*?\)/, '').trim();
|
|
757
|
-
}
|
|
513
|
+
const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
|
|
514
|
+
if (exampleMatches) {
|
|
515
|
+
example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
|
|
516
|
+
title = title.replace(/\(.*?\)/, '').trim();
|
|
758
517
|
}
|
|
759
518
|
|
|
760
519
|
stack = `${
|
|
@@ -809,7 +568,6 @@ function reduceTestCases(prev, item) {
|
|
|
809
568
|
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
810
569
|
status,
|
|
811
570
|
title,
|
|
812
|
-
originalTestName, // Store original name for enhanced features
|
|
813
571
|
root_suite_id: TESTOMATIO_SUITE,
|
|
814
572
|
suite_title: suiteTitle,
|
|
815
573
|
files,
|
|
@@ -819,113 +577,6 @@ function reduceTestCases(prev, item) {
|
|
|
819
577
|
return prev;
|
|
820
578
|
}
|
|
821
579
|
|
|
822
|
-
function extractSourceFilePath(testCaseItem, item) {
|
|
823
|
-
// Priority order for file path extraction to match Test Explorer structure:
|
|
824
|
-
// 1. filepath attribute (direct .cs file path from NUnit)
|
|
825
|
-
// 2. fullname (contains full project path)
|
|
826
|
-
// 3. file attribute from test case
|
|
827
|
-
// 4. package (fallback)
|
|
828
|
-
|
|
829
|
-
// NUnit provides filepath attribute with actual .cs file path - use this first
|
|
830
|
-
if (item.filepath) {
|
|
831
|
-
// Clean up Windows/Unix path separators and ensure proper format
|
|
832
|
-
let filePath = item.filepath.replace(/\\/g, '/');
|
|
833
|
-
|
|
834
|
-
// Make relative to current working directory if absolute
|
|
835
|
-
if (path.isAbsolute(item.filepath)) {
|
|
836
|
-
const cwd = process.cwd().replace(/\\/g, '/');
|
|
837
|
-
if (filePath.startsWith(cwd)) {
|
|
838
|
-
filePath = path.relative(cwd, item.filepath).replace(/\\/g, '/');
|
|
839
|
-
} else {
|
|
840
|
-
// Try to extract relative path from common patterns
|
|
841
|
-
const commonPatterns = ['/Tests/', '/test/', '/src/', '/Test/'];
|
|
842
|
-
for (const pattern of commonPatterns) {
|
|
843
|
-
const index = filePath.lastIndexOf(pattern);
|
|
844
|
-
if (index !== -1) {
|
|
845
|
-
filePath = filePath.substring(index + 1);
|
|
846
|
-
break;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
return filePath;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
if (testCaseItem.file) {
|
|
855
|
-
let filePath = testCaseItem.file.replace(/\\/g, '/');
|
|
856
|
-
|
|
857
|
-
// Make relative to current working directory if absolute
|
|
858
|
-
if (path.isAbsolute(testCaseItem.file)) {
|
|
859
|
-
const cwd = process.cwd().replace(/\\/g, '/');
|
|
860
|
-
if (filePath.startsWith(cwd)) {
|
|
861
|
-
filePath = path.relative(cwd, testCaseItem.file).replace(/\\/g, '/');
|
|
862
|
-
} else {
|
|
863
|
-
// Try to extract relative path from common patterns
|
|
864
|
-
const commonPatterns = ['/Tests/', '/test/', '/src/', '/Test/'];
|
|
865
|
-
for (const pattern of commonPatterns) {
|
|
866
|
-
const index = filePath.lastIndexOf(pattern);
|
|
867
|
-
if (index !== -1) {
|
|
868
|
-
filePath = filePath.substring(index + 1);
|
|
869
|
-
break;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
return filePath;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
if (item.fullname) {
|
|
878
|
-
// Extract actual file path from fullname if it contains path separators
|
|
879
|
-
const fullnameParts = item.fullname.split('.');
|
|
880
|
-
if (fullnameParts.length > 2) {
|
|
881
|
-
// For ParameterizedMethod, get the class name (not method name)
|
|
882
|
-
// Example: "NUnit_sample_test.Tests.SampleTests.TestBooleanValue" -> "Tests/SampleTests.cs"
|
|
883
|
-
let namespaceParts, className;
|
|
884
|
-
|
|
885
|
-
if (item.type === 'ParameterizedMethod') {
|
|
886
|
-
// For parameterized methods, the last part is the method name, second-to-last is class
|
|
887
|
-
namespaceParts = fullnameParts.slice(1, -2); // Skip project name and method name
|
|
888
|
-
className = fullnameParts[fullnameParts.length - 2]; // Get class name
|
|
889
|
-
} else {
|
|
890
|
-
// For regular classes/fixtures
|
|
891
|
-
namespaceParts = fullnameParts.slice(1, -1); // Skip project name
|
|
892
|
-
className = fullnameParts[fullnameParts.length - 1];
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
return `${namespaceParts.join('/')}/${className}.cs`;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
if (item.package) return item.package.replace(/\\/g, '/');
|
|
900
|
-
|
|
901
|
-
// Fallback: construct from classname
|
|
902
|
-
if (testCaseItem.classname) {
|
|
903
|
-
const parts = testCaseItem.classname.split('.');
|
|
904
|
-
const className = parts[parts.length - 1];
|
|
905
|
-
const namespacePath = parts.slice(0, -1).join('/');
|
|
906
|
-
return `${namespacePath}/${className}.cs`;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
return '';
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function extractTestExplorerSuiteTitle(testCaseItem, item) {
|
|
913
|
-
// Extract suite title to match Test Explorer structure (Project/Namespace hierarchy)
|
|
914
|
-
// Priority: fullname > classname > name
|
|
915
|
-
|
|
916
|
-
if (item.fullname) {
|
|
917
|
-
// Use fullname to maintain Test Explorer structure
|
|
918
|
-
return item.fullname;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
if (testCaseItem.classname) {
|
|
922
|
-
return testCaseItem.classname;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
// Fallback to item name but prefer classname structure
|
|
926
|
-
return item.name || testCaseItem.classname || 'UnknownClass';
|
|
927
|
-
}
|
|
928
|
-
|
|
929
580
|
function processTestSuite(testsuite) {
|
|
930
581
|
if (!testsuite) return [];
|
|
931
582
|
if (testsuite.testsuite) return processTestSuite(testsuite.testsuite);
|
|
@@ -936,8 +587,7 @@ function processTestSuite(testsuite) {
|
|
|
936
587
|
suites = [testsuite];
|
|
937
588
|
}
|
|
938
589
|
|
|
939
|
-
|
|
940
|
-
const subSuites = suites.filter(s => s['test-suite'] && !s['test-case']);
|
|
590
|
+
const subSuites = suites.filter(s => s['test-suite'] && !testsuite['test-case']);
|
|
941
591
|
|
|
942
592
|
return [...subSuites.map(s => processTestSuite(s['test-suite'])), ...suites.reduce(reduceTestCases, [])].flat();
|
|
943
593
|
}
|