@testomatio/reporter 2.3.6-beta.2-fix-beforesuite → 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/bin/reportXml.js +4 -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 -15
- package/lib/uploader.js +4 -0
- package/lib/utils/utils.js +35 -10
- package/lib/xmlReader.d.ts +11 -26
- package/lib/xmlReader.js +50 -1
- package/package.json +1 -1
- package/src/bin/reportXml.js +4 -1
- 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 +75 -81
- package/src/uploader.js +5 -0
- package/src/utils/utils.js +35 -9
- package/src/xmlReader.js +62 -1
package/lib/utils/utils.js
CHANGED
|
@@ -121,12 +121,8 @@ const fetchFilesFromStackTrace = (stack = '', checkExists = true) => {
|
|
|
121
121
|
.map(f => f[1].trim())
|
|
122
122
|
.map(f => f.replace(/^\/+/, '/').replace(/^\/([A-Za-z]:)/, '$1')) // Remove extra slashes, handle Windows paths
|
|
123
123
|
.map(f => {
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
// Convert Windows path to Linux equivalent for test scenarios
|
|
127
|
-
return f.replace(/^[A-Za-z]:[\\\/]/, '/').replace(/\\/g, '/');
|
|
128
|
-
}
|
|
129
|
-
return f;
|
|
124
|
+
// Normalize path separators for cross-platform compatibility
|
|
125
|
+
return f.replace(/\\/g, '/');
|
|
130
126
|
});
|
|
131
127
|
debug('Found files in stack trace: ', files);
|
|
132
128
|
return files.filter(f => {
|
|
@@ -173,6 +169,8 @@ exports.fetchSourceCodeFromStackTrace = fetchSourceCodeFromStackTrace;
|
|
|
173
169
|
exports.TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
174
170
|
exports.SUITE_ID_REGEX = /@S([\w\d]{8})/;
|
|
175
171
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
172
|
+
if (!code)
|
|
173
|
+
return null;
|
|
176
174
|
const comments = code
|
|
177
175
|
.split('\n')
|
|
178
176
|
.map(l => l.trim())
|
|
@@ -215,10 +213,29 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
215
213
|
lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
216
214
|
}
|
|
217
215
|
else if (opts.lang === 'csharp') {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (lineIndex === -1)
|
|
216
|
+
// Enhanced C# method detection for NUnit tests
|
|
217
|
+
lineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
|
|
218
|
+
if (lineIndex === -1) {
|
|
219
|
+
lineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
|
|
220
|
+
}
|
|
221
|
+
if (lineIndex === -1) {
|
|
221
222
|
lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
223
|
+
}
|
|
224
|
+
// Look for TestCase or Test attributes above the method
|
|
225
|
+
if (lineIndex === -1) {
|
|
226
|
+
const testAttributeIndex = lines.findIndex((l, index) => {
|
|
227
|
+
if (l.includes('[TestCase') || l.includes('[Test')) {
|
|
228
|
+
// Check next few lines for the method
|
|
229
|
+
const nextLines = lines.slice(index, Math.min(lines.length, index + 5));
|
|
230
|
+
const hasMethod = nextLines.some(nextLine => nextLine.includes(`${title}(`));
|
|
231
|
+
return hasMethod;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
});
|
|
235
|
+
if (testAttributeIndex !== -1) {
|
|
236
|
+
lineIndex = testAttributeIndex;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
222
239
|
}
|
|
223
240
|
else {
|
|
224
241
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
@@ -227,7 +244,7 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
227
244
|
if (opts.prepend) {
|
|
228
245
|
lineIndex -= opts.prepend;
|
|
229
246
|
}
|
|
230
|
-
if (lineIndex) {
|
|
247
|
+
if (lineIndex !== -1 && lineIndex !== undefined) {
|
|
231
248
|
const result = [];
|
|
232
249
|
for (let i = lineIndex; i < lineIndex + limit; i++) {
|
|
233
250
|
if (lines[i] === undefined)
|
|
@@ -270,6 +287,14 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
270
287
|
break;
|
|
271
288
|
if (opts.lang === 'java' && lines[i].includes(' class '))
|
|
272
289
|
break;
|
|
290
|
+
if (opts.lang === 'csharp' && lines[i].trim().match(/^\[Test/))
|
|
291
|
+
break;
|
|
292
|
+
if (opts.lang === 'csharp' && lines[i].includes(' public void '))
|
|
293
|
+
break;
|
|
294
|
+
if (opts.lang === 'csharp' && lines[i].includes(' public async Task '))
|
|
295
|
+
break;
|
|
296
|
+
if (opts.lang === 'csharp' && lines[i].includes(' class ') && lines[i].includes('public'))
|
|
297
|
+
break;
|
|
273
298
|
}
|
|
274
299
|
result.push(lines[i]);
|
|
275
300
|
}
|
package/lib/xmlReader.d.ts
CHANGED
|
@@ -19,25 +19,11 @@ declare class XmlReader {
|
|
|
19
19
|
tests: any[];
|
|
20
20
|
stats: {};
|
|
21
21
|
uploader: S3Uploader;
|
|
22
|
+
enhancedNunit: boolean;
|
|
23
|
+
groupParameterized: boolean;
|
|
22
24
|
version: any;
|
|
23
25
|
connectAdapter(): import("./junit-adapter/adapter.js").default;
|
|
24
|
-
parse(fileName: any):
|
|
25
|
-
status: string;
|
|
26
|
-
create_tests: boolean;
|
|
27
|
-
tests_count: number;
|
|
28
|
-
passed_count: number;
|
|
29
|
-
skipped_count: number;
|
|
30
|
-
failed_count: number;
|
|
31
|
-
tests: any;
|
|
32
|
-
} | {
|
|
33
|
-
status: any;
|
|
34
|
-
create_tests: boolean;
|
|
35
|
-
tests_count: number;
|
|
36
|
-
passed_count: number;
|
|
37
|
-
failed_count: number;
|
|
38
|
-
skipped_count: number;
|
|
39
|
-
tests: any[];
|
|
40
|
-
};
|
|
26
|
+
parse(fileName: any): any;
|
|
41
27
|
processJUnit(jsonSuite: any): {
|
|
42
28
|
create_tests: boolean;
|
|
43
29
|
duration: number;
|
|
@@ -49,15 +35,14 @@ declare class XmlReader {
|
|
|
49
35
|
tests: any[];
|
|
50
36
|
tests_count: number;
|
|
51
37
|
};
|
|
52
|
-
processNUnit(jsonSuite: any):
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
};
|
|
38
|
+
processNUnit(jsonSuite: any): any;
|
|
39
|
+
/**
|
|
40
|
+
* Check if the XML is actually NUnit format (has test-suite hierarchy)
|
|
41
|
+
* @param {Object} jsonSuite - Parsed XML suite object
|
|
42
|
+
* @returns {boolean} - True if this is NUnit XML format
|
|
43
|
+
*/
|
|
44
|
+
isNUnitXml(jsonSuite: any): boolean;
|
|
45
|
+
processNUnitEnhanced(jsonSuite: any): any;
|
|
61
46
|
processTRX(jsonSuite: any): {
|
|
62
47
|
status: string;
|
|
63
48
|
create_tests: boolean;
|
package/lib/xmlReader.js
CHANGED
|
@@ -11,6 +11,7 @@ const fast_xml_parser_1 = require("fast-xml-parser");
|
|
|
11
11
|
const constants_js_1 = require("./constants.js");
|
|
12
12
|
const crypto_1 = require("crypto");
|
|
13
13
|
const url_1 = require("url");
|
|
14
|
+
const nunit_parser_js_1 = require("./junit-adapter/nunit-parser.js");
|
|
14
15
|
const utils_js_1 = require("./utils/utils.js");
|
|
15
16
|
const index_js_1 = require("./pipe/index.js");
|
|
16
17
|
const index_js_2 = __importDefault(require("./junit-adapter/index.js"));
|
|
@@ -54,6 +55,9 @@ class XmlReader {
|
|
|
54
55
|
this.stats = {};
|
|
55
56
|
this.stats.language = opts.lang?.toLowerCase();
|
|
56
57
|
this.uploader = new uploader_js_1.S3Uploader();
|
|
58
|
+
// Enhanced NUnit parsing - enabled by default for NUnit XML
|
|
59
|
+
this.enhancedNunit = opts.enhancedNunit !== false; // Default true, can be disabled
|
|
60
|
+
this.groupParameterized = opts.groupParameterized !== false; // Default true, can be disabled
|
|
57
61
|
// @ts-ignore
|
|
58
62
|
const packageJsonPath = path_1.default.resolve(__dirname, '..', 'package.json');
|
|
59
63
|
this.version = JSON.parse(fs_1.default.readFileSync(packageJsonPath).toString()).version;
|
|
@@ -102,7 +106,8 @@ class XmlReader {
|
|
|
102
106
|
return this.processJUnit(jsonSuite);
|
|
103
107
|
}
|
|
104
108
|
processJUnit(jsonSuite) {
|
|
105
|
-
const { testsuite, name,
|
|
109
|
+
const { testsuite, name, failures, errors } = jsonSuite;
|
|
110
|
+
const tests = testsuite?.tests || jsonSuite.tests;
|
|
106
111
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
107
112
|
const resultTests = processTestSuite(testsuite);
|
|
108
113
|
const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
|
|
@@ -128,6 +133,13 @@ class XmlReader {
|
|
|
128
133
|
};
|
|
129
134
|
}
|
|
130
135
|
processNUnit(jsonSuite) {
|
|
136
|
+
// Use enhanced NUnit parser if enabled and this is actually NUnit XML
|
|
137
|
+
if (this.enhancedNunit && this.isNUnitXml(jsonSuite)) {
|
|
138
|
+
debug('Using enhanced NUnit parser');
|
|
139
|
+
return this.processNUnitEnhanced(jsonSuite);
|
|
140
|
+
}
|
|
141
|
+
// Fallback to legacy parser for backward compatibility
|
|
142
|
+
debug('Using legacy NUnit parser');
|
|
131
143
|
const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
|
|
132
144
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
133
145
|
const resultTests = processTestSuite(jsonSuite['test-suite']);
|
|
@@ -142,6 +154,43 @@ class XmlReader {
|
|
|
142
154
|
tests: resultTests,
|
|
143
155
|
};
|
|
144
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if the XML is actually NUnit format (has test-suite hierarchy)
|
|
159
|
+
* @param {Object} jsonSuite - Parsed XML suite object
|
|
160
|
+
* @returns {boolean} - True if this is NUnit XML format
|
|
161
|
+
*/
|
|
162
|
+
isNUnitXml(jsonSuite) {
|
|
163
|
+
// NUnit XML has test-suite elements with type attributes
|
|
164
|
+
if (jsonSuite['test-suite']) {
|
|
165
|
+
const testSuite = Array.isArray(jsonSuite['test-suite']) ? jsonSuite['test-suite'][0] : jsonSuite['test-suite'];
|
|
166
|
+
// Check for NUnit-specific test-suite types
|
|
167
|
+
return (testSuite &&
|
|
168
|
+
testSuite.type &&
|
|
169
|
+
['Assembly', 'TestSuite', 'TestFixture', 'ParameterizedMethod'].includes(testSuite.type));
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
processNUnitEnhanced(jsonSuite) {
|
|
174
|
+
debug('Processing NUnit XML with enhanced parser');
|
|
175
|
+
try {
|
|
176
|
+
const nunitParser = new nunit_parser_js_1.NUnitXmlParser({
|
|
177
|
+
groupParameterized: this.groupParameterized,
|
|
178
|
+
...this.opts,
|
|
179
|
+
});
|
|
180
|
+
const result = nunitParser.parseTestRun(jsonSuite);
|
|
181
|
+
// Add parsed tests to our collection
|
|
182
|
+
this.tests = this.tests.concat(result.tests);
|
|
183
|
+
debug(`Enhanced NUnit parser processed ${result.tests.length} tests`);
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
debug('Enhanced NUnit parser failed, falling back to legacy parser:', error.message);
|
|
188
|
+
console.warn(`${constants_js_1.APP_PREFIX} Enhanced NUnit parsing failed, using legacy parser: ${error.message}`);
|
|
189
|
+
// Fallback to legacy parser
|
|
190
|
+
this.enhancedNunit = false;
|
|
191
|
+
return this.processNUnit(jsonSuite);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
145
194
|
processTRX(jsonSuite) {
|
|
146
195
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
147
196
|
if (!Array.isArray(defs))
|
package/package.json
CHANGED
package/src/bin/reportXml.js
CHANGED
|
@@ -34,7 +34,10 @@ program
|
|
|
34
34
|
}
|
|
35
35
|
lang = lang?.toLowerCase();
|
|
36
36
|
if (javaTests === true || (lang === 'java' && !javaTests)) javaTests = 'src/test/java';
|
|
37
|
-
const runReader = new XmlReader({
|
|
37
|
+
const runReader = new XmlReader({
|
|
38
|
+
javaTests,
|
|
39
|
+
lang,
|
|
40
|
+
});
|
|
38
41
|
const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
39
42
|
if (!files.length) {
|
|
40
43
|
console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
|
|
@@ -3,18 +3,45 @@ import Adapter from './adapter.js';
|
|
|
3
3
|
|
|
4
4
|
class CSharpAdapter extends Adapter {
|
|
5
5
|
formatTest(t) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (
|
|
6
|
+
// Don't override example if it already exists from NUnit XML processing
|
|
7
|
+
// The xmlReader.js already extracts parameters correctly from <arguments>
|
|
8
|
+
if (!t.example) {
|
|
9
|
+
const title = t.title.replace(/\(.*?\)/, '').trim();
|
|
10
|
+
const exampleMatch = t.title.match(/\((.*?)\)/);
|
|
11
|
+
if (exampleMatch) {
|
|
12
|
+
// Keep as array for consistency with NUnit XML processing
|
|
13
|
+
t.example = exampleMatch[1].split(',').map(param => param.trim());
|
|
14
|
+
}
|
|
15
|
+
t.title = title.trim();
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
const suite = t.suite_title.split('.');
|
|
10
19
|
t.suite_title = suite.pop();
|
|
11
20
|
t.file = namespaceToFileName(t.file);
|
|
12
|
-
t.title = title.trim();
|
|
13
21
|
return t;
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
getFilePath(t) {
|
|
17
|
-
|
|
25
|
+
if (!t.file) return null;
|
|
26
|
+
|
|
27
|
+
// Normalize path separators for cross-platform compatibility
|
|
28
|
+
let filePath = t.file.replace(/\\/g, '/');
|
|
29
|
+
|
|
30
|
+
// If file already has .cs extension, use it directly
|
|
31
|
+
if (filePath.endsWith('.cs')) {
|
|
32
|
+
// Make relative path if it's absolute
|
|
33
|
+
if (path.isAbsolute(filePath)) {
|
|
34
|
+
// Try to find project-relative path
|
|
35
|
+
const cwd = process.cwd().replace(/\\/g, '/');
|
|
36
|
+
if (filePath.startsWith(cwd)) {
|
|
37
|
+
filePath = path.relative(cwd, filePath).replace(/\\/g, '/');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return filePath;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Convert namespace path to file path
|
|
44
|
+
const fileName = namespaceToFileName(filePath);
|
|
18
45
|
return fileName;
|
|
19
46
|
}
|
|
20
47
|
}
|
|
@@ -22,7 +49,14 @@ class CSharpAdapter extends Adapter {
|
|
|
22
49
|
export default CSharpAdapter;
|
|
23
50
|
|
|
24
51
|
function namespaceToFileName(fileName) {
|
|
52
|
+
if (!fileName) return '';
|
|
53
|
+
|
|
54
|
+
// If already a .cs file path, clean it up
|
|
55
|
+
if (fileName.endsWith('.cs')) {
|
|
56
|
+
return fileName.replace(/\\/g, '/');
|
|
57
|
+
}
|
|
58
|
+
|
|
25
59
|
const fileParts = fileName.split('.');
|
|
26
60
|
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
27
|
-
return `${fileParts.join(
|
|
61
|
+
return `${fileParts.join('/')}.cs`;
|
|
28
62
|
}
|