@testomatio/reporter 2.3.6-beta.1-truncate-steps → 2.3.7-beta.1-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/README.md +1 -1
- package/lib/adapter/codecept.js +2 -3
- package/lib/bin/reportXml.js +4 -1
- package/lib/client.js +1 -1
- package/lib/junit-adapter/csharp.d.ts +0 -1
- package/lib/junit-adapter/csharp.js +36 -7
- package/lib/junit-adapter/nunit-parser.d.ts +82 -0
- package/lib/junit-adapter/nunit-parser.js +357 -0
- package/lib/pipe/debug.js +1 -1
- package/lib/pipe/testomatio.js +19 -14
- package/lib/uploader.js +4 -0
- package/lib/utils/utils.d.ts +0 -1
- package/lib/utils/utils.js +35 -19
- package/lib/xmlReader.d.ts +11 -26
- package/lib/xmlReader.js +50 -1
- package/package.json +1 -1
- package/src/adapter/codecept.js +3 -5
- package/src/bin/reportXml.js +4 -1
- package/src/client.js +2 -2
- package/src/junit-adapter/csharp.js +40 -6
- package/src/junit-adapter/nunit-parser.js +391 -0
- package/src/pipe/debug.js +2 -3
- package/src/pipe/testomatio.js +76 -80
- package/src/uploader.js +5 -0
- package/src/utils/utils.js +35 -17
- package/src/xmlReader.js +62 -1
package/src/utils/utils.js
CHANGED
|
@@ -83,12 +83,8 @@ 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
|
-
|
|
88
|
-
// Convert Windows path to Linux equivalent for test scenarios
|
|
89
|
-
return f.replace(/^[A-Za-z]:[\\\/]/, '/').replace(/\\/g, '/');
|
|
90
|
-
}
|
|
91
|
-
return f;
|
|
86
|
+
// Normalize path separators for cross-platform compatibility
|
|
87
|
+
return f.replace(/\\/g, '/');
|
|
92
88
|
});
|
|
93
89
|
|
|
94
90
|
debug('Found files in stack trace: ', files);
|
|
@@ -139,6 +135,8 @@ export const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
|
139
135
|
export const SUITE_ID_REGEX = /@S([\w\d]{8})/;
|
|
140
136
|
|
|
141
137
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
138
|
+
if (!code) return null;
|
|
139
|
+
|
|
142
140
|
const comments = code
|
|
143
141
|
.split('\n')
|
|
144
142
|
.map(l => l.trim())
|
|
@@ -180,8 +178,32 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
180
178
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
181
179
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
182
180
|
} else if (opts.lang === 'csharp') {
|
|
183
|
-
|
|
184
|
-
|
|
181
|
+
// Enhanced C# method detection for NUnit tests
|
|
182
|
+
lineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
|
|
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
|
+
}
|
|
185
207
|
} else {
|
|
186
208
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
187
209
|
}
|
|
@@ -191,7 +213,7 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
191
213
|
lineIndex -= opts.prepend;
|
|
192
214
|
}
|
|
193
215
|
|
|
194
|
-
if (lineIndex) {
|
|
216
|
+
if (lineIndex !== -1 && lineIndex !== undefined) {
|
|
195
217
|
const result = [];
|
|
196
218
|
for (let i = lineIndex; i < lineIndex + limit; i++) {
|
|
197
219
|
if (lines[i] === undefined) continue;
|
|
@@ -216,6 +238,10 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
216
238
|
if (opts.lang === 'java' && lines[i].trim().match(/^@\w+/)) break;
|
|
217
239
|
if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
|
|
218
240
|
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;
|
|
219
245
|
}
|
|
220
246
|
result.push(lines[i]);
|
|
221
247
|
}
|
|
@@ -428,16 +454,8 @@ function transformEnvVarToBoolean(value) {
|
|
|
428
454
|
return Boolean(value);
|
|
429
455
|
}
|
|
430
456
|
|
|
431
|
-
function truncate(s, size = 255) {
|
|
432
|
-
if (s.toString().trim().length < size) {
|
|
433
|
-
return s.toString();
|
|
434
|
-
}
|
|
435
|
-
return `${s.toString().substring(0, size)}...`;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
457
|
export {
|
|
439
458
|
ansiRegExp,
|
|
440
|
-
truncate,
|
|
441
459
|
cleanLatestRunId,
|
|
442
460
|
isSameTest,
|
|
443
461
|
fetchSourceCode,
|
package/src/xmlReader.js
CHANGED
|
@@ -6,6 +6,7 @@ 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';
|
|
9
10
|
import {
|
|
10
11
|
fetchFilesFromStackTrace,
|
|
11
12
|
fetchIdFromOutput,
|
|
@@ -75,6 +76,10 @@ class XmlReader {
|
|
|
75
76
|
this.stats.language = opts.lang?.toLowerCase();
|
|
76
77
|
this.uploader = new S3Uploader();
|
|
77
78
|
|
|
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
|
+
|
|
78
83
|
// @ts-ignore
|
|
79
84
|
const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
|
|
80
85
|
this.version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
|
|
@@ -126,7 +131,8 @@ class XmlReader {
|
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
processJUnit(jsonSuite) {
|
|
129
|
-
const { testsuite, name,
|
|
134
|
+
const { testsuite, name, failures, errors } = jsonSuite;
|
|
135
|
+
const tests = testsuite?.tests || jsonSuite.tests;
|
|
130
136
|
|
|
131
137
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
132
138
|
const resultTests = processTestSuite(testsuite);
|
|
@@ -157,6 +163,14 @@ class XmlReader {
|
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
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');
|
|
160
174
|
const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
|
|
161
175
|
|
|
162
176
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
@@ -175,6 +189,53 @@ class XmlReader {
|
|
|
175
189
|
};
|
|
176
190
|
}
|
|
177
191
|
|
|
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
|
+
|
|
178
239
|
processTRX(jsonSuite) {
|
|
179
240
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
180
241
|
if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
|