@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.
@@ -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
- // Convert Windows paths to Linux paths for testing purposes
125
- if (f.match(/^[A-Za-z]:[\\\/]/)) {
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
- if (lineIndex === -1)
219
- lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
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
  }
@@ -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
- status: any;
54
- create_tests: boolean;
55
- tests_count: number;
56
- passed_count: number;
57
- failed_count: number;
58
- skipped_count: number;
59
- tests: any[];
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, tests, failures, errors } = jsonSuite;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.3.6-beta.2-fix-beforesuite",
3
+ "version": "2.3.7-beta.1-xml-import",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -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({ javaTests, lang });
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
- const title = t.title.replace(/\(.*?\)/, '').trim();
7
- const example = t.title.match(/\((.*?)\)/);
8
- if (example) t.example = { ...example[1].split(',') };
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
- const fileName = namespaceToFileName(t.file);
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(path.sep)}.cs`;
61
+ return `${fileParts.join('/')}.cs`;
28
62
  }