@testomatio/reporter 2.3.6 β†’ 2.3.7-beta.2-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 CHANGED
@@ -13,7 +13,7 @@ Testomat.io Reporter (this npm package) supports:
13
13
  - πŸ”Ž [Stack traces](./docs/stacktrace.md) and error messages
14
14
  - πŸ™ [GitHub](./docs/pipes/github.md), [GitLab](./docs/pipes/gitlab.md) & [Bitbucket](./docs/pipes/bitbucket.md) integration
15
15
  - πŸš… Realtime reports
16
- - πŸ—ƒοΈ Other test frameworks supported via [JUNit XML](./docs/junit.md)
16
+ - πŸ—ƒοΈ Other test frameworks supported via [JUnit XML](./docs/junit.md) with [XML import configuration](./docs/xml-imports.md)
17
17
  - πŸšΆβ€β™€οΈ Steps _(work in progress)_
18
18
  - πŸ“„ [Logger](./docs/logger.md) _(work in progress, supports Jest for now)_
19
19
  - ☁️ Custom properties and metadata _(work in progress)_
package/lib/bin/cli.js CHANGED
@@ -145,7 +145,7 @@ program
145
145
  .option('--lang <lang>', 'Language used (python, ruby, java)')
146
146
  .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
147
147
  .action(async (pattern, opts) => {
148
- if (!pattern.endsWith('.xml')) {
148
+ if (!pattern.endsWith('.xml') && !pattern.includes('*')) {
149
149
  pattern += '.xml';
150
150
  }
151
151
  let { javaTests, lang } = opts;
@@ -25,7 +25,7 @@ program
25
25
  .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
26
26
  .option('--env-file <envfile>', 'Load environment variables from env file')
27
27
  .action(async (pattern, opts) => {
28
- if (!pattern.endsWith('.xml')) {
28
+ if (!pattern.endsWith('.xml') && !pattern.includes('*')) {
29
29
  pattern += '.xml';
30
30
  }
31
31
  let { javaTests, lang } = opts;
@@ -37,7 +37,10 @@ program
37
37
  lang = lang?.toLowerCase();
38
38
  if (javaTests === true || (lang === 'java' && !javaTests))
39
39
  javaTests = 'src/test/java';
40
- const runReader = new xmlReader_js_1.default({ javaTests, lang });
40
+ const runReader = new xmlReader_js_1.default({
41
+ javaTests,
42
+ lang,
43
+ });
41
44
  const files = glob_1.glob.sync(pattern, { cwd: opts.dir || process.cwd() });
42
45
  if (!files.length) {
43
46
  console.log(constants_js_1.APP_PREFIX, `Report can't be created. No XML files found πŸ˜₯`);
@@ -35,7 +35,7 @@ while (i < args.length) {
35
35
  newArgs[0] = 'start';
36
36
  }
37
37
  else if (arg === '--finish') {
38
- // Map --finish to finish command
38
+ // Map --finish to finish command
39
39
  newArgs[0] = 'finish';
40
40
  }
41
41
  else {
@@ -46,8 +46,8 @@ while (i < args.length) {
46
46
  }
47
47
  // Execute the main CLI with mapped arguments
48
48
  const child = (0, node_child_process_1.spawn)(process.execPath, [cliPath, ...newArgs], {
49
- stdio: 'inherit'
49
+ stdio: 'inherit',
50
50
  });
51
- child.on('exit', (code) => {
51
+ child.on('exit', code => {
52
52
  process.exit(code);
53
53
  });
package/lib/client.js CHANGED
@@ -70,8 +70,8 @@ class Client {
70
70
  this.pipeStore = {};
71
71
  this.runId = '';
72
72
  this.queue = Promise.resolve();
73
- // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
74
- const pathToPackageJSON = path_1.default.join(__dirname, '../package.json');
73
+ // Get package.json path - use a simple approach that works in both environments
74
+ const pathToPackageJSON = path_1.default.join(process.cwd(), 'package.json');
75
75
  try {
76
76
  this.version = JSON.parse(fs_1.default.readFileSync(pathToPackageJSON).toString()).version;
77
77
  console.log(constants_js_1.APP_PREFIX, `Testomatio Reporter v${this.version}`);
@@ -329,7 +329,11 @@ class Client {
329
329
  */
330
330
  formatLogs({ error, steps, logs }) {
331
331
  error = error?.trim();
332
- logs = logs?.trim().split('\n').map(l => (0, utils_js_1.truncate)(l)).join('\n');
332
+ logs = logs
333
+ ?.trim()
334
+ .split('\n')
335
+ .map(l => (0, utils_js_1.truncate)(l))
336
+ .join('\n');
333
337
  if (Array.isArray(steps)) {
334
338
  steps = steps
335
339
  .map(step => (0, utils_js_1.formatStep)(step))
@@ -1,5 +1,4 @@
1
1
  export default CSharpAdapter;
2
2
  declare class CSharpAdapter extends Adapter {
3
- getFilePath(t: any): string;
4
3
  }
5
4
  import Adapter from './adapter.js';
@@ -7,24 +7,57 @@ 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
- const title = t.title.replace(/\(.*?\)/, '').trim();
11
- const example = t.title.match(/\((.*?)\)/);
12
- if (example)
13
- t.example = { ...example[1].split(',') };
10
+ // Extract example from title if not already present
11
+ if (!t.example) {
12
+ const exampleMatch = t.title.match(/\((.*?)\)/);
13
+ if (exampleMatch) {
14
+ // Extract parameters as object with numeric keys for API
15
+ const params = exampleMatch[1].split(',').map(param => param.trim());
16
+ t.example = {};
17
+ params.forEach((param, index) => {
18
+ t.example[index] = param;
19
+ });
20
+ }
21
+ }
22
+ // For runs: keep full title with parameters for display
23
+ // The example field will be used for grouping on import
24
+ // Do NOT remove parameters from title
14
25
  const suite = t.suite_title.split('.');
15
26
  t.suite_title = suite.pop();
16
27
  t.file = namespaceToFileName(t.file);
17
- t.title = title.trim();
18
28
  return t;
19
29
  }
20
30
  getFilePath(t) {
21
- const fileName = namespaceToFileName(t.file);
31
+ if (!t.file)
32
+ return null;
33
+ // Normalize path separators for cross-platform compatibility
34
+ let filePath = t.file.replace(/\\/g, '/');
35
+ // If file already has .cs extension, use it directly
36
+ if (filePath.endsWith('.cs')) {
37
+ // Make relative path if it's absolute
38
+ if (path_1.default.isAbsolute(filePath)) {
39
+ // Try to find project-relative path
40
+ const cwd = process.cwd().replace(/\\/g, '/');
41
+ if (filePath.startsWith(cwd)) {
42
+ filePath = path_1.default.relative(cwd, filePath).replace(/\\/g, '/');
43
+ }
44
+ }
45
+ return filePath;
46
+ }
47
+ // Convert namespace path to file path
48
+ const fileName = namespaceToFileName(filePath);
22
49
  return fileName;
23
50
  }
24
51
  }
25
52
  module.exports = CSharpAdapter;
26
53
  function namespaceToFileName(fileName) {
54
+ if (!fileName)
55
+ return '';
56
+ // If already a .cs file path, clean it up
57
+ if (fileName.endsWith('.cs')) {
58
+ return fileName.replace(/\\/g, '/');
59
+ }
27
60
  const fileParts = fileName.split('.');
28
61
  fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
29
- return `${fileParts.join(path_1.default.sep)}.cs`;
62
+ return `${fileParts.join('/')}.cs`;
30
63
  }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Enhanced NUnit XML Parser that properly handles test-suite hierarchy
3
+ * and parameterized tests
4
+ */
5
+ export class NUnitXmlParser {
6
+ constructor(options?: {});
7
+ options: {};
8
+ tests: any[];
9
+ stats: {
10
+ total: number;
11
+ passed: number;
12
+ failed: number;
13
+ skipped: number;
14
+ inconclusive: number;
15
+ };
16
+ /**
17
+ * Parse NUnit XML test-run structure
18
+ * @param {Object} testRun - Parsed XML test-run object
19
+ * @returns {Object} - Parsed test results
20
+ */
21
+ parseTestRun(testRun: any): any;
22
+ /**
23
+ * Recursively parse test-suite elements based on their type
24
+ * @param {Object|Array} testSuite - Test suite object or array
25
+ * @param {Array} parentPath - Current path in the hierarchy
26
+ */
27
+ parseTestSuite(testSuite: any | any[], parentPath?: any[]): void;
28
+ /**
29
+ * Process child elements of a test suite
30
+ * @param {Object} testSuite - Test suite object
31
+ * @param {Array} currentPath - Current path in hierarchy
32
+ */
33
+ processChildren(testSuite: any, currentPath: any[]): void;
34
+ /**
35
+ * Parse test-case elements (actual tests)
36
+ * @param {Object|Array} testCases - Test case object or array
37
+ * @param {Array} suitePath - Path to the test suite
38
+ * @param {Object} parentSuite - Parent test suite for context
39
+ */
40
+ parseTestCases(testCases: any | any[], suitePath: any[], parentSuite: any): void;
41
+ /**
42
+ * Parse individual test case
43
+ * @param {Object} testCase - Test case object
44
+ * @param {Array} suitePath - Path to the test suite
45
+ * @param {Object} parentSuite - Parent test suite for context
46
+ * @returns {Object|null} - Parsed test object
47
+ */
48
+ parseTestCase(testCase: any, suitePath: any[], parentSuite: any): any | null;
49
+ /**
50
+ * Extract method name and parameters from test name
51
+ * @param {string} testName - Full test name
52
+ * @returns {Object} - Extracted information
53
+ */
54
+ extractParameters(testName: string): any;
55
+ /**
56
+ * Parse parameter string into array of parameters
57
+ * @param {string} paramString - Parameter string
58
+ * @returns {Array} - Array of parameters
59
+ */
60
+ parseParameterString(paramString: string): any[];
61
+ /**
62
+ * Extract method name from test name (fallback)
63
+ * @param {string} testName - Test name
64
+ * @returns {string} - Method name
65
+ */
66
+ extractMethodName(testName: string): string;
67
+ /**
68
+ * Build file path from suite path and class name
69
+ * @param {Array} suitePath - Suite path array
70
+ * @param {string} className - Class name
71
+ * @param {Object} parentSuite - Parent suite for context
72
+ * @returns {string} - File path
73
+ */
74
+ buildFilePath(suitePath: any[], className: string, parentSuite: any): string;
75
+ /**
76
+ * Group parameterized tests by base method name
77
+ * @param {Array} tests - Array of parsed tests
78
+ * @returns {Object} - Grouped tests
79
+ */
80
+ groupParameterizedTests(tests: any[]): any;
81
+ }
82
+ export default NUnitXmlParser;
@@ -0,0 +1,369 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NUnitXmlParser = void 0;
7
+ const debug_1 = __importDefault(require("debug"));
8
+ const constants_js_1 = require("../constants.js");
9
+ const debug = (0, debug_1.default)('@testomatio/reporter:nunit-parser');
10
+ /**
11
+ * Enhanced NUnit XML Parser that properly handles test-suite hierarchy
12
+ * and parameterized tests
13
+ */
14
+ class NUnitXmlParser {
15
+ constructor(options = {}) {
16
+ this.options = options;
17
+ this.tests = [];
18
+ this.stats = {
19
+ total: 0,
20
+ passed: 0,
21
+ failed: 0,
22
+ skipped: 0,
23
+ inconclusive: 0,
24
+ };
25
+ }
26
+ /**
27
+ * Parse NUnit XML test-run structure
28
+ * @param {Object} testRun - Parsed XML test-run object
29
+ * @returns {Object} - Parsed test results
30
+ */
31
+ parseTestRun(testRun) {
32
+ debug('Parsing NUnit test-run');
33
+ // Extract run-level statistics
34
+ this.stats = {
35
+ total: parseInt(testRun.total || 0, 10),
36
+ passed: parseInt(testRun.passed || 0, 10),
37
+ failed: parseInt(testRun.failed || 0, 10),
38
+ skipped: parseInt(testRun.skipped || 0, 10),
39
+ inconclusive: parseInt(testRun.inconclusive || 0, 10),
40
+ };
41
+ // Process the root test-suite
42
+ if (testRun['test-suite']) {
43
+ this.parseTestSuite(testRun['test-suite'], []);
44
+ }
45
+ debug(`Parsed ${this.tests.length} tests from NUnit XML`);
46
+ return {
47
+ status: testRun.result?.toLowerCase() || 'unknown',
48
+ create_tests: true,
49
+ tests_count: this.tests.length,
50
+ passed_count: this.tests.filter(t => t.status === constants_js_1.STATUS.PASSED).length,
51
+ failed_count: this.tests.filter(t => t.status === constants_js_1.STATUS.FAILED).length,
52
+ skipped_count: this.tests.filter(t => t.status === constants_js_1.STATUS.SKIPPED).length,
53
+ tests: this.tests,
54
+ };
55
+ }
56
+ /**
57
+ * Recursively parse test-suite elements based on their type
58
+ * @param {Object|Array} testSuite - Test suite object or array
59
+ * @param {Array} parentPath - Current path in the hierarchy
60
+ */
61
+ parseTestSuite(testSuite, parentPath = []) {
62
+ if (!testSuite)
63
+ return;
64
+ // Handle arrays of test suites
65
+ if (Array.isArray(testSuite)) {
66
+ testSuite.forEach(suite => this.parseTestSuite(suite, parentPath));
67
+ return;
68
+ }
69
+ const suiteType = testSuite.type;
70
+ const suiteName = testSuite.name;
71
+ const fullName = testSuite.fullname;
72
+ debug(`Processing test-suite: type=${suiteType}, name=${suiteName}`);
73
+ switch (suiteType) {
74
+ case 'Assembly':
75
+ // Assembly level - ignore the name, just process children
76
+ debug('Processing Assembly level - ignoring name, processing children');
77
+ this.processChildren(testSuite, parentPath);
78
+ break;
79
+ case 'TestSuite':
80
+ // Namespace/grouping level - add to path but don't create test
81
+ debug(`Processing TestSuite level - adding '${suiteName}' to path`);
82
+ const newPath = [...parentPath, suiteName];
83
+ this.processChildren(testSuite, newPath);
84
+ break;
85
+ case 'TestFixture':
86
+ // Test class level - add to path and process test cases
87
+ debug(`Processing TestFixture level - test class '${suiteName}'`);
88
+ const testFixturePath = [...parentPath, suiteName];
89
+ this.processChildren(testSuite, testFixturePath);
90
+ break;
91
+ default:
92
+ debug(`Unknown test-suite type: ${suiteType}, treating as TestSuite`);
93
+ const unknownPath = [...parentPath, suiteName];
94
+ this.processChildren(testSuite, unknownPath);
95
+ break;
96
+ }
97
+ }
98
+ /**
99
+ * Process child elements of a test suite
100
+ * @param {Object} testSuite - Test suite object
101
+ * @param {Array} currentPath - Current path in hierarchy
102
+ */
103
+ processChildren(testSuite, currentPath) {
104
+ // Process nested test-suites
105
+ if (testSuite['test-suite']) {
106
+ this.parseTestSuite(testSuite['test-suite'], currentPath);
107
+ }
108
+ // Process test-cases
109
+ if (testSuite['test-case']) {
110
+ this.parseTestCases(testSuite['test-case'], currentPath, testSuite);
111
+ }
112
+ }
113
+ /**
114
+ * Parse test-case elements (actual tests)
115
+ * @param {Object|Array} testCases - Test case object or array
116
+ * @param {Array} suitePath - Path to the test suite
117
+ * @param {Object} parentSuite - Parent test suite for context
118
+ */
119
+ parseTestCases(testCases, suitePath, parentSuite) {
120
+ if (!testCases)
121
+ return;
122
+ // Handle arrays of test cases
123
+ if (!Array.isArray(testCases)) {
124
+ testCases = [testCases];
125
+ }
126
+ testCases.forEach(testCase => {
127
+ const parsedTest = this.parseTestCase(testCase, suitePath, parentSuite);
128
+ if (parsedTest) {
129
+ this.tests.push(parsedTest);
130
+ }
131
+ });
132
+ }
133
+ /**
134
+ * Parse individual test case
135
+ * @param {Object} testCase - Test case object
136
+ * @param {Array} suitePath - Path to the test suite
137
+ * @param {Object} parentSuite - Parent test suite for context
138
+ * @returns {Object|null} - Parsed test object
139
+ */
140
+ parseTestCase(testCase, suitePath, parentSuite) {
141
+ if (!testCase || !testCase.name) {
142
+ debug('Skipping test case without name');
143
+ return null;
144
+ }
145
+ const testName = testCase.name;
146
+ const fullName = testCase.fullname;
147
+ const methodName = testCase.methodname || this.extractMethodName(testName);
148
+ const className = testCase.classname || parentSuite?.name;
149
+ debug(`Parsing test case: ${testName}`);
150
+ // Extract parameters if this is a parameterized test
151
+ const { baseMethodName, parameters, isParameterized } = this.extractParameters(testName);
152
+ // Determine test status
153
+ let status = constants_js_1.STATUS.PASSED;
154
+ if (testCase.result) {
155
+ switch (testCase.result.toLowerCase()) {
156
+ case 'passed':
157
+ status = constants_js_1.STATUS.PASSED;
158
+ break;
159
+ case 'failed':
160
+ status = constants_js_1.STATUS.FAILED;
161
+ break;
162
+ case 'skipped':
163
+ case 'ignored':
164
+ status = constants_js_1.STATUS.SKIPPED;
165
+ break;
166
+ case 'inconclusive':
167
+ status = constants_js_1.STATUS.SKIPPED; // Treat inconclusive as skipped
168
+ break;
169
+ default:
170
+ status = constants_js_1.STATUS.PASSED;
171
+ }
172
+ }
173
+ // Extract error information
174
+ let message = '';
175
+ let stack = '';
176
+ if (testCase.failure) {
177
+ message = testCase.failure.message || '';
178
+ stack = testCase.failure['stack-trace'] || testCase.failure['#text'] || '';
179
+ }
180
+ if (testCase.output && testCase.output['#text']) {
181
+ stack = `${stack}\n\n${testCase.output['#text']}`.trim();
182
+ }
183
+ // Extract test ID from properties
184
+ let testId = null;
185
+ if (testCase.properties && testCase.properties.property) {
186
+ const properties = Array.isArray(testCase.properties.property)
187
+ ? testCase.properties.property
188
+ : [testCase.properties.property];
189
+ const idProperty = properties.find(p => p.name === 'ID');
190
+ if (idProperty) {
191
+ testId = idProperty.value;
192
+ // Remove @ and T prefixes if present
193
+ if (testId.startsWith('@'))
194
+ testId = testId.slice(1);
195
+ if (testId.startsWith('T'))
196
+ testId = testId.slice(1);
197
+ }
198
+ }
199
+ // Build file path from suite path and class name
200
+ const filePath = this.buildFilePath(suitePath, className, parentSuite);
201
+ // For parameterized tests, format example as expected by Testomatio API
202
+ // Convert array of parameters to object with numeric keys
203
+ let example = null;
204
+ if (isParameterized && parameters.length > 0) {
205
+ example = {};
206
+ parameters.forEach((param, index) => {
207
+ example[index] = param;
208
+ });
209
+ }
210
+ return {
211
+ // For runs: use full test name with parameters (TestBooleanValue(true))
212
+ // For import: API will group by base name using the example field
213
+ title: testName, // Full name with parameters for run display
214
+ methodName: baseMethodName || methodName || testName,
215
+ fullName: fullName,
216
+ suitePath: suitePath,
217
+ suite_title: className || suitePath[suitePath.length - 1] || 'Unknown',
218
+ file: filePath,
219
+ status: status,
220
+ message: message,
221
+ stack: stack,
222
+ run_time: parseFloat(testCase.duration || testCase.time || 0) * 1000,
223
+ test_id: testId,
224
+ create: true,
225
+ retry: false,
226
+ // Parameterized test metadata
227
+ example: example, // Parameters as object for API grouping
228
+ isParameterized: isParameterized,
229
+ parameters: parameters, // Keep original array for reference
230
+ baseMethodName: baseMethodName,
231
+ };
232
+ }
233
+ /**
234
+ * Extract method name and parameters from test name
235
+ * @param {string} testName - Full test name
236
+ * @returns {Object} - Extracted information
237
+ */
238
+ extractParameters(testName) {
239
+ const paramMatch = testName.match(/^(.+?)\((.+)\)$/);
240
+ if (paramMatch) {
241
+ const baseMethodName = paramMatch[1].trim();
242
+ const paramString = paramMatch[2];
243
+ // Parse parameters - handle quoted strings and nested structures
244
+ const parameters = this.parseParameterString(paramString);
245
+ return {
246
+ baseMethodName: baseMethodName,
247
+ parameters: parameters,
248
+ isParameterized: true,
249
+ };
250
+ }
251
+ return {
252
+ baseMethodName: testName,
253
+ parameters: [],
254
+ isParameterized: false,
255
+ };
256
+ }
257
+ /**
258
+ * Parse parameter string into array of parameters
259
+ * @param {string} paramString - Parameter string
260
+ * @returns {Array} - Array of parameters
261
+ */
262
+ parseParameterString(paramString) {
263
+ const parameters = [];
264
+ let current = '';
265
+ let inQuotes = false;
266
+ let quoteChar = null;
267
+ let depth = 0;
268
+ for (let i = 0; i < paramString.length; i++) {
269
+ const char = paramString[i];
270
+ if (!inQuotes && (char === '"' || char === "'")) {
271
+ inQuotes = true;
272
+ quoteChar = char;
273
+ current += char;
274
+ }
275
+ else if (inQuotes && char === quoteChar) {
276
+ inQuotes = false;
277
+ quoteChar = null;
278
+ current += char;
279
+ }
280
+ else if (!inQuotes && char === '(') {
281
+ depth++;
282
+ current += char;
283
+ }
284
+ else if (!inQuotes && char === ')') {
285
+ depth--;
286
+ current += char;
287
+ }
288
+ else if (!inQuotes && char === ',' && depth === 0) {
289
+ parameters.push(current.trim());
290
+ current = '';
291
+ }
292
+ else {
293
+ current += char;
294
+ }
295
+ }
296
+ if (current.trim()) {
297
+ parameters.push(current.trim());
298
+ }
299
+ // Clean up parameters - remove quotes if they wrap the entire parameter
300
+ return parameters.map(param => {
301
+ param = param.trim();
302
+ if ((param.startsWith('"') && param.endsWith('"')) || (param.startsWith("'") && param.endsWith("'"))) {
303
+ return param.slice(1, -1);
304
+ }
305
+ return param;
306
+ });
307
+ }
308
+ /**
309
+ * Extract method name from test name (fallback)
310
+ * @param {string} testName - Test name
311
+ * @returns {string} - Method name
312
+ */
313
+ extractMethodName(testName) {
314
+ // Remove parameters if present
315
+ const paramMatch = testName.match(/^(.+?)\(/);
316
+ return paramMatch ? paramMatch[1].trim() : testName;
317
+ }
318
+ /**
319
+ * Build file path from suite path and class name
320
+ * @param {Array} suitePath - Suite path array
321
+ * @param {string} className - Class name
322
+ * @param {Object} parentSuite - Parent suite for context
323
+ * @returns {string} - File path
324
+ */
325
+ buildFilePath(suitePath, className, parentSuite) {
326
+ // Try to get file path from parent suite
327
+ if (parentSuite && parentSuite.filepath) {
328
+ return parentSuite.filepath;
329
+ }
330
+ // Build path from suite hierarchy
331
+ const pathParts = [...suitePath];
332
+ if (className && !pathParts.includes(className)) {
333
+ pathParts.push(className);
334
+ }
335
+ // Convert to file path format
336
+ return pathParts.join('/') + '.cs'; // Assume C# for NUnit
337
+ }
338
+ /**
339
+ * Group parameterized tests by base method name
340
+ * @param {Array} tests - Array of parsed tests
341
+ * @returns {Object} - Grouped tests
342
+ */
343
+ groupParameterizedTests(tests) {
344
+ const grouped = {};
345
+ tests.forEach(test => {
346
+ const key = test.isParameterized
347
+ ? `${test.suitePath.join('.')}.${test.baseMethodName}`
348
+ : `${test.suitePath.join('.')}.${test.title}`;
349
+ if (!grouped[key]) {
350
+ grouped[key] = {
351
+ baseTest: {
352
+ name: test.baseMethodName || test.title,
353
+ suitePath: test.suitePath,
354
+ suite_title: test.suite_title,
355
+ file: test.file,
356
+ isParameterized: test.isParameterized,
357
+ },
358
+ variations: [],
359
+ };
360
+ }
361
+ grouped[key].variations.push(test);
362
+ });
363
+ return grouped;
364
+ }
365
+ }
366
+ exports.NUnitXmlParser = NUnitXmlParser;
367
+ module.exports = NUnitXmlParser;
368
+
369
+ module.exports.NUnitXmlParser = NUnitXmlParser;
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 ?? true,
21
+ isEnabled: this.params.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
22
22
  intervalFunction: null,
23
23
  intervalTime: 5000,
24
24
  tests: [],