@testomatio/reporter 2.3.6-beta.2-fix-beforesuite β†’ 2.3.7-beta.1-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)_
@@ -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 πŸ˜₯`);
@@ -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,53 @@ 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
+ // Don't override example if it already exists from NUnit XML processing
11
+ // The xmlReader.js already extracts parameters correctly from <arguments>
12
+ if (!t.example) {
13
+ const title = t.title.replace(/\(.*?\)/, '').trim();
14
+ const exampleMatch = t.title.match(/\((.*?)\)/);
15
+ if (exampleMatch) {
16
+ // Keep as array for consistency with NUnit XML processing
17
+ t.example = exampleMatch[1].split(',').map(param => param.trim());
18
+ }
19
+ t.title = title.trim();
20
+ }
14
21
  const suite = t.suite_title.split('.');
15
22
  t.suite_title = suite.pop();
16
23
  t.file = namespaceToFileName(t.file);
17
- t.title = title.trim();
18
24
  return t;
19
25
  }
20
26
  getFilePath(t) {
21
- const fileName = namespaceToFileName(t.file);
27
+ if (!t.file)
28
+ return null;
29
+ // Normalize path separators for cross-platform compatibility
30
+ let filePath = t.file.replace(/\\/g, '/');
31
+ // If file already has .cs extension, use it directly
32
+ if (filePath.endsWith('.cs')) {
33
+ // Make relative path if it's absolute
34
+ if (path_1.default.isAbsolute(filePath)) {
35
+ // Try to find project-relative path
36
+ const cwd = process.cwd().replace(/\\/g, '/');
37
+ if (filePath.startsWith(cwd)) {
38
+ filePath = path_1.default.relative(cwd, filePath).replace(/\\/g, '/');
39
+ }
40
+ }
41
+ return filePath;
42
+ }
43
+ // Convert namespace path to file path
44
+ const fileName = namespaceToFileName(filePath);
22
45
  return fileName;
23
46
  }
24
47
  }
25
48
  module.exports = CSharpAdapter;
26
49
  function namespaceToFileName(fileName) {
50
+ if (!fileName)
51
+ return '';
52
+ // If already a .cs file path, clean it up
53
+ if (fileName.endsWith('.cs')) {
54
+ return fileName.replace(/\\/g, '/');
55
+ }
27
56
  const fileParts = fileName.split('.');
28
57
  fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
29
- return `${fileParts.join(path_1.default.sep)}.cs`;
58
+ return `${fileParts.join('/')}.cs`;
30
59
  }
@@ -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,357 @@
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
+ return {
202
+ title: isParameterized ? testName : methodName || testName,
203
+ methodName: baseMethodName || methodName || testName,
204
+ fullName: fullName,
205
+ suitePath: suitePath,
206
+ suite_title: className || suitePath[suitePath.length - 1] || 'Unknown',
207
+ file: filePath,
208
+ status: status,
209
+ message: message,
210
+ stack: stack,
211
+ run_time: parseFloat(testCase.duration || testCase.time || 0) * 1000,
212
+ test_id: testId,
213
+ create: true,
214
+ retry: false,
215
+ // Parameterized test metadata
216
+ isParameterized: isParameterized,
217
+ parameters: parameters,
218
+ baseMethodName: baseMethodName,
219
+ };
220
+ }
221
+ /**
222
+ * Extract method name and parameters from test name
223
+ * @param {string} testName - Full test name
224
+ * @returns {Object} - Extracted information
225
+ */
226
+ extractParameters(testName) {
227
+ const paramMatch = testName.match(/^(.+?)\((.+)\)$/);
228
+ if (paramMatch) {
229
+ const baseMethodName = paramMatch[1].trim();
230
+ const paramString = paramMatch[2];
231
+ // Parse parameters - handle quoted strings and nested structures
232
+ const parameters = this.parseParameterString(paramString);
233
+ return {
234
+ baseMethodName: baseMethodName,
235
+ parameters: parameters,
236
+ isParameterized: true,
237
+ };
238
+ }
239
+ return {
240
+ baseMethodName: testName,
241
+ parameters: [],
242
+ isParameterized: false,
243
+ };
244
+ }
245
+ /**
246
+ * Parse parameter string into array of parameters
247
+ * @param {string} paramString - Parameter string
248
+ * @returns {Array} - Array of parameters
249
+ */
250
+ parseParameterString(paramString) {
251
+ const parameters = [];
252
+ let current = '';
253
+ let inQuotes = false;
254
+ let quoteChar = null;
255
+ let depth = 0;
256
+ for (let i = 0; i < paramString.length; i++) {
257
+ const char = paramString[i];
258
+ if (!inQuotes && (char === '"' || char === "'")) {
259
+ inQuotes = true;
260
+ quoteChar = char;
261
+ current += char;
262
+ }
263
+ else if (inQuotes && char === quoteChar) {
264
+ inQuotes = false;
265
+ quoteChar = null;
266
+ current += char;
267
+ }
268
+ else if (!inQuotes && char === '(') {
269
+ depth++;
270
+ current += char;
271
+ }
272
+ else if (!inQuotes && char === ')') {
273
+ depth--;
274
+ current += char;
275
+ }
276
+ else if (!inQuotes && char === ',' && depth === 0) {
277
+ parameters.push(current.trim());
278
+ current = '';
279
+ }
280
+ else {
281
+ current += char;
282
+ }
283
+ }
284
+ if (current.trim()) {
285
+ parameters.push(current.trim());
286
+ }
287
+ // Clean up parameters - remove quotes if they wrap the entire parameter
288
+ return parameters.map(param => {
289
+ param = param.trim();
290
+ if ((param.startsWith('"') && param.endsWith('"')) || (param.startsWith("'") && param.endsWith("'"))) {
291
+ return param.slice(1, -1);
292
+ }
293
+ return param;
294
+ });
295
+ }
296
+ /**
297
+ * Extract method name from test name (fallback)
298
+ * @param {string} testName - Test name
299
+ * @returns {string} - Method name
300
+ */
301
+ extractMethodName(testName) {
302
+ // Remove parameters if present
303
+ const paramMatch = testName.match(/^(.+?)\(/);
304
+ return paramMatch ? paramMatch[1].trim() : testName;
305
+ }
306
+ /**
307
+ * Build file path from suite path and class name
308
+ * @param {Array} suitePath - Suite path array
309
+ * @param {string} className - Class name
310
+ * @param {Object} parentSuite - Parent suite for context
311
+ * @returns {string} - File path
312
+ */
313
+ buildFilePath(suitePath, className, parentSuite) {
314
+ // Try to get file path from parent suite
315
+ if (parentSuite && parentSuite.filepath) {
316
+ return parentSuite.filepath;
317
+ }
318
+ // Build path from suite hierarchy
319
+ const pathParts = [...suitePath];
320
+ if (className && !pathParts.includes(className)) {
321
+ pathParts.push(className);
322
+ }
323
+ // Convert to file path format
324
+ return pathParts.join('/') + '.cs'; // Assume C# for NUnit
325
+ }
326
+ /**
327
+ * Group parameterized tests by base method name
328
+ * @param {Array} tests - Array of parsed tests
329
+ * @returns {Object} - Grouped tests
330
+ */
331
+ groupParameterizedTests(tests) {
332
+ const grouped = {};
333
+ tests.forEach(test => {
334
+ const key = test.isParameterized
335
+ ? `${test.suitePath.join('.')}.${test.baseMethodName}`
336
+ : `${test.suitePath.join('.')}.${test.title}`;
337
+ if (!grouped[key]) {
338
+ grouped[key] = {
339
+ baseTest: {
340
+ name: test.baseMethodName || test.title,
341
+ suitePath: test.suitePath,
342
+ suite_title: test.suite_title,
343
+ file: test.file,
344
+ isParameterized: test.isParameterized,
345
+ },
346
+ variations: [],
347
+ };
348
+ }
349
+ grouped[key].variations.push(test);
350
+ });
351
+ return grouped;
352
+ }
353
+ }
354
+ exports.NUnitXmlParser = NUnitXmlParser;
355
+ module.exports = NUnitXmlParser;
356
+
357
+ 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: [],
@@ -23,7 +23,7 @@ if (process.env.TESTOMATIO_RUN)
23
23
  class TestomatioPipe {
24
24
  constructor(params, store) {
25
25
  this.batch = {
26
- isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
26
+ isEnabled: params?.isBatchEnabled ?? (process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ? false : true),
27
27
  intervalFunction: null, // will be created in createRun by setInterval function
28
28
  intervalTime: 5000, // how often tests are sent
29
29
  tests: [], // array of tests in batch
@@ -60,7 +60,7 @@ class TestomatioPipe {
60
60
  retry: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest,
61
61
  retryDelay: constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout,
62
62
  httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
63
- shouldRetry: (error) => {
63
+ shouldRetry: error => {
64
64
  if (!error.response)
65
65
  return false;
66
66
  switch (error.response?.status) {
@@ -73,8 +73,8 @@ class TestomatioPipe {
73
73
  break;
74
74
  }
75
75
  return error.response?.status >= 401; // Retry on 401+ and 5xx
76
- }
77
- }
76
+ },
77
+ },
78
78
  });
79
79
  this.isEnabled = true;
80
80
  // do not finish this run (for parallel testing)
@@ -193,7 +193,7 @@ class TestomatioPipe {
193
193
  method: 'PUT',
194
194
  url: `/api/reporter/${this.runId}`,
195
195
  data: runParams,
196
- responseType: 'json'
196
+ responseType: 'json',
197
197
  });
198
198
  if (resp.data.artifacts)
199
199
  (0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
@@ -206,7 +206,7 @@ class TestomatioPipe {
206
206
  url: '/api/reporter',
207
207
  data: runParams,
208
208
  maxContentLength: Infinity,
209
- responseType: 'json'
209
+ responseType: 'json',
210
210
  });
211
211
  this.runId = resp.data.uid;
212
212
  this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
@@ -259,15 +259,17 @@ class TestomatioPipe {
259
259
  this.#formatData(data);
260
260
  const json = json_cycle_1.default.stringify(data);
261
261
  debug('Adding test', json);
262
- return this.client.request({
262
+ return this.client
263
+ .request({
263
264
  method: 'POST',
264
265
  url: `/api/reporter/${this.runId}/testrun`,
265
266
  data: json,
266
267
  headers: {
267
268
  'Content-Type': 'application/json',
268
269
  },
269
- maxContentLength: Infinity
270
- }).catch(err => {
270
+ maxContentLength: Infinity,
271
+ })
272
+ .catch(err => {
271
273
  this.requestFailures++;
272
274
  this.notReportedTestsCount++;
273
275
  if (err.response) {
@@ -312,19 +314,21 @@ class TestomatioPipe {
312
314
  // get tests from batch and clear batch
313
315
  const testsToSend = this.batch.tests.splice(0);
314
316
  debug('πŸ“¨ Batch upload', testsToSend.length, 'tests');
315
- return this.client.request({
317
+ return this.client
318
+ .request({
316
319
  method: 'POST',
317
320
  url: `/api/reporter/${this.runId}/testrun`,
318
321
  data: {
319
322
  api_key: this.apiKey,
320
323
  tests: testsToSend,
321
- batch_index: this.batch.batchIndex
324
+ batch_index: this.batch.batchIndex,
322
325
  },
323
326
  headers: {
324
327
  'Content-Type': 'application/json',
325
328
  },
326
- maxContentLength: Infinity
327
- }).catch(err => {
329
+ maxContentLength: Infinity,
330
+ })
331
+ .catch(err => {
328
332
  this.requestFailures++;
329
333
  this.notReportedTestsCount += testsToSend.length;
330
334
  if (err.response) {
@@ -408,9 +412,9 @@ class TestomatioPipe {
408
412
  status_event,
409
413
  detach: params.detach,
410
414
  tests: params.tests,
411
- }
415
+ },
412
416
  });
413
- console.log(constants_js_1.APP_PREFIX, 'βœ… Testrun finished');
417
+ debug(constants_js_1.APP_PREFIX, 'βœ… Testrun finished');
414
418
  if (this.runUrl) {
415
419
  console.log(constants_js_1.APP_PREFIX, 'πŸ“Š Report Saved. Report URL:', picocolors_1.default.magenta(this.runUrl));
416
420
  }
package/lib/uploader.js CHANGED
@@ -170,6 +170,10 @@ class S3Uploader {
170
170
  if (typeof filePath === 'string' && !path_1.default.isAbsolute(filePath)) {
171
171
  filePath = path_1.default.join(process.cwd(), filePath);
172
172
  }
173
+ // Normalize path separators for cross-platform compatibility
174
+ if (typeof filePath === 'string') {
175
+ filePath = filePath.replace(/\\/g, '/');
176
+ }
173
177
  const data = { rid, file: filePath, uploaded };
174
178
  const jsonLine = `${JSON.stringify(data)}\n`;
175
179
  fs_1.default.appendFileSync(tempFilePath, jsonLine);