@testomatio/reporter 2.3.8 → 2.3.9-beta-bin-fix

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.
@@ -116,26 +116,19 @@ const isValidUrl = s => {
116
116
  }
117
117
  };
118
118
  exports.isValidUrl = isValidUrl;
119
- const fileMatchRegex = /file:(\/*)([A-Za-z]:[\\/].*?|\/.*?)\.(png|avi|webm|jpg|html|txt)/gi;
119
+ const fileMatchRegex = /file:(\/+(?:[A-Za-z]:[\\/]|\/)?[^\s]*?\.(png|avi|webm|jpg|html|txt))/gi;
120
120
  const fetchFilesFromStackTrace = (stack = '', checkExists = true) => {
121
- let files = Array.from(stack.matchAll(fileMatchRegex))
122
- .map(match => {
123
- // match[0] is full match, match[1] is slashes, match[2] is path, match[3] is extension
124
- const slashes = match[1] || '';
125
- const path = match[2];
126
- const extension = match[3];
127
- return `${slashes}${path}.${extension}`;
128
- })
129
- .map(f => f.trim())
121
+ const files = Array.from(stack.matchAll(fileMatchRegex))
122
+ .map(f => f[1].trim())
130
123
  .map(f => f.replace(/^\/+/, '/').replace(/^\/([A-Za-z]:)/, '$1')) // Remove extra slashes, handle Windows paths
131
124
  .map(f => {
132
- // Normalize path separators for cross-platform compatibility
133
- return f.replace(/\\/g, '/');
125
+ // Convert Windows paths to Linux paths for testing purposes
126
+ if (f.match(/^[A-Za-z]:[\\\/]/)) {
127
+ // Convert Windows path to Linux equivalent for test scenarios
128
+ return f.replace(/^[A-Za-z]:[\\\/]/, '/').replace(/\\/g, '/');
129
+ }
130
+ return f;
134
131
  });
135
- // If we're not checking file existence, remove Windows drive letters for consistency
136
- if (!checkExists) {
137
- files = files.map(f => f.replace(/^([A-Za-z]):/, ''));
138
- }
139
132
  debug('Found files in stack trace: ', files);
140
133
  return files.filter(f => {
141
134
  if (!checkExists)
@@ -151,88 +144,19 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
151
144
  const stackLines = stack
152
145
  .split('\n')
153
146
  .filter(l => l.includes(':'))
147
+ // .map(l => l.match(/\[(.*?)\]/)?.[1] || l) // minitest format
148
+ // .map(l => l.split(':')[0])
154
149
  .map(l => l.trim())
155
- .map(l => {
156
- // Remove 'at ' prefix if present
157
- if (l.startsWith('at ')) {
158
- return l.substring(3).trim();
159
- }
160
- // Find the part that looks like a file path with line number
161
- const parts = l.split(' ');
162
- for (const part of parts) {
163
- // Check if this part has a colon
164
- if (part.includes(':')) {
165
- // For Windows paths, we need to handle drive letters (C:, D:, etc.)
166
- // Split by colon but keep drive letter with the path
167
- const colonParts = part.split(':');
168
- let filePath;
169
- // Check if first part is a Windows drive letter (single letter)
170
- if (colonParts.length >= 2 && colonParts[0].length === 1 && /[A-Za-z]/.test(colonParts[0])) {
171
- // Windows path like D:\path\file.php:24
172
- // Reconstruct as D:\path\file.php
173
- filePath = colonParts[0] + ':' + colonParts[1];
174
- }
175
- else {
176
- // Unix path like /path/file.php:24
177
- filePath = colonParts[0];
178
- }
179
- // Only consider it valid if the file exists
180
- if (fs_1.default.existsSync(filePath)) {
181
- return part;
182
- }
183
- }
184
- }
185
- // If no valid file path found in parts, return the whole line
186
- // It will be filtered out later if it's not a valid file path
187
- return parts.find(p => p.includes(':')) || l;
188
- })
189
- .filter(l => {
190
- // Extract file path from line (accounting for Windows drive letters)
191
- if (!l)
192
- return false;
193
- const colonParts = l.split(':');
194
- let filePath;
195
- if (colonParts.length >= 2 && colonParts[0].length === 1 && /[A-Za-z]/.test(colonParts[0])) {
196
- // Windows path
197
- filePath = colonParts[0] + ':' + colonParts[1];
198
- }
199
- else {
200
- // Unix path
201
- filePath = colonParts[0];
202
- }
203
- return filePath && fs_1.default.existsSync(filePath);
204
- })
150
+ .map(l => l.split(' ').find(p => p.includes(':')) || '')
151
+ .filter(l => (0, is_valid_path_1.default)(l?.split(':')[0]))
205
152
  // // filter out 3rd party libs
206
153
  .filter(l => !l?.includes(`vendor${path_1.sep}`))
207
154
  .filter(l => !l?.includes(`node_modules${path_1.sep}`))
208
- .filter(l => {
209
- // Extract file path for final check (accounting for Windows drive letters)
210
- const colonParts = l.split(':');
211
- let filePath;
212
- if (colonParts.length >= 2 && colonParts[0].length === 1 && /[A-Za-z]/.test(colonParts[0])) {
213
- filePath = colonParts[0] + ':' + colonParts[1];
214
- }
215
- else {
216
- filePath = colonParts[0];
217
- }
218
- return fs_1.default.lstatSync(filePath).isFile();
219
- });
155
+ .filter(l => fs_1.default.existsSync(l.split(':')[0]))
156
+ .filter(l => fs_1.default.lstatSync(l.split(':')[0]).isFile());
220
157
  if (!stackLines.length)
221
158
  return '';
222
- // Extract file and line number (accounting for Windows drive letters)
223
- const firstLine = stackLines[0];
224
- const colonParts = firstLine.split(':');
225
- let file, line;
226
- if (colonParts.length >= 3 && colonParts[0].length === 1 && /[A-Za-z]/.test(colonParts[0])) {
227
- // Windows path like D:\path\file.php:24
228
- file = colonParts[0] + ':' + colonParts[1];
229
- line = colonParts[2];
230
- }
231
- else {
232
- // Unix path like /path/file.php:24
233
- file = colonParts[0];
234
- line = colonParts[1];
235
- }
159
+ const [file, line] = stackLines[0].split(':');
236
160
  const prepend = 3;
237
161
  const source = fetchSourceCode(fs_1.default.readFileSync(file).toString(), { line, prepend, limit: 7 });
238
162
  if (!source)
@@ -250,8 +174,6 @@ exports.fetchSourceCodeFromStackTrace = fetchSourceCodeFromStackTrace;
250
174
  exports.TEST_ID_REGEX = /@T([\w\d]{8})/;
251
175
  exports.SUITE_ID_REGEX = /@S([\w\d]{8})/;
252
176
  const fetchIdFromCode = (code, opts = {}) => {
253
- if (!code)
254
- return null;
255
177
  const comments = code
256
178
  .split('\n')
257
179
  .map(l => l.trim())
@@ -294,58 +216,10 @@ const fetchSourceCode = (contents, opts = {}) => {
294
216
  lineIndex = lines.findIndex(l => l.includes(`${title}(`));
295
217
  }
296
218
  else if (opts.lang === 'csharp') {
297
- // Find the method declaration line
298
- let methodLineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
299
- if (methodLineIndex === -1) {
300
- methodLineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
301
- }
302
- if (methodLineIndex === -1) {
303
- methodLineIndex = lines.findIndex(l => l.includes(`${title}(`));
304
- }
305
- // If found, scan upwards to find [TestCase], [Test] attributes and XML comments
306
- if (methodLineIndex !== -1) {
307
- lineIndex = methodLineIndex;
308
- // Scan upwards to find the start of attributes and comments
309
- for (let i = methodLineIndex - 1; i >= 0; i--) {
310
- const trimmedLine = lines[i].trim();
311
- // Include [TestCase], [Test], and other attributes
312
- if (trimmedLine.startsWith('[')) {
313
- lineIndex = i;
314
- continue;
315
- }
316
- // Include XML documentation comments
317
- if (trimmedLine.startsWith('///')) {
318
- lineIndex = i;
319
- continue;
320
- }
321
- // Stop at empty lines (with some tolerance)
322
- if (trimmedLine === '') {
323
- // Check if next non-empty line is an attribute or comment
324
- let hasMoreAttributes = false;
325
- for (let j = i - 1; j >= 0; j--) {
326
- const nextTrimmed = lines[j].trim();
327
- if (nextTrimmed === '')
328
- continue;
329
- if (nextTrimmed.startsWith('[') || nextTrimmed.startsWith('///')) {
330
- hasMoreAttributes = true;
331
- lineIndex = j;
332
- }
333
- break;
334
- }
335
- if (!hasMoreAttributes)
336
- break;
337
- continue;
338
- }
339
- // Stop at other method declarations or class-level elements
340
- if (trimmedLine.includes('public ') ||
341
- trimmedLine.includes('private ') ||
342
- trimmedLine.includes('protected ') ||
343
- trimmedLine.includes('internal ')) {
344
- if (!trimmedLine.startsWith('['))
345
- break;
346
- }
347
- }
348
- }
219
+ if (lineIndex === -1)
220
+ lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
221
+ if (lineIndex === -1)
222
+ lineIndex = lines.findIndex(l => l.includes(`${title}(`));
349
223
  }
350
224
  else {
351
225
  lineIndex = lines.findIndex(l => l.includes(title));
@@ -354,28 +228,11 @@ const fetchSourceCode = (contents, opts = {}) => {
354
228
  if (opts.prepend) {
355
229
  lineIndex -= opts.prepend;
356
230
  }
357
- if (lineIndex !== -1 && lineIndex !== undefined) {
231
+ if (lineIndex) {
358
232
  const result = [];
359
- let braceDepth = 0; // Track brace depth for C# methods
360
- let methodStartFound = false; // Flag to indicate we've found the method opening brace
361
233
  for (let i = lineIndex; i < lineIndex + limit; i++) {
362
234
  if (lines[i] === undefined)
363
235
  continue;
364
- // Track brace depth for C# to stop after method closes
365
- if (opts.lang === 'csharp') {
366
- const line = lines[i];
367
- // Count opening and closing braces
368
- const openBraces = (line.match(/\{/g) || []).length;
369
- const closeBraces = (line.match(/\}/g) || []).length;
370
- if (openBraces > 0)
371
- methodStartFound = true;
372
- braceDepth += openBraces - closeBraces;
373
- // If we've started the method and depth returns to 0, method is complete
374
- if (methodStartFound && braceDepth === 0 && closeBraces > 0) {
375
- // Don't include the closing brace - just break
376
- break;
377
- }
378
- }
379
236
  if (i > lineIndex + 2 && !opts.prepend) {
380
237
  // annotation
381
238
  if (opts.lang === 'php' && lines[i].trim().startsWith('#['))
@@ -414,24 +271,6 @@ const fetchSourceCode = (contents, opts = {}) => {
414
271
  break;
415
272
  if (opts.lang === 'java' && lines[i].includes(' class '))
416
273
  break;
417
- // For C#, additional checks if brace tracking didn't stop us
418
- if (opts.lang === 'csharp') {
419
- const trimmed = lines[i].trim();
420
- // Stop at attribute that marks beginning of next test (but not if we're still in the current method)
421
- if (trimmed.match(/^\[(Test|TestCase|Theory|Fact)/) && methodStartFound && braceDepth === 0)
422
- break;
423
- // Stop at XML documentation comments that belong to next method
424
- if (trimmed.startsWith('///') && methodStartFound && braceDepth === 0)
425
- break;
426
- // Stop at another method declaration (but not if we're still in the current method)
427
- if (trimmed.match(/^\s*(public|private|protected|internal)\s+(\w+|async\s+\w+)\s+\w+\s*\(/) &&
428
- methodStartFound &&
429
- braceDepth === 0)
430
- break;
431
- // Stop at class declaration
432
- if (trimmed.includes(' class ') && trimmed.includes('public'))
433
- break;
434
- }
435
274
  }
436
275
  result.push(lines[i]);
437
276
  }
@@ -632,14 +471,10 @@ function transformEnvVarToBoolean(value) {
632
471
  return Boolean(value);
633
472
  }
634
473
  function truncate(s, size = 255) {
635
- if (s === undefined || s === null) {
636
- return '';
637
- }
638
- const str = s.toString();
639
- if (str.trim().length < size) {
640
- return str;
474
+ if (s.toString().trim().length < size) {
475
+ return s.toString();
641
476
  }
642
- return `${str.substring(0, size)}...`;
477
+ return `${s.toString().substring(0, size)}...`;
643
478
  }
644
479
 
645
480
  module.exports.getPackageVersion = getPackageVersion;
@@ -19,11 +19,25 @@ declare class XmlReader {
19
19
  tests: any[];
20
20
  stats: {};
21
21
  uploader: S3Uploader;
22
- enhancedNunit: boolean;
23
- groupParameterized: boolean;
24
22
  version: any;
25
23
  connectAdapter(): import("./junit-adapter/adapter.js").default;
26
- parse(fileName: any): any;
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
+ };
27
41
  processJUnit(jsonSuite: any): {
28
42
  create_tests: boolean;
29
43
  duration: number;
@@ -35,14 +49,15 @@ declare class XmlReader {
35
49
  tests: any[];
36
50
  tests_count: number;
37
51
  };
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;
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
+ };
46
61
  processTRX(jsonSuite: any): {
47
62
  status: string;
48
63
  create_tests: boolean;
@@ -52,27 +67,6 @@ declare class XmlReader {
52
67
  failed_count: number;
53
68
  tests: any;
54
69
  };
55
- _parseTRXTestDefinition(td: any): {
56
- title: any;
57
- example: any;
58
- file: string;
59
- description: any;
60
- suite_title: any;
61
- id: any;
62
- };
63
- _parseTRXTestResult(td: any, tests: any): {
64
- suite_title: any;
65
- title: any;
66
- file: any;
67
- description: any;
68
- code: any;
69
- run_time: number;
70
- stack: any;
71
- files: any;
72
- create: boolean;
73
- overwrite: boolean;
74
- };
75
- _mapTRXStatus(outcome: any): string;
76
70
  processXUnit(assemblies: any): {
77
71
  status: string;
78
72
  create_tests: boolean;
package/lib/xmlReader.js CHANGED
@@ -11,7 +11,6 @@ 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");
15
14
  const utils_js_1 = require("./utils/utils.js");
16
15
  const index_js_1 = require("./pipe/index.js");
17
16
  const index_js_2 = __importDefault(require("./junit-adapter/index.js"));
@@ -21,7 +20,7 @@ const uploader_js_1 = require("./uploader.js");
21
20
  const debug = (0, debug_1.default)('@testomatio/reporter:xml');
22
21
  const ridRunId = (0, crypto_1.randomUUID)();
23
22
  const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
24
- const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE, TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED, TESTOMATIO_LEGACY_NUNIT, } = process.env;
23
+ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE, TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED, } = process.env;
25
24
  const options = {
26
25
  ignoreDeclaration: true,
27
26
  ignoreAttributes: false,
@@ -55,10 +54,6 @@ class XmlReader {
55
54
  this.stats = {};
56
55
  this.stats.language = opts.lang?.toLowerCase();
57
56
  this.uploader = new uploader_js_1.S3Uploader();
58
- // Enhanced NUnit parsing - enabled by default for NUnit XML
59
- // Can be disabled via opts.enhancedNunit = false or TESTOMATIO_LEGACY_NUNIT=1
60
- this.enhancedNunit = !(0, utils_js_1.transformEnvVarToBoolean)(TESTOMATIO_LEGACY_NUNIT);
61
- this.groupParameterized = opts.groupParameterized !== false; // Default true, can be disabled
62
57
  // @ts-ignore
63
58
  const packageJsonPath = path_1.default.resolve(__dirname, '..', 'package.json');
64
59
  this.version = JSON.parse(fs_1.default.readFileSync(packageJsonPath).toString()).version;
@@ -107,8 +102,7 @@ class XmlReader {
107
102
  return this.processJUnit(jsonSuite);
108
103
  }
109
104
  processJUnit(jsonSuite) {
110
- const { testsuite, name, failures, errors } = jsonSuite;
111
- const tests = testsuite?.tests || jsonSuite.tests;
105
+ const { testsuite, name, tests, failures, errors } = jsonSuite;
112
106
  reduceOptions.preferClassname = this.stats.language === 'python';
113
107
  const resultTests = processTestSuite(testsuite);
114
108
  const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
@@ -134,13 +128,6 @@ class XmlReader {
134
128
  };
135
129
  }
136
130
  processNUnit(jsonSuite) {
137
- // Use enhanced NUnit parser if enabled and this is actually NUnit XML
138
- if (this.enhancedNunit && this.isNUnitXml(jsonSuite)) {
139
- debug('Using enhanced NUnit parser');
140
- return this.processNUnitEnhanced(jsonSuite);
141
- }
142
- // Fallback to legacy parser for backward compatibility
143
- debug('Using legacy NUnit parser');
144
131
  const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
145
132
  reduceOptions.preferClassname = this.stats.language === 'python';
146
133
  const resultTests = processTestSuite(jsonSuite['test-suite']);
@@ -155,58 +142,65 @@ class XmlReader {
155
142
  tests: resultTests,
156
143
  };
157
144
  }
158
- /**
159
- * Check if the XML is actually NUnit format (has test-suite hierarchy)
160
- * @param {Object} jsonSuite - Parsed XML suite object
161
- * @returns {boolean} - True if this is NUnit XML format
162
- */
163
- isNUnitXml(jsonSuite) {
164
- // NUnit XML has test-suite elements with type attributes
165
- if (jsonSuite['test-suite']) {
166
- const testSuite = Array.isArray(jsonSuite['test-suite']) ? jsonSuite['test-suite'][0] : jsonSuite['test-suite'];
167
- // Check for NUnit-specific test-suite types
168
- return (testSuite &&
169
- testSuite.type &&
170
- ['Assembly', 'TestSuite', 'TestFixture', 'ParameterizedMethod'].includes(testSuite.type));
171
- }
172
- return false;
173
- }
174
- processNUnitEnhanced(jsonSuite) {
175
- debug('Processing NUnit XML with enhanced parser');
176
- try {
177
- const nunitParser = new nunit_parser_js_1.NUnitXmlParser({
178
- groupParameterized: this.groupParameterized,
179
- ...this.opts,
180
- });
181
- const result = nunitParser.parseTestRun(jsonSuite);
182
- // Add parsed tests to our collection
183
- this.tests = this.tests.concat(result.tests);
184
- debug(`Enhanced NUnit parser processed ${result.tests.length} tests`);
185
- return result;
186
- }
187
- catch (error) {
188
- debug('Enhanced NUnit parser failed, falling back to legacy parser:', error.message);
189
- console.warn(`${constants_js_1.APP_PREFIX} Enhanced NUnit parsing failed, using legacy parser: ${error.message}`);
190
- // Fallback to legacy parser
191
- this.enhancedNunit = false;
192
- return this.processNUnit(jsonSuite);
193
- }
194
- }
195
145
  processTRX(jsonSuite) {
196
146
  let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
197
147
  if (!Array.isArray(defs))
198
148
  defs = [defs].filter(d => !!d);
199
- // Parse test definitions
200
- const tests = defs.map(td => this._parseTRXTestDefinition(td));
201
- // Parse test results
149
+ const tests = defs.map(td => {
150
+ const title = td.name.replace(/\(.*?\)/, '').trim();
151
+ let example = td.name.match(/\((.*?)\)/);
152
+ if (example)
153
+ example = { ...example[1].split(',') };
154
+ const suite = td.TestMethod.className.split(', ')[0].split('.');
155
+ const suite_title = suite.pop();
156
+ return {
157
+ title,
158
+ example,
159
+ file: suite.join('/'),
160
+ description: td.Description,
161
+ suite_title,
162
+ id: td.Execution.id,
163
+ };
164
+ }) || [];
202
165
  let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
203
166
  if (!Array.isArray(result))
204
167
  result = [result].filter(d => !!d);
205
- const results = result.map(td => this._parseTRXTestResult(td, tests));
168
+ const results = result.map(td => ({
169
+ id: td.executionId,
170
+ // seconds are used in junit reports, but ms are used by testomatio
171
+ run_time: parseFloat(td.duration) * 1000,
172
+ status: td.outcome,
173
+ stack: td.Output.StdOut,
174
+ files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
175
+ }));
176
+ results.forEach(r => {
177
+ const test = tests.find(t => t.id === r.id) || {};
178
+ r.suite_title = test.suite_title;
179
+ r.title = test.title?.trim();
180
+ if (test.code)
181
+ r.code = test.code;
182
+ if (test.description)
183
+ r.description = test.description;
184
+ if (test.example)
185
+ r.example = test.example;
186
+ if (test.file)
187
+ r.file = test.file;
188
+ r.create = true;
189
+ r.overwrite = true;
190
+ if (r.status === 'Passed')
191
+ r.status = constants_js_1.STATUS.PASSED;
192
+ if (r.status === 'Failed')
193
+ r.status = constants_js_1.STATUS.FAILED;
194
+ if (r.status === 'Skipped')
195
+ r.status = constants_js_1.STATUS.SKIPPED;
196
+ delete r.id;
197
+ });
206
198
  debug(results);
207
199
  const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
208
200
  const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
209
- const status = failed_count > 0 ? constants_js_1.STATUS.FAILED : constants_js_1.STATUS.PASSED.toString();
201
+ let status = constants_js_1.STATUS.PASSED.toString();
202
+ if (failed_count > 0)
203
+ status = constants_js_1.STATUS.FAILED;
210
204
  this.tests = results.filter(t => !!t.title);
211
205
  return {
212
206
  status,
@@ -218,59 +212,6 @@ class XmlReader {
218
212
  tests: results,
219
213
  };
220
214
  }
221
- _parseTRXTestDefinition(td) {
222
- const title = td.name.replace(/\(.*?\)/, '').trim();
223
- const exampleMatch = td.name.match(/\((.*?)\)/);
224
- const example = exampleMatch ? {
225
- ...exampleMatch[1].split(',').map(p => p.trim()).filter(p => p !== '')
226
- } : null;
227
- const suite = td.TestMethod.className.split(', ')[0].split('.');
228
- const suite_title = suite.pop();
229
- // Convert namespace to file path for C#
230
- const file = `${suite.join('/')}.cs`;
231
- return {
232
- title, // Base name without parameters for test import
233
- example, // Parameters object for parameterized tests
234
- file, // File path with .cs extension
235
- description: td.Description,
236
- suite_title,
237
- id: td.Execution.id,
238
- };
239
- }
240
- _parseTRXTestResult(td, tests) {
241
- const test = tests.find(t => t.id === td.executionId) || {};
242
- const result = {
243
- suite_title: test.suite_title,
244
- title: test.title?.trim(),
245
- file: test.file,
246
- description: test.description,
247
- code: test.code,
248
- run_time: parseFloat(td.duration) * 1000,
249
- stack: td.Output?.StdOut || '',
250
- files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
251
- create: true,
252
- overwrite: true,
253
- };
254
- // Add example for parameterized tests
255
- if (test.example) {
256
- result.example = test.example;
257
- }
258
- // Map TRX status to Testomat.io status
259
- result.status = this._mapTRXStatus(td.outcome);
260
- return result;
261
- }
262
- _mapTRXStatus(outcome) {
263
- switch (outcome) {
264
- case 'Passed':
265
- return constants_js_1.STATUS.PASSED;
266
- case 'Failed':
267
- return constants_js_1.STATUS.FAILED;
268
- case 'Skipped':
269
- return constants_js_1.STATUS.SKIPPED;
270
- default:
271
- return constants_js_1.STATUS.PASSED;
272
- }
273
- }
274
215
  processXUnit(assemblies) {
275
216
  const tests = [];
276
217
  assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
@@ -518,7 +459,7 @@ function reduceTestCases(prev, item) {
518
459
  tags ||= [];
519
460
  const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
520
461
  if (exampleMatches) {
521
- example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')).filter(v => v !== '') };
462
+ example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
522
463
  title = title.replace(/\(.*?\)/, '').trim();
523
464
  }
524
465
  stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
package/package.json CHANGED
@@ -1,13 +1,11 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.3.8",
3
+ "version": "2.3.9-beta-bin-fix",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
7
7
  },
8
- "main": "lib/reporter.js",
9
- "module": "src/reporter.js",
10
- "types": "types/types.d.ts",
8
+ "typings": "typings/index.d.ts",
11
9
  "repository": "git@github.com:testomatio/reporter.git",
12
10
  "author": "Michael Bodnarchuk <davert@testomat.io>,Koushik Mohan <koushikmohan1996@gmail.com>",
13
11
  "license": "MIT",
@@ -47,8 +45,7 @@
47
45
  "bin",
48
46
  "lib",
49
47
  "src",
50
- "testcafe",
51
- "types"
48
+ "testcafe"
52
49
  ],
53
50
  "scripts": {
54
51
  "clear-exportdir": "rm -rf export/",
@@ -60,8 +57,7 @@
60
57
  "test": "mocha 'tests/unit/**/*_test.js'",
61
58
  "test:playwright": "mocha tests/adapter/playwright.test.js",
62
59
  "test:codecept": "mocha tests/adapter/codecept.test.js tests/adapter/codecept_comprehensive.test.js tests/adapter/codecept_steps_sections.test.js",
63
- "test:vitest": "mocha tests/adapter/vitest.test.js",
64
- "test:frameworks": "npm run test:playwright && npm run test:codecept && npm run test:vitest",
60
+ "test:frameworks": "npm run test:playwright && npm run test:codecept",
65
61
  "test:all": "npm run test && npm run test:frameworks",
66
62
  "test:adapters": "mocha tests/adapter/*.test.js",
67
63
  "test:codecept:bug948": "mocha tests/adapter/codecept_aftersuite_failure.test.js",
@@ -106,7 +102,7 @@
106
102
  "vitest": "^1.6.0"
107
103
  },
108
104
  "bin": {
109
- "@testomatio/reporter": "./lib/bin/cli.js",
105
+ "testomatio/reporter": "./lib/bin/cli.js",
110
106
  "report-xml": "./lib/bin/reportXml.js",
111
107
  "start-test-run": "./lib/bin/startTest.js",
112
108
  "upload-artifacts": "./lib/bin/uploadArtifacts.js"