@testomatio/reporter 2.3.7-beta.1-xml-import → 2.3.7-beta.11-stack-artifacts

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.
@@ -20,7 +20,7 @@ if (process.env.TESTOMATIO_RUN) process.env.runId = process.env.TESTOMATIO_RUN;
20
20
  class TestomatioPipe {
21
21
  constructor(params, store) {
22
22
  this.batch = {
23
- isEnabled: params?.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
23
+ isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
24
24
  intervalFunction: null, // will be created in createRun by setInterval function
25
25
  intervalTime: 5000, // how often tests are sent
26
26
  tests: [], // array of tests in batch
@@ -60,8 +60,8 @@ class TestomatioPipe {
60
60
  retryConfig: {
61
61
  retry: REPORTER_REQUEST_RETRIES.retriesPerRequest,
62
62
  retryDelay: REPORTER_REQUEST_RETRIES.retryTimeout,
63
- httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
64
- shouldRetry: error => {
63
+ httpMethodsToRetry: ['GET','PUT','HEAD','OPTIONS','DELETE','POST'],
64
+ shouldRetry: (error) => {
65
65
  if (!error.response) return false;
66
66
  switch (error.response?.status) {
67
67
  case 400: // Bad request (probably wrong API key)
@@ -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
 
80
80
  this.isEnabled = true;
@@ -104,6 +104,7 @@ class TestomatioPipe {
104
104
  // add test ID + run ID
105
105
  if (data.rid) data.rid = `${this.runId}-${data.rid}`;
106
106
 
107
+
107
108
  if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
108
109
  data.stack = null;
109
110
  }
@@ -119,6 +120,7 @@ class TestomatioPipe {
119
120
  return data;
120
121
  }
121
122
 
123
+
122
124
  /**
123
125
  * Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
124
126
  * @param {Object} opts - The options for preparing the test grepList.
@@ -213,7 +215,7 @@ class TestomatioPipe {
213
215
  method: 'PUT',
214
216
  url: `/api/reporter/${this.runId}`,
215
217
  data: runParams,
216
- responseType: 'json',
218
+ responseType: 'json'
217
219
  });
218
220
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
219
221
  return;
@@ -226,7 +228,7 @@ class TestomatioPipe {
226
228
  url: '/api/reporter',
227
229
  data: runParams,
228
230
  maxContentLength: Infinity,
229
- responseType: 'json',
231
+ responseType: 'json'
230
232
  });
231
233
 
232
234
  this.runId = resp.data.uid;
@@ -285,44 +287,44 @@ class TestomatioPipe {
285
287
 
286
288
  debug('Adding test', json);
287
289
 
288
- return this.client
289
- .request({
290
- method: 'POST',
291
- url: `/api/reporter/${this.runId}/testrun`,
292
- data: json,
293
- headers: {
294
- 'Content-Type': 'application/json',
295
- },
296
- maxContentLength: Infinity,
297
- })
298
- .catch(err => {
299
- this.requestFailures++;
300
- this.notReportedTestsCount++;
301
- if (err.response) {
302
- if (err.response.status >= 400) {
303
- const responseData = err.response.data || { message: '' };
304
- console.log(
305
- APP_PREFIX,
306
- pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
307
- pc.gray(data?.title || ''),
308
- );
309
- if (err.response?.data?.message?.includes('could not be matched')) {
310
- this.hasUnmatchedTests = true;
311
- }
312
- return;
313
- }
290
+ return this.client.request({
291
+ method: 'POST',
292
+ url: `/api/reporter/${this.runId}/testrun`,
293
+ data: json,
294
+ headers: {
295
+ 'Content-Type': 'application/json',
296
+ },
297
+ maxContentLength: Infinity
298
+ }).catch(err => {
299
+ this.requestFailures++;
300
+ this.notReportedTestsCount++;
301
+ if (err.response) {
302
+ if (err.response.status >= 400) {
303
+ const responseData = err.response.data || { message: '' };
314
304
  console.log(
315
305
  APP_PREFIX,
316
- pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
317
- `Report couldn't be processed: ${err?.response?.data?.message}`,
306
+ pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
307
+ pc.gray(data?.title || ''),
318
308
  );
319
- printCreateIssue(err);
320
- } else {
321
- console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
309
+ if (err.response?.data?.message?.includes('could not be matched')) {
310
+ this.hasUnmatchedTests = true;
311
+ }
312
+ return;
322
313
  }
323
- });
314
+ console.log(
315
+ APP_PREFIX,
316
+ pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
317
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
318
+ );
319
+ printCreateIssue(err);
320
+ } else {
321
+ console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
322
+ }
323
+ });
324
324
  };
325
325
 
326
+
327
+
326
328
  /**
327
329
  * Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
328
330
  */
@@ -347,42 +349,43 @@ class TestomatioPipe {
347
349
  const testsToSend = this.batch.tests.splice(0);
348
350
  debug('📨 Batch upload', testsToSend.length, 'tests');
349
351
 
350
- return this.client
351
- .request({
352
- method: 'POST',
353
- url: `/api/reporter/${this.runId}/testrun`,
354
- data: {
355
- api_key: this.apiKey,
356
- tests: testsToSend,
357
- batch_index: this.batch.batchIndex,
358
- },
359
- headers: {
360
- 'Content-Type': 'application/json',
361
- },
362
- maxContentLength: Infinity,
363
- })
364
- .catch(err => {
365
- this.requestFailures++;
366
- this.notReportedTestsCount += testsToSend.length;
367
- if (err.response) {
368
- if (err.response.status >= 400) {
369
- const responseData = err.response.data || { message: '' };
370
- console.log(APP_PREFIX, pc.yellow(`Warning: ${responseData.message} (${err.response.status})`));
371
- if (err.response?.data?.message?.includes('could not be matched')) {
372
- this.hasUnmatchedTests = true;
373
- }
374
- return;
375
- }
352
+ return this.client.request({
353
+ method: 'POST',
354
+ url: `/api/reporter/${this.runId}/testrun`,
355
+ data: {
356
+ api_key: this.apiKey,
357
+ tests: testsToSend,
358
+ batch_index: this.batch.batchIndex
359
+ },
360
+ headers: {
361
+ 'Content-Type': 'application/json',
362
+ },
363
+ maxContentLength: Infinity
364
+ }).catch(err => {
365
+ this.requestFailures++;
366
+ this.notReportedTestsCount += testsToSend.length;
367
+ if (err.response) {
368
+ if (err.response.status >= 400) {
369
+ const responseData = err.response.data || { message: '' };
376
370
  console.log(
377
371
  APP_PREFIX,
378
- pc.yellow(`Warning: (${err.response?.status})`),
379
- `Report couldn't be processed: ${err?.response?.data?.message}`,
372
+ pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
380
373
  );
381
- printCreateIssue(err);
382
- } else {
383
- console.log(APP_PREFIX, "Report couldn't be processed", err);
374
+ if (err.response?.data?.message?.includes('could not be matched')) {
375
+ this.hasUnmatchedTests = true;
376
+ }
377
+ return;
384
378
  }
385
- });
379
+ console.log(
380
+ APP_PREFIX,
381
+ pc.yellow(`Warning: (${err.response?.status})`),
382
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
383
+ );
384
+ printCreateIssue(err);
385
+ } else {
386
+ console.log(APP_PREFIX, "Report couldn't be processed", err);
387
+ }
388
+ });
386
389
  };
387
390
 
388
391
  /**
@@ -405,9 +408,9 @@ class TestomatioPipe {
405
408
  else this.batch.tests.push(data);
406
409
 
407
410
  // if test is added after run which is already finished
408
- if (!this.batch.intervalFunction) uploading = this.#batchUpload();
411
+ if (!this.batch.intervalFunction) uploading = this.#batchUpload();
409
412
 
410
- // return promise to be able to wait for it
413
+ // return promise to be able to wait for it
411
414
  return uploading;
412
415
  }
413
416
 
@@ -456,11 +459,9 @@ class TestomatioPipe {
456
459
  status_event,
457
460
  detach: params.detach,
458
461
  tests: params.tests,
459
- },
462
+ }
460
463
  });
461
464
 
462
- debug(APP_PREFIX, '✅ Testrun finished');
463
-
464
465
  if (this.runUrl) {
465
466
  console.log(APP_PREFIX, '📊 Report Saved. Report URL:', pc.magenta(this.runUrl));
466
467
  }
@@ -524,6 +525,9 @@ function printCreateIssue(err) {
524
525
  console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
525
526
  console.log('```');
526
527
  });
528
+
527
529
  }
528
530
 
531
+
532
+
529
533
  export default TestomatioPipe;
package/src/reporter.js CHANGED
@@ -1,8 +1,10 @@
1
- // import TestomatClient from './client.js';
2
- // import * as TRConstants from './constants.js';
1
+ import Client from './client.js';
2
+ import * as TestomatioConstants from './constants.js';
3
3
  import { services } from './services/index.js';
4
4
  import reporterFunctions from './reporter-functions.js';
5
5
 
6
+ export { Client };
7
+ export const STATUS = TestomatioConstants.STATUS;
6
8
  export const artifact = reporterFunctions.artifact;
7
9
  export const log = reporterFunctions.log;
8
10
  export const logger = services.logger;
@@ -35,6 +37,7 @@ export default {
35
37
  linkTest: reporterFunctions.linkTest,
36
38
  linkJira: reporterFunctions.linkJira,
37
39
 
38
- // TestomatClient,
39
- // TRConstants,
40
+ TestomatioClient: Client,
41
+ STATUS,
42
+
40
43
  };
package/src/uploader.js CHANGED
@@ -194,11 +194,6 @@ export class S3Uploader {
194
194
  filePath = path.join(process.cwd(), filePath);
195
195
  }
196
196
 
197
- // Normalize path separators for cross-platform compatibility
198
- if (typeof filePath === 'string') {
199
- filePath = filePath.replace(/\\/g, '/');
200
- }
201
-
202
197
  const data = { rid, file: filePath, uploaded };
203
198
  const jsonLine = `${JSON.stringify(data)}\n`;
204
199
  fs.appendFileSync(tempFilePath, jsonLine);
@@ -83,8 +83,12 @@ const fetchFilesFromStackTrace = (stack = '', checkExists = true) => {
83
83
  .map(f => f[1].trim())
84
84
  .map(f => f.replace(/^\/+/, '/').replace(/^\/([A-Za-z]:)/, '$1')) // Remove extra slashes, handle Windows paths
85
85
  .map(f => {
86
- // Normalize path separators for cross-platform compatibility
87
- return f.replace(/\\/g, '/');
86
+ // Convert Windows paths to Linux paths for testing purposes
87
+ if (f.match(/^[A-Za-z]:[\\\/]/)) {
88
+ // Convert Windows path to Linux equivalent for test scenarios
89
+ return f.replace(/^[A-Za-z]:[\\\/]/, '/').replace(/\\/g, '/');
90
+ }
91
+ return f;
88
92
  });
89
93
 
90
94
  debug('Found files in stack trace: ', files);
@@ -135,8 +139,6 @@ export const TEST_ID_REGEX = /@T([\w\d]{8})/;
135
139
  export const SUITE_ID_REGEX = /@S([\w\d]{8})/;
136
140
 
137
141
  const fetchIdFromCode = (code, opts = {}) => {
138
- if (!code) return null;
139
-
140
142
  const comments = code
141
143
  .split('\n')
142
144
  .map(l => l.trim())
@@ -178,32 +180,8 @@ const fetchSourceCode = (contents, opts = {}) => {
178
180
  if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
179
181
  if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
180
182
  } else if (opts.lang === 'csharp') {
181
- // Enhanced C# method detection for NUnit tests
182
- lineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
183
-
184
- if (lineIndex === -1) {
185
- lineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
186
- }
187
-
188
- if (lineIndex === -1) {
189
- lineIndex = lines.findIndex(l => l.includes(`${title}(`));
190
- }
191
-
192
- // Look for TestCase or Test attributes above the method
193
- if (lineIndex === -1) {
194
- const testAttributeIndex = lines.findIndex((l, index) => {
195
- if (l.includes('[TestCase') || l.includes('[Test')) {
196
- // Check next few lines for the method
197
- const nextLines = lines.slice(index, Math.min(lines.length, index + 5));
198
- const hasMethod = nextLines.some(nextLine => nextLine.includes(`${title}(`));
199
- return hasMethod;
200
- }
201
- return false;
202
- });
203
- if (testAttributeIndex !== -1) {
204
- lineIndex = testAttributeIndex;
205
- }
206
- }
183
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
184
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
207
185
  } else {
208
186
  lineIndex = lines.findIndex(l => l.includes(title));
209
187
  }
@@ -213,7 +191,7 @@ const fetchSourceCode = (contents, opts = {}) => {
213
191
  lineIndex -= opts.prepend;
214
192
  }
215
193
 
216
- if (lineIndex !== -1 && lineIndex !== undefined) {
194
+ if (lineIndex) {
217
195
  const result = [];
218
196
  for (let i = lineIndex; i < lineIndex + limit; i++) {
219
197
  if (lines[i] === undefined) continue;
@@ -238,10 +216,6 @@ const fetchSourceCode = (contents, opts = {}) => {
238
216
  if (opts.lang === 'java' && lines[i].trim().match(/^@\w+/)) break;
239
217
  if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
240
218
  if (opts.lang === 'java' && lines[i].includes(' class ')) break;
241
- if (opts.lang === 'csharp' && lines[i].trim().match(/^\[Test/)) break;
242
- if (opts.lang === 'csharp' && lines[i].includes(' public void ')) break;
243
- if (opts.lang === 'csharp' && lines[i].includes(' public async Task ')) break;
244
- if (opts.lang === 'csharp' && lines[i].includes(' class ') && lines[i].includes('public')) break;
245
219
  }
246
220
  result.push(lines[i]);
247
221
  }
@@ -454,8 +428,20 @@ function transformEnvVarToBoolean(value) {
454
428
  return Boolean(value);
455
429
  }
456
430
 
431
+ function truncate(s, size = 255) {
432
+ if (s === undefined || s === null) {
433
+ return '';
434
+ }
435
+ const str = s.toString();
436
+ if (str.trim().length < size) {
437
+ return str;
438
+ }
439
+ return `${str.substring(0, size)}...`;
440
+ }
441
+
457
442
  export {
458
443
  ansiRegExp,
444
+ truncate,
459
445
  cleanLatestRunId,
460
446
  isSameTest,
461
447
  fetchSourceCode,
package/src/xmlReader.js CHANGED
@@ -6,7 +6,6 @@ import { XMLParser } from 'fast-xml-parser';
6
6
  import { APP_PREFIX, STATUS } from './constants.js';
7
7
  import { randomUUID } from 'crypto';
8
8
  import { fileURLToPath } from 'url';
9
- import { NUnitXmlParser } from './junit-adapter/nunit-parser.js';
10
9
  import {
11
10
  fetchFilesFromStackTrace,
12
11
  fetchIdFromOutput,
@@ -76,10 +75,6 @@ class XmlReader {
76
75
  this.stats.language = opts.lang?.toLowerCase();
77
76
  this.uploader = new S3Uploader();
78
77
 
79
- // Enhanced NUnit parsing - enabled by default for NUnit XML
80
- this.enhancedNunit = opts.enhancedNunit !== false; // Default true, can be disabled
81
- this.groupParameterized = opts.groupParameterized !== false; // Default true, can be disabled
82
-
83
78
  // @ts-ignore
84
79
  const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
85
80
  this.version = JSON.parse(fs.readFileSync(packageJsonPath).toString()).version;
@@ -131,8 +126,7 @@ class XmlReader {
131
126
  }
132
127
 
133
128
  processJUnit(jsonSuite) {
134
- const { testsuite, name, failures, errors } = jsonSuite;
135
- const tests = testsuite?.tests || jsonSuite.tests;
129
+ const { testsuite, name, tests, failures, errors } = jsonSuite;
136
130
 
137
131
  reduceOptions.preferClassname = this.stats.language === 'python';
138
132
  const resultTests = processTestSuite(testsuite);
@@ -163,14 +157,6 @@ class XmlReader {
163
157
  }
164
158
 
165
159
  processNUnit(jsonSuite) {
166
- // Use enhanced NUnit parser if enabled and this is actually NUnit XML
167
- if (this.enhancedNunit && this.isNUnitXml(jsonSuite)) {
168
- debug('Using enhanced NUnit parser');
169
- return this.processNUnitEnhanced(jsonSuite);
170
- }
171
-
172
- // Fallback to legacy parser for backward compatibility
173
- debug('Using legacy NUnit parser');
174
160
  const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
175
161
 
176
162
  reduceOptions.preferClassname = this.stats.language === 'python';
@@ -189,53 +175,6 @@ class XmlReader {
189
175
  };
190
176
  }
191
177
 
192
- /**
193
- * Check if the XML is actually NUnit format (has test-suite hierarchy)
194
- * @param {Object} jsonSuite - Parsed XML suite object
195
- * @returns {boolean} - True if this is NUnit XML format
196
- */
197
- isNUnitXml(jsonSuite) {
198
- // NUnit XML has test-suite elements with type attributes
199
- if (jsonSuite['test-suite']) {
200
- const testSuite = Array.isArray(jsonSuite['test-suite']) ? jsonSuite['test-suite'][0] : jsonSuite['test-suite'];
201
-
202
- // Check for NUnit-specific test-suite types
203
- return (
204
- testSuite &&
205
- testSuite.type &&
206
- ['Assembly', 'TestSuite', 'TestFixture', 'ParameterizedMethod'].includes(testSuite.type)
207
- );
208
- }
209
- return false;
210
- }
211
-
212
- processNUnitEnhanced(jsonSuite) {
213
- debug('Processing NUnit XML with enhanced parser');
214
-
215
- try {
216
- const nunitParser = new NUnitXmlParser({
217
- groupParameterized: this.groupParameterized,
218
- ...this.opts,
219
- });
220
-
221
- const result = nunitParser.parseTestRun(jsonSuite);
222
-
223
- // Add parsed tests to our collection
224
- this.tests = this.tests.concat(result.tests);
225
-
226
- debug(`Enhanced NUnit parser processed ${result.tests.length} tests`);
227
-
228
- return result;
229
- } catch (error) {
230
- debug('Enhanced NUnit parser failed, falling back to legacy parser:', error.message);
231
- console.warn(`${APP_PREFIX} Enhanced NUnit parsing failed, using legacy parser: ${error.message}`);
232
-
233
- // Fallback to legacy parser
234
- this.enhancedNunit = false;
235
- return this.processNUnit(jsonSuite);
236
- }
237
- }
238
-
239
178
  processTRX(jsonSuite) {
240
179
  let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
241
180
  if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
@@ -1,82 +0,0 @@
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;