@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.
@@ -1,4 +1,5 @@
1
1
  export default CSharpAdapter;
2
2
  declare class CSharpAdapter extends Adapter {
3
+ getFilePath(t: any): string;
3
4
  }
4
5
  import Adapter from './adapter.js';
@@ -7,53 +7,24 @@ const path_1 = __importDefault(require("path"));
7
7
  const adapter_js_1 = __importDefault(require("./adapter.js"));
8
8
  class CSharpAdapter extends adapter_js_1.default {
9
9
  formatTest(t) {
10
- // Don't override example if it already exists from NUnit XML processing
11
- // The xmlReader.js already extracts parameters correctly from <arguments>
12
- if (!t.example) {
13
- const title = t.title.replace(/\(.*?\)/, '').trim();
14
- const exampleMatch = t.title.match(/\((.*?)\)/);
15
- if (exampleMatch) {
16
- // Keep as array for consistency with NUnit XML processing
17
- t.example = exampleMatch[1].split(',').map(param => param.trim());
18
- }
19
- t.title = title.trim();
20
- }
10
+ const title = t.title.replace(/\(.*?\)/, '').trim();
11
+ const example = t.title.match(/\((.*?)\)/);
12
+ if (example)
13
+ t.example = { ...example[1].split(',') };
21
14
  const suite = t.suite_title.split('.');
22
15
  t.suite_title = suite.pop();
23
16
  t.file = namespaceToFileName(t.file);
17
+ t.title = title.trim();
24
18
  return t;
25
19
  }
26
20
  getFilePath(t) {
27
- if (!t.file)
28
- return null;
29
- // Normalize path separators for cross-platform compatibility
30
- let filePath = t.file.replace(/\\/g, '/');
31
- // If file already has .cs extension, use it directly
32
- if (filePath.endsWith('.cs')) {
33
- // Make relative path if it's absolute
34
- if (path_1.default.isAbsolute(filePath)) {
35
- // Try to find project-relative path
36
- const cwd = process.cwd().replace(/\\/g, '/');
37
- if (filePath.startsWith(cwd)) {
38
- filePath = path_1.default.relative(cwd, filePath).replace(/\\/g, '/');
39
- }
40
- }
41
- return filePath;
42
- }
43
- // Convert namespace path to file path
44
- const fileName = namespaceToFileName(filePath);
21
+ const fileName = namespaceToFileName(t.file);
45
22
  return fileName;
46
23
  }
47
24
  }
48
25
  module.exports = CSharpAdapter;
49
26
  function namespaceToFileName(fileName) {
50
- if (!fileName)
51
- return '';
52
- // If already a .cs file path, clean it up
53
- if (fileName.endsWith('.cs')) {
54
- return fileName.replace(/\\/g, '/');
55
- }
56
27
  const fileParts = fileName.split('.');
57
28
  fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
58
- return `${fileParts.join('/')}.cs`;
29
+ return `${fileParts.join(path_1.default.sep)}.cs`;
59
30
  }
package/lib/pipe/debug.js CHANGED
@@ -18,7 +18,7 @@ class DebugPipe {
18
18
  this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
19
19
  if (this.isEnabled) {
20
20
  this.batch = {
21
- isEnabled: this.params.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
21
+ isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
22
22
  intervalFunction: null,
23
23
  intervalTime: 5000,
24
24
  tests: [],
@@ -23,7 +23,7 @@ if (process.env.TESTOMATIO_RUN)
23
23
  class TestomatioPipe {
24
24
  constructor(params, store) {
25
25
  this.batch = {
26
- isEnabled: params?.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
26
+ isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
27
27
  intervalFunction: null, // will be created in createRun by setInterval function
28
28
  intervalTime: 5000, // how often tests are sent
29
29
  tests: [], // array of tests in batch
@@ -60,7 +60,7 @@ class TestomatioPipe {
60
60
  retry: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest,
61
61
  retryDelay: constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout,
62
62
  httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
63
- shouldRetry: error => {
63
+ shouldRetry: (error) => {
64
64
  if (!error.response)
65
65
  return false;
66
66
  switch (error.response?.status) {
@@ -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
  this.isEnabled = true;
80
80
  // do not finish this run (for parallel testing)
@@ -193,7 +193,7 @@ class TestomatioPipe {
193
193
  method: 'PUT',
194
194
  url: `/api/reporter/${this.runId}`,
195
195
  data: runParams,
196
- responseType: 'json',
196
+ responseType: 'json'
197
197
  });
198
198
  if (resp.data.artifacts)
199
199
  (0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
@@ -206,7 +206,7 @@ class TestomatioPipe {
206
206
  url: '/api/reporter',
207
207
  data: runParams,
208
208
  maxContentLength: Infinity,
209
- responseType: 'json',
209
+ responseType: 'json'
210
210
  });
211
211
  this.runId = resp.data.uid;
212
212
  this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
@@ -259,17 +259,15 @@ class TestomatioPipe {
259
259
  this.#formatData(data);
260
260
  const json = json_cycle_1.default.stringify(data);
261
261
  debug('Adding test', json);
262
- return this.client
263
- .request({
262
+ return this.client.request({
264
263
  method: 'POST',
265
264
  url: `/api/reporter/${this.runId}/testrun`,
266
265
  data: json,
267
266
  headers: {
268
267
  'Content-Type': 'application/json',
269
268
  },
270
- maxContentLength: Infinity,
271
- })
272
- .catch(err => {
269
+ maxContentLength: Infinity
270
+ }).catch(err => {
273
271
  this.requestFailures++;
274
272
  this.notReportedTestsCount++;
275
273
  if (err.response) {
@@ -314,21 +312,19 @@ class TestomatioPipe {
314
312
  // get tests from batch and clear batch
315
313
  const testsToSend = this.batch.tests.splice(0);
316
314
  debug('📨 Batch upload', testsToSend.length, 'tests');
317
- return this.client
318
- .request({
315
+ return this.client.request({
319
316
  method: 'POST',
320
317
  url: `/api/reporter/${this.runId}/testrun`,
321
318
  data: {
322
319
  api_key: this.apiKey,
323
320
  tests: testsToSend,
324
- batch_index: this.batch.batchIndex,
321
+ batch_index: this.batch.batchIndex
325
322
  },
326
323
  headers: {
327
324
  'Content-Type': 'application/json',
328
325
  },
329
- maxContentLength: Infinity,
330
- })
331
- .catch(err => {
326
+ maxContentLength: Infinity
327
+ }).catch(err => {
332
328
  this.requestFailures++;
333
329
  this.notReportedTestsCount += testsToSend.length;
334
330
  if (err.response) {
@@ -412,7 +408,7 @@ class TestomatioPipe {
412
408
  status_event,
413
409
  detach: params.detach,
414
410
  tests: params.tests,
415
- },
411
+ }
416
412
  });
417
413
  console.log(constants_js_1.APP_PREFIX, '✅ Testrun finished');
418
414
  if (this.runUrl) {
@@ -173,8 +173,6 @@ exports.fetchSourceCodeFromStackTrace = fetchSourceCodeFromStackTrace;
173
173
  exports.TEST_ID_REGEX = /@T([\w\d]{8})/;
174
174
  exports.SUITE_ID_REGEX = /@S([\w\d]{8})/;
175
175
  const fetchIdFromCode = (code, opts = {}) => {
176
- if (!code)
177
- return null;
178
176
  const comments = code
179
177
  .split('\n')
180
178
  .map(l => l.trim())
@@ -217,29 +215,10 @@ const fetchSourceCode = (contents, opts = {}) => {
217
215
  lineIndex = lines.findIndex(l => l.includes(`${title}(`));
218
216
  }
219
217
  else if (opts.lang === 'csharp') {
220
- // Enhanced C# method detection for NUnit tests
221
- lineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
222
- if (lineIndex === -1) {
223
- lineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
224
- }
225
- if (lineIndex === -1) {
218
+ if (lineIndex === -1)
219
+ lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
220
+ if (lineIndex === -1)
226
221
  lineIndex = lines.findIndex(l => l.includes(`${title}(`));
227
- }
228
- // Look for TestCase or Test attributes above the method
229
- if (lineIndex === -1) {
230
- const testAttributeIndex = lines.findIndex((l, index) => {
231
- if (l.includes('[TestCase') || l.includes('[Test')) {
232
- // Check next few lines for the method
233
- const nextLines = lines.slice(index, Math.min(lines.length, index + 5));
234
- const hasMethod = nextLines.some(nextLine => nextLine.includes(`${title}(`));
235
- return hasMethod;
236
- }
237
- return false;
238
- });
239
- if (testAttributeIndex !== -1) {
240
- lineIndex = testAttributeIndex;
241
- }
242
- }
243
222
  }
244
223
  else {
245
224
  lineIndex = lines.findIndex(l => l.includes(title));
@@ -248,7 +227,7 @@ const fetchSourceCode = (contents, opts = {}) => {
248
227
  if (opts.prepend) {
249
228
  lineIndex -= opts.prepend;
250
229
  }
251
- if (lineIndex !== -1 && lineIndex !== undefined) {
230
+ if (lineIndex) {
252
231
  const result = [];
253
232
  for (let i = lineIndex; i < lineIndex + limit; i++) {
254
233
  if (lines[i] === undefined)
@@ -291,14 +270,6 @@ const fetchSourceCode = (contents, opts = {}) => {
291
270
  break;
292
271
  if (opts.lang === 'java' && lines[i].includes(' class '))
293
272
  break;
294
- if (opts.lang === 'csharp' && lines[i].trim().match(/^\[Test/))
295
- break;
296
- if (opts.lang === 'csharp' && lines[i].includes(' public void '))
297
- break;
298
- if (opts.lang === 'csharp' && lines[i].includes(' public async Task '))
299
- break;
300
- if (opts.lang === 'csharp' && lines[i].includes(' class ') && lines[i].includes('public'))
301
- break;
302
273
  }
303
274
  result.push(lines[i]);
304
275
  }
@@ -77,13 +77,6 @@ declare class XmlReader {
77
77
  skipped_count: number;
78
78
  tests: any[];
79
79
  };
80
- deduplicateTestsByFQN(tests: any): any[];
81
- generateFQN(test: any): string;
82
- generateNormalizedFQN(test: any): string;
83
- extractAssemblyName(test: any): any;
84
- extractNamespace(test: any): any;
85
- extractClassName(test: any): any;
86
- extractCsFileFromPath(test: any): any;
87
80
  calculateStats(): {};
88
81
  fetchSourceCode(): void;
89
82
  formatTests(): void;
package/lib/xmlReader.js CHANGED
@@ -131,21 +131,7 @@ class XmlReader {
131
131
  const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
132
132
  reduceOptions.preferClassname = this.stats.language === 'python';
133
133
  const resultTests = processTestSuite(jsonSuite['test-suite']);
134
- debug('Raw tests extracted from NUnit XML:', resultTests.length);
135
- debug('Raw tests:', resultTests.map(t => ({ title: t.title, example: t.example, file: t.file })));
136
- // Optional deduplication for complex NUnit scenarios - can be enabled via options
137
- let finalTests = resultTests;
138
- if (this.opts.enableNUnitDeduplication) {
139
- finalTests = this.deduplicateTestsByFQN(resultTests);
140
- debug('Tests after deduplication:', finalTests.length);
141
- debug('Deduplicated tests:', finalTests.map(t => ({
142
- title: t.title,
143
- examples: t.examples,
144
- example: t.example,
145
- file: t.file,
146
- })));
147
- }
148
- this.tests = this.tests.concat(finalTests);
134
+ this.tests = this.tests.concat(resultTests);
149
135
  return {
150
136
  status: result?.toLowerCase(),
151
137
  create_tests: true,
@@ -153,7 +139,7 @@ class XmlReader {
153
139
  passed_count: parseInt(passed, 10),
154
140
  failed_count: parseInt(failed, 10),
155
141
  skipped_count: parseInt(inconclusive + skipped, 10),
156
- tests: finalTests,
142
+ tests: resultTests,
157
143
  };
158
144
  }
159
145
  processTRX(jsonSuite) {
@@ -289,171 +275,6 @@ class XmlReader {
289
275
  tests,
290
276
  };
291
277
  }
292
- deduplicateTestsByFQN(tests) {
293
- const fqnMap = new Map();
294
- tests.forEach(test => {
295
- const fqn = this.generateNormalizedFQN(test);
296
- if (fqnMap.has(fqn)) {
297
- const existingTest = fqnMap.get(fqn);
298
- // For parameterized tests, merge as Examples
299
- if (test.example && Array.isArray(test.example) && test.example.length > 0) {
300
- // Initialize examples array if it doesn't exist
301
- if (!existingTest.examples) {
302
- existingTest.examples = [];
303
- // Add the existing test's example as the first item if it has parameters
304
- if (existingTest.example && Array.isArray(existingTest.example) && existingTest.example.length > 0) {
305
- existingTest.examples.push({
306
- parameters: existingTest.example,
307
- status: existingTest.status,
308
- run_time: existingTest.run_time,
309
- message: existingTest.message,
310
- stack: existingTest.stack,
311
- });
312
- // Clear the main test's example since it's now in examples array
313
- delete existingTest.example;
314
- }
315
- }
316
- // Add this test's execution as an example
317
- existingTest.examples.push({
318
- parameters: test.example,
319
- status: test.status,
320
- run_time: test.run_time,
321
- message: test.message,
322
- stack: test.stack,
323
- });
324
- // Update the main test status to reflect the worst status
325
- if (test.status === 'failed' || existingTest.status === 'failed') {
326
- existingTest.status = 'failed';
327
- }
328
- else if (test.status === 'skipped' && existingTest.status !== 'failed') {
329
- existingTest.status = 'skipped';
330
- }
331
- // Update total run time
332
- existingTest.run_time = (existingTest.run_time || 0) + (test.run_time || 0);
333
- }
334
- else {
335
- // Merge test properties for non-parameterized tests, prioritizing Test Explorer structure
336
- if (test.test_id && !existingTest.test_id) {
337
- existingTest.test_id = test.test_id;
338
- }
339
- // Keep the most complete test data
340
- if (test.stack && !existingTest.stack) {
341
- existingTest.stack = test.stack;
342
- }
343
- if (test.message && !existingTest.message) {
344
- existingTest.message = test.message;
345
- }
346
- }
347
- // Prefer Test Explorer structure (longer, more complete suite_title)
348
- if (test.suite_title && test.suite_title.length > existingTest.suite_title.length) {
349
- existingTest.suite_title = test.suite_title;
350
- }
351
- // Always use the source file path if available
352
- if (test.file && test.file.endsWith('.cs')) {
353
- existingTest.file = test.file;
354
- }
355
- else if (!existingTest.file || !existingTest.file.endsWith('.cs')) {
356
- existingTest.file = this.extractCsFileFromPath(test);
357
- }
358
- }
359
- else {
360
- // Fix file path to use proper .cs file names from source paths
361
- if (!test.file || !test.file.endsWith('.cs')) {
362
- test.file = this.extractCsFileFromPath(test);
363
- }
364
- fqnMap.set(fqn, test);
365
- }
366
- });
367
- return Array.from(fqnMap.values());
368
- }
369
- generateFQN(test) {
370
- // Generate Fully Qualified Name: Namespace + Class + Method (standard .NET FQN)
371
- // Don't include assembly as it can vary between different test structures
372
- const namespace = this.extractNamespace(test);
373
- const className = this.extractClassName(test);
374
- const methodName = test.title;
375
- // Use the most complete namespace.class structure available
376
- if (test.suite_title && test.suite_title.includes('.')) {
377
- return `${test.suite_title}.${methodName}`;
378
- }
379
- return `${namespace}.${className}.${methodName}`;
380
- }
381
- generateNormalizedFQN(test) {
382
- // Generate normalized FQN for deduplication by extracting the core namespace.class.method
383
- // For parameterized tests, we want the SAME FQN so they merge into one test with multiple Examples
384
- const fullClassName = test.suite_title || '';
385
- const methodName = test.title;
386
- // Extract the most specific namespace.class pattern
387
- if (fullClassName.includes('.')) {
388
- const parts = fullClassName.split('.');
389
- if (parts.length >= 2) {
390
- const className = parts[parts.length - 1];
391
- // Look for common .NET namespace patterns and normalize them:
392
- // TestProject.Tests.MyClass -> Tests.MyClass
393
- // Tests.MyClass -> Tests.MyClass
394
- // MyProject.SubNamespace.Tests.MyClass -> Tests.MyClass
395
- let normalizedNamespace = '';
396
- for (let i = parts.length - 2; i >= 0; i--) {
397
- const part = parts[i];
398
- // Build namespace from right to left, excluding project names
399
- if (part === 'Tests' || part.endsWith('Tests') || part.includes('Test')) {
400
- // Found a test namespace, use it as the normalized namespace
401
- normalizedNamespace = part;
402
- break;
403
- }
404
- else if (i === parts.length - 2) {
405
- // If no test namespace found, use the immediate parent as namespace
406
- normalizedNamespace = part;
407
- }
408
- }
409
- return `${normalizedNamespace}.${className}.${methodName}`;
410
- }
411
- }
412
- // Fallback for simple class names
413
- return `${fullClassName}.${methodName}`;
414
- }
415
- extractAssemblyName(test) {
416
- // Extract assembly name from file path or use default
417
- if (test.file) {
418
- const parts = test.file.split(/[/\\]/);
419
- return parts[0] || 'DefaultAssembly';
420
- }
421
- return 'DefaultAssembly';
422
- }
423
- extractNamespace(test) {
424
- // Extract namespace from suite_title or classname
425
- if (test.suite_title && test.suite_title.includes('.')) {
426
- const parts = test.suite_title.split('.');
427
- return parts.slice(0, -1).join('.');
428
- }
429
- return test.suite_title || 'DefaultNamespace';
430
- }
431
- extractClassName(test) {
432
- // Extract class name from suite_title
433
- if (test.suite_title && test.suite_title.includes('.')) {
434
- const parts = test.suite_title.split('.');
435
- return parts[parts.length - 1];
436
- }
437
- return test.suite_title || 'DefaultClass';
438
- }
439
- extractCsFileFromPath(test) {
440
- // Extract .cs file name from source file path, not namespace
441
- if (test.file) {
442
- // Look for actual .cs file path patterns
443
- const csFileMatch = test.file.match(/([^/\\]+\.cs)$/);
444
- if (csFileMatch) {
445
- return test.file;
446
- }
447
- // If no .cs extension, assume it's a namespace path and convert to likely file name
448
- const className = this.extractClassName(test);
449
- const pathParts = test.file.split(/[/\\]/);
450
- pathParts[pathParts.length - 1] = `${className}.cs`;
451
- return pathParts.join('/');
452
- }
453
- // Fallback to class name
454
- const className = this.extractClassName(test);
455
- return `${className}.cs`;
456
- }
457
278
  calculateStats() {
458
279
  this.stats = {
459
280
  ...this.stats,
@@ -503,9 +324,7 @@ class XmlReader {
503
324
  return;
504
325
  }
505
326
  const contents = fs_1.default.readFileSync(file).toString();
506
- // Try original test name first (for parameterized tests), fallback to regular title
507
- const titleForLookup = t.originalTestName ? t.originalTestName.replace(/\(.*?\)/, '').trim() : t.title;
508
- t.code = (0, utils_js_1.fetchSourceCode)(contents, { ...t, title: titleForLookup, lang: this.stats.language });
327
+ t.code = (0, utils_js_1.fetchSourceCode)(contents, { ...t, lang: this.stats.language });
509
328
  if (t.code)
510
329
  debug('Fetched code for test %s', t.title);
511
330
  t.test_id = (0, utils_js_1.fetchIdFromCode)(t.code, { lang: this.stats.language });
@@ -611,12 +430,7 @@ function reduceTestCases(prev, item) {
611
430
  testCases
612
431
  .filter(t => !!t)
613
432
  .forEach(testCaseItem => {
614
- // Simple file extraction (version 2.1.1 approach) with fallback to enhanced extraction
615
- let file = testCaseItem.file || item.filepath || item.fullname || item.package || '';
616
- // If no file found with simple approach and we have enhanced extraction enabled, use it
617
- if (!file && item.filepath) {
618
- file = extractSourceFilePath(testCaseItem, item);
619
- }
433
+ const file = testCaseItem.file || item.filepath || item.fullname || item.package || '';
620
434
  let stack = '';
621
435
  let message = '';
622
436
  if (testCaseItem.error)
@@ -640,31 +454,13 @@ function reduceTestCases(prev, item) {
640
454
  // SpecFlow config
641
455
  let { title, tags, testId } = fetchProperties(isParametrized ? item : testCaseItem);
642
456
  let example = null;
643
- // Simple suite title extraction (version 2.1.1 approach) with fallback to enhanced
644
- let suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
645
- if (!suiteTitle && item.fullname) {
646
- suiteTitle = extractTestExplorerSuiteTitle(testCaseItem, item);
647
- }
457
+ const suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
648
458
  title ||= testCaseItem.name || testCaseItem.methodname || testCaseItem.classname;
649
459
  tags ||= [];
650
- // Store original test name for enhanced parameter extraction
651
- const originalTestName = testCaseItem.name || testCaseItem.methodname;
652
- // Enhanced NUnit-style arguments from <arguments> element
653
- if (testCaseItem.arguments && testCaseItem.arguments.arg) {
654
- const args = Array.isArray(testCaseItem.arguments.arg)
655
- ? testCaseItem.arguments.arg
656
- : [testCaseItem.arguments.arg];
657
- example = args; // Store as array instead of object
658
- // Remove parameters from title for NUnit tests
659
- title = (testCaseItem.methodname || title).replace(/\(.*?\)/, '').trim();
660
- }
661
- else {
662
- // Simple parameter extraction (version 2.1.1 approach)
663
- const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
664
- if (exampleMatches) {
665
- example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
666
- title = title.replace(/\(.*?\)/, '').trim();
667
- }
460
+ const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
461
+ if (exampleMatches) {
462
+ example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
463
+ title = title.replace(/\(.*?\)/, '').trim();
668
464
  }
669
465
  stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
670
466
  if (!testId)
@@ -712,7 +508,6 @@ function reduceTestCases(prev, item) {
712
508
  run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
713
509
  status,
714
510
  title,
715
- originalTestName, // Store original name for enhanced features
716
511
  root_suite_id: TESTOMATIO_SUITE,
717
512
  suite_title: suiteTitle,
718
513
  files,
@@ -721,102 +516,6 @@ function reduceTestCases(prev, item) {
721
516
  });
722
517
  return prev;
723
518
  }
724
- function extractSourceFilePath(testCaseItem, item) {
725
- // Priority order for file path extraction to match Test Explorer structure:
726
- // 1. filepath attribute (direct .cs file path from NUnit)
727
- // 2. fullname (contains full project path)
728
- // 3. file attribute from test case
729
- // 4. package (fallback)
730
- // NUnit provides filepath attribute with actual .cs file path - use this first
731
- if (item.filepath) {
732
- // Clean up Windows/Unix path separators and ensure proper format
733
- let filePath = item.filepath.replace(/\\/g, '/');
734
- // Make relative to current working directory if absolute
735
- if (path_1.default.isAbsolute(item.filepath)) {
736
- const cwd = process.cwd().replace(/\\/g, '/');
737
- if (filePath.startsWith(cwd)) {
738
- filePath = path_1.default.relative(cwd, item.filepath).replace(/\\/g, '/');
739
- }
740
- else {
741
- // Try to extract relative path from common patterns
742
- const commonPatterns = ['/Tests/', '/test/', '/src/', '/Test/'];
743
- for (const pattern of commonPatterns) {
744
- const index = filePath.lastIndexOf(pattern);
745
- if (index !== -1) {
746
- filePath = filePath.substring(index + 1);
747
- break;
748
- }
749
- }
750
- }
751
- }
752
- return filePath;
753
- }
754
- if (testCaseItem.file) {
755
- let filePath = testCaseItem.file.replace(/\\/g, '/');
756
- // Make relative to current working directory if absolute
757
- if (path_1.default.isAbsolute(testCaseItem.file)) {
758
- const cwd = process.cwd().replace(/\\/g, '/');
759
- if (filePath.startsWith(cwd)) {
760
- filePath = path_1.default.relative(cwd, testCaseItem.file).replace(/\\/g, '/');
761
- }
762
- else {
763
- // Try to extract relative path from common patterns
764
- const commonPatterns = ['/Tests/', '/test/', '/src/', '/Test/'];
765
- for (const pattern of commonPatterns) {
766
- const index = filePath.lastIndexOf(pattern);
767
- if (index !== -1) {
768
- filePath = filePath.substring(index + 1);
769
- break;
770
- }
771
- }
772
- }
773
- }
774
- return filePath;
775
- }
776
- if (item.fullname) {
777
- // Extract actual file path from fullname if it contains path separators
778
- const fullnameParts = item.fullname.split('.');
779
- if (fullnameParts.length > 2) {
780
- // For ParameterizedMethod, get the class name (not method name)
781
- // Example: "NUnit_sample_test.Tests.SampleTests.TestBooleanValue" -> "Tests/SampleTests.cs"
782
- let namespaceParts, className;
783
- if (item.type === 'ParameterizedMethod') {
784
- // For parameterized methods, the last part is the method name, second-to-last is class
785
- namespaceParts = fullnameParts.slice(1, -2); // Skip project name and method name
786
- className = fullnameParts[fullnameParts.length - 2]; // Get class name
787
- }
788
- else {
789
- // For regular classes/fixtures
790
- namespaceParts = fullnameParts.slice(1, -1); // Skip project name
791
- className = fullnameParts[fullnameParts.length - 1];
792
- }
793
- return `${namespaceParts.join('/')}/${className}.cs`;
794
- }
795
- }
796
- if (item.package)
797
- return item.package.replace(/\\/g, '/');
798
- // Fallback: construct from classname
799
- if (testCaseItem.classname) {
800
- const parts = testCaseItem.classname.split('.');
801
- const className = parts[parts.length - 1];
802
- const namespacePath = parts.slice(0, -1).join('/');
803
- return `${namespacePath}/${className}.cs`;
804
- }
805
- return '';
806
- }
807
- function extractTestExplorerSuiteTitle(testCaseItem, item) {
808
- // Extract suite title to match Test Explorer structure (Project/Namespace hierarchy)
809
- // Priority: fullname > classname > name
810
- if (item.fullname) {
811
- // Use fullname to maintain Test Explorer structure
812
- return item.fullname;
813
- }
814
- if (testCaseItem.classname) {
815
- return testCaseItem.classname;
816
- }
817
- // Fallback to item name but prefer classname structure
818
- return item.name || testCaseItem.classname || 'UnknownClass';
819
- }
820
519
  function processTestSuite(testsuite) {
821
520
  if (!testsuite)
822
521
  return [];
@@ -828,8 +527,7 @@ function processTestSuite(testsuite) {
828
527
  if (!Array.isArray(testsuite)) {
829
528
  suites = [testsuite];
830
529
  }
831
- // Simple approach from version 2.1.1 with enhanced processing for complex scenarios
832
- const subSuites = suites.filter(s => s['test-suite'] && !s['test-case']);
530
+ const subSuites = suites.filter(s => s['test-suite'] && !testsuite['test-case']);
833
531
  return [...subSuites.map(s => processTestSuite(s['test-suite'])), ...suites.reduce(reduceTestCases, [])].flat();
834
532
  }
835
533
  function fetchProperties(item) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.3.5-beta-5-xml-import",
3
+ "version": "2.3.5-beta.8-xml-import",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"