@testomatio/reporter 2.3.7-beta.3-xml-import → 2.3.7-beta.5-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.
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"));
@@ -55,9 +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
- this.enhancedNunit = opts.enhancedNunit !== false; // Default true, can be disabled
60
- this.groupParameterized = opts.groupParameterized !== false; // Default true, can be disabled
61
57
  // @ts-ignore
62
58
  const packageJsonPath = path_1.default.resolve(__dirname, '..', 'package.json');
63
59
  this.version = JSON.parse(fs_1.default.readFileSync(packageJsonPath).toString()).version;
@@ -106,8 +102,7 @@ class XmlReader {
106
102
  return this.processJUnit(jsonSuite);
107
103
  }
108
104
  processJUnit(jsonSuite) {
109
- const { testsuite, name, failures, errors } = jsonSuite;
110
- const tests = testsuite?.tests || jsonSuite.tests;
105
+ const { testsuite, name, tests, failures, errors } = jsonSuite;
111
106
  reduceOptions.preferClassname = this.stats.language === 'python';
112
107
  const resultTests = processTestSuite(testsuite);
113
108
  const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
@@ -133,13 +128,6 @@ class XmlReader {
133
128
  };
134
129
  }
135
130
  processNUnit(jsonSuite) {
136
- // Use enhanced NUnit parser if enabled and this is actually NUnit XML
137
- if (this.enhancedNunit && this.isNUnitXml(jsonSuite)) {
138
- debug('Using enhanced NUnit parser');
139
- return this.processNUnitEnhanced(jsonSuite);
140
- }
141
- // Fallback to legacy parser for backward compatibility
142
- debug('Using legacy NUnit parser');
143
131
  const { result, total, passed, failed, inconclusive, skipped } = jsonSuite;
144
132
  reduceOptions.preferClassname = this.stats.language === 'python';
145
133
  const resultTests = processTestSuite(jsonSuite['test-suite']);
@@ -154,58 +142,65 @@ class XmlReader {
154
142
  tests: resultTests,
155
143
  };
156
144
  }
157
- /**
158
- * Check if the XML is actually NUnit format (has test-suite hierarchy)
159
- * @param {Object} jsonSuite - Parsed XML suite object
160
- * @returns {boolean} - True if this is NUnit XML format
161
- */
162
- isNUnitXml(jsonSuite) {
163
- // NUnit XML has test-suite elements with type attributes
164
- if (jsonSuite['test-suite']) {
165
- const testSuite = Array.isArray(jsonSuite['test-suite']) ? jsonSuite['test-suite'][0] : jsonSuite['test-suite'];
166
- // Check for NUnit-specific test-suite types
167
- return (testSuite &&
168
- testSuite.type &&
169
- ['Assembly', 'TestSuite', 'TestFixture', 'ParameterizedMethod'].includes(testSuite.type));
170
- }
171
- return false;
172
- }
173
- processNUnitEnhanced(jsonSuite) {
174
- debug('Processing NUnit XML with enhanced parser');
175
- try {
176
- const nunitParser = new nunit_parser_js_1.NUnitXmlParser({
177
- groupParameterized: this.groupParameterized,
178
- ...this.opts,
179
- });
180
- const result = nunitParser.parseTestRun(jsonSuite);
181
- // Add parsed tests to our collection
182
- this.tests = this.tests.concat(result.tests);
183
- debug(`Enhanced NUnit parser processed ${result.tests.length} tests`);
184
- return result;
185
- }
186
- catch (error) {
187
- debug('Enhanced NUnit parser failed, falling back to legacy parser:', error.message);
188
- console.warn(`${constants_js_1.APP_PREFIX} Enhanced NUnit parsing failed, using legacy parser: ${error.message}`);
189
- // Fallback to legacy parser
190
- this.enhancedNunit = false;
191
- return this.processNUnit(jsonSuite);
192
- }
193
- }
194
145
  processTRX(jsonSuite) {
195
146
  let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
196
147
  if (!Array.isArray(defs))
197
148
  defs = [defs].filter(d => !!d);
198
- // Parse test definitions
199
- const tests = defs.map(td => this._parseTRXTestDefinition(td));
200
- // 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
+ }) || [];
201
165
  let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
202
166
  if (!Array.isArray(result))
203
167
  result = [result].filter(d => !!d);
204
- 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
+ });
205
198
  debug(results);
206
199
  const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
207
200
  const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
208
- 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;
209
204
  this.tests = results.filter(t => !!t.title);
210
205
  return {
211
206
  status,
@@ -217,57 +212,6 @@ class XmlReader {
217
212
  tests: results,
218
213
  };
219
214
  }
220
- _parseTRXTestDefinition(td) {
221
- const title = td.name.replace(/\(.*?\)/, '').trim();
222
- const exampleMatch = td.name.match(/\((.*?)\)/);
223
- const example = exampleMatch ? { ...exampleMatch[1].split(',') } : null;
224
- const suite = td.TestMethod.className.split(', ')[0].split('.');
225
- const suite_title = suite.pop();
226
- // Convert namespace to file path for C#
227
- const file = `${suite.join('/')}.cs`;
228
- return {
229
- title, // Base name without parameters for test import
230
- example, // Parameters object for parameterized tests
231
- file, // File path with .cs extension
232
- description: td.Description,
233
- suite_title,
234
- id: td.Execution.id,
235
- };
236
- }
237
- _parseTRXTestResult(td, tests) {
238
- const test = tests.find(t => t.id === td.executionId) || {};
239
- const result = {
240
- suite_title: test.suite_title,
241
- title: test.title?.trim(),
242
- file: test.file,
243
- description: test.description,
244
- code: test.code,
245
- run_time: parseFloat(td.duration) * 1000,
246
- stack: td.Output?.StdOut || '',
247
- files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
248
- create: true,
249
- overwrite: true,
250
- };
251
- // Add example for parameterized tests
252
- if (test.example) {
253
- result.example = test.example;
254
- }
255
- // Map TRX status to Testomat.io status
256
- result.status = this._mapTRXStatus(td.outcome);
257
- return result;
258
- }
259
- _mapTRXStatus(outcome) {
260
- switch (outcome) {
261
- case 'Passed':
262
- return constants_js_1.STATUS.PASSED;
263
- case 'Failed':
264
- return constants_js_1.STATUS.FAILED;
265
- case 'Skipped':
266
- return constants_js_1.STATUS.SKIPPED;
267
- default:
268
- return constants_js_1.STATUS.PASSED;
269
- }
270
- }
271
215
  processXUnit(assemblies) {
272
216
  const tests = [];
273
217
  assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.3.7-beta.3-xml-import",
3
+ "version": "2.3.7-beta.5-stack-artifacts",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
package/src/bin/cli.js CHANGED
@@ -158,7 +158,7 @@ program
158
158
  .option('--lang <lang>', 'Language used (python, ruby, java)')
159
159
  .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
160
160
  .action(async (pattern, opts) => {
161
- if (!pattern.endsWith('.xml') && !pattern.includes('*')) {
161
+ if (!pattern.endsWith('.xml')) {
162
162
  pattern += '.xml';
163
163
  }
164
164
  let { javaTests, lang } = opts;
@@ -23,7 +23,7 @@ program
23
23
  .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
24
24
  .option('--env-file <envfile>', 'Load environment variables from env file')
25
25
  .action(async (pattern, opts) => {
26
- if (!pattern.endsWith('.xml') && !pattern.includes('*')) {
26
+ if (!pattern.endsWith('.xml')) {
27
27
  pattern += '.xml';
28
28
  }
29
29
  let { javaTests, lang } = opts;
@@ -34,10 +34,7 @@ program
34
34
  }
35
35
  lang = lang?.toLowerCase();
36
36
  if (javaTests === true || (lang === 'java' && !javaTests)) javaTests = 'src/test/java';
37
- const runReader = new XmlReader({
38
- javaTests,
39
- lang,
40
- });
37
+ const runReader = new XmlReader({ javaTests, lang });
41
38
  const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
42
39
  if (!files.length) {
43
40
  console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
package/src/client.js CHANGED
@@ -37,8 +37,9 @@ class Client {
37
37
  this.runId = '';
38
38
  this.queue = Promise.resolve();
39
39
 
40
- // Get package.json path - use a simple approach that works in both environments
41
- const pathToPackageJSON = path.join(process.cwd(), 'package.json');
40
+ // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
41
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
42
+ const pathToPackageJSON = path.join(__dirname, '../package.json');
42
43
  try {
43
44
  this.version = JSON.parse(fs.readFileSync(pathToPackageJSON).toString()).version;
44
45
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
@@ -386,11 +387,7 @@ class Client {
386
387
  */
387
388
  formatLogs({ error, steps, logs }) {
388
389
  error = error?.trim();
389
- logs = logs
390
- ?.trim()
391
- .split('\n')
392
- .map(l => truncate(l))
393
- .join('\n');
390
+ logs = logs?.trim().split('\n').map(l => truncate(l)).join('\n');
394
391
 
395
392
  if (Array.isArray(steps)) {
396
393
  steps = steps
@@ -3,50 +3,18 @@ import Adapter from './adapter.js';
3
3
 
4
4
  class CSharpAdapter extends Adapter {
5
5
  formatTest(t) {
6
- // Extract example from title if not already present
7
- if (!t.example) {
8
- const exampleMatch = t.title.match(/\((.*?)\)/);
9
- if (exampleMatch) {
10
- // Extract parameters as object with numeric keys for API
11
- const params = exampleMatch[1].split(',').map(param => param.trim());
12
- t.example = {};
13
- params.forEach((param, index) => {
14
- t.example[index] = param;
15
- });
16
- }
17
- }
18
-
19
- // Remove parameters from title to avoid duplicates in Test Suite
20
- // The example field will be used for grouping on import
21
- t.title = t.title.replace(/\(.*?\)/, '').trim();
22
-
6
+ const title = t.title.replace(/\(.*?\)/, '').trim();
7
+ const example = t.title.match(/\((.*?)\)/);
8
+ if (example) t.example = { ...example[1].split(',') };
23
9
  const suite = t.suite_title.split('.');
24
10
  t.suite_title = suite.pop();
25
11
  t.file = namespaceToFileName(t.file);
12
+ t.title = title.trim();
26
13
  return t;
27
14
  }
28
15
 
29
16
  getFilePath(t) {
30
- if (!t.file) return null;
31
-
32
- // Normalize path separators for cross-platform compatibility
33
- let filePath = t.file.replace(/\\/g, '/');
34
-
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.isAbsolute(filePath)) {
39
- // Try to find project-relative path
40
- const cwd = process.cwd().replace(/\\/g, '/');
41
- if (filePath.startsWith(cwd)) {
42
- filePath = path.relative(cwd, filePath).replace(/\\/g, '/');
43
- }
44
- }
45
- return filePath;
46
- }
47
-
48
- // Convert namespace path to file path
49
- const fileName = namespaceToFileName(filePath);
17
+ const fileName = namespaceToFileName(t.file);
50
18
  return fileName;
51
19
  }
52
20
  }
@@ -54,14 +22,7 @@ class CSharpAdapter extends Adapter {
54
22
  export default CSharpAdapter;
55
23
 
56
24
  function namespaceToFileName(fileName) {
57
- if (!fileName) return '';
58
-
59
- // If already a .cs file path, clean it up
60
- if (fileName.endsWith('.cs')) {
61
- return fileName.replace(/\\/g, '/');
62
- }
63
-
64
25
  const fileParts = fileName.split('.');
65
26
  fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
66
- return `${fileParts.join('/')}.cs`;
27
+ return `${fileParts.join(path.sep)}.cs`;
67
28
  }
package/src/pipe/debug.js CHANGED
@@ -15,7 +15,7 @@ export class DebugPipe {
15
15
  this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
16
16
  if (this.isEnabled) {
17
17
  this.batch = {
18
- isEnabled: this.params.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
18
+ isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
19
19
  intervalFunction: null,
20
20
  intervalTime: 5000,
21
21
  tests: [],
@@ -93,7 +93,8 @@ export class DebugPipe {
93
93
  const logData = { action: 'addTest', testId: data };
94
94
  if (this.store.runId) logData.runId = this.store.runId;
95
95
  this.logToFile(logData);
96
- } else this.batch.tests.push(data);
96
+ }
97
+ else this.batch.tests.push(data);
97
98
 
98
99
  if (!this.batch.intervalFunction) await this.batchUpload();
99
100
  }
@@ -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,7 +459,7 @@ class TestomatioPipe {
456
459
  status_event,
457
460
  detach: params.detach,
458
461
  tests: params.tests,
459
- },
462
+ }
460
463
  });
461
464
 
462
465
  if (this.runUrl) {
@@ -522,6 +525,9 @@ function printCreateIssue(err) {
522
525
  console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
523
526
  console.log('```');
524
527
  });
528
+
525
529
  }
526
530
 
531
+
532
+
527
533
  export default TestomatioPipe;
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);