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

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.
Files changed (45) hide show
  1. package/README.md +2 -1
  2. package/lib/adapter/codecept.js +12 -9
  3. package/lib/bin/cli.js +14 -4
  4. package/lib/bin/reportXml.js +5 -2
  5. package/lib/client.d.ts +1 -11
  6. package/lib/client.js +39 -142
  7. package/lib/junit-adapter/csharp.d.ts +0 -1
  8. package/lib/junit-adapter/csharp.js +43 -7
  9. package/lib/junit-adapter/nunit-parser.d.ts +82 -0
  10. package/lib/junit-adapter/nunit-parser.js +433 -0
  11. package/lib/pipe/bitbucket.js +5 -5
  12. package/lib/pipe/gitlab.js +4 -4
  13. package/lib/pipe/testomatio.d.ts +2 -1
  14. package/lib/pipe/testomatio.js +19 -14
  15. package/lib/reporter-functions.js +1 -3
  16. package/lib/reporter.d.ts +19 -9
  17. package/lib/reporter.js +40 -5
  18. package/lib/uploader.js +4 -0
  19. package/lib/utils/log-formatter.d.ts +28 -0
  20. package/lib/utils/log-formatter.js +127 -0
  21. package/lib/utils/utils.js +189 -24
  22. package/lib/xmlReader.d.ts +32 -26
  23. package/lib/xmlReader.js +121 -52
  24. package/package.json +8 -4
  25. package/src/adapter/codecept.js +19 -19
  26. package/src/adapter/mocha.js +1 -1
  27. package/src/adapter/playwright.js +2 -2
  28. package/src/bin/cli.js +16 -4
  29. package/src/bin/reportXml.js +5 -2
  30. package/src/client.js +47 -116
  31. package/src/junit-adapter/csharp.js +48 -6
  32. package/src/junit-adapter/nunit-parser.js +474 -0
  33. package/src/pipe/bitbucket.js +5 -5
  34. package/src/pipe/debug.js +1 -2
  35. package/src/pipe/gitlab.js +4 -4
  36. package/src/pipe/testomatio.js +75 -80
  37. package/src/reporter-functions.js +2 -3
  38. package/src/reporter.js +6 -4
  39. package/src/services/links.js +1 -1
  40. package/src/uploader.js +5 -0
  41. package/src/utils/log-formatter.js +113 -0
  42. package/src/utils/utils.js +202 -22
  43. package/src/xmlReader.js +144 -46
  44. package/types/types.d.ts +364 -0
  45. package/types/vitest.types.d.ts +93 -0
@@ -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,7 +104,6 @@ class TestomatioPipe {
104
104
  // add test ID + run ID
105
105
  if (data.rid) data.rid = `${this.runId}-${data.rid}`;
106
106
 
107
-
108
107
  if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
109
108
  data.stack = null;
110
109
  }
@@ -120,7 +119,6 @@ class TestomatioPipe {
120
119
  return data;
121
120
  }
122
121
 
123
-
124
122
  /**
125
123
  * Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
126
124
  * @param {Object} opts - The options for preparing the test grepList.
@@ -163,7 +161,7 @@ class TestomatioPipe {
163
161
 
164
162
  /**
165
163
  * Creates a new run on Testomat.io
166
- * @param {{isBatchEnabled?: boolean}} params
164
+ * @param {{isBatchEnabled?: boolean, kind?: string}} params
167
165
  * @returns Promise<void>
168
166
  */
169
167
  async createRun(params = {}) {
@@ -204,6 +202,7 @@ class TestomatioPipe {
204
202
  label: this.label,
205
203
  shared_run: this.sharedRun,
206
204
  shared_run_timeout: this.sharedRunTimeout,
205
+ kind: params.kind,
207
206
  }).filter(([, value]) => !!value),
208
207
  );
209
208
  debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
@@ -215,7 +214,7 @@ class TestomatioPipe {
215
214
  method: 'PUT',
216
215
  url: `/api/reporter/${this.runId}`,
217
216
  data: runParams,
218
- responseType: 'json'
217
+ responseType: 'json',
219
218
  });
220
219
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
221
220
  return;
@@ -228,7 +227,7 @@ class TestomatioPipe {
228
227
  url: '/api/reporter',
229
228
  data: runParams,
230
229
  maxContentLength: Infinity,
231
- responseType: 'json'
230
+ responseType: 'json',
232
231
  });
233
232
 
234
233
  this.runId = resp.data.uid;
@@ -287,44 +286,44 @@ class TestomatioPipe {
287
286
 
288
287
  debug('Adding test', json);
289
288
 
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: '' };
289
+ return this.client
290
+ .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
+ })
299
+ .catch(err => {
300
+ this.requestFailures++;
301
+ this.notReportedTestsCount++;
302
+ if (err.response) {
303
+ if (err.response.status >= 400) {
304
+ const responseData = err.response.data || { message: '' };
305
+ console.log(
306
+ APP_PREFIX,
307
+ pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
308
+ pc.gray(data?.title || ''),
309
+ );
310
+ if (err.response?.data?.message?.includes('could not be matched')) {
311
+ this.hasUnmatchedTests = true;
312
+ }
313
+ return;
314
+ }
304
315
  console.log(
305
316
  APP_PREFIX,
306
- pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
307
- pc.gray(data?.title || ''),
317
+ pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
318
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
308
319
  );
309
- if (err.response?.data?.message?.includes('could not be matched')) {
310
- this.hasUnmatchedTests = true;
311
- }
312
- return;
320
+ printCreateIssue(err);
321
+ } else {
322
+ console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
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
 
326
-
327
-
328
327
  /**
329
328
  * Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
330
329
  */
@@ -349,43 +348,42 @@ class TestomatioPipe {
349
348
  const testsToSend = this.batch.tests.splice(0);
350
349
  debug('📨 Batch upload', testsToSend.length, 'tests');
351
350
 
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: '' };
351
+ return this.client
352
+ .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
+ })
365
+ .catch(err => {
366
+ this.requestFailures++;
367
+ this.notReportedTestsCount += testsToSend.length;
368
+ if (err.response) {
369
+ if (err.response.status >= 400) {
370
+ const responseData = err.response.data || { message: '' };
371
+ console.log(APP_PREFIX, pc.yellow(`Warning: ${responseData.message} (${err.response.status})`));
372
+ if (err.response?.data?.message?.includes('could not be matched')) {
373
+ this.hasUnmatchedTests = true;
374
+ }
375
+ return;
376
+ }
370
377
  console.log(
371
378
  APP_PREFIX,
372
- pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
379
+ pc.yellow(`Warning: (${err.response?.status})`),
380
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
373
381
  );
374
- if (err.response?.data?.message?.includes('could not be matched')) {
375
- this.hasUnmatchedTests = true;
376
- }
377
- return;
382
+ printCreateIssue(err);
383
+ } else {
384
+ console.log(APP_PREFIX, "Report couldn't be processed", err);
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
389
  /**
@@ -408,9 +406,9 @@ class TestomatioPipe {
408
406
  else this.batch.tests.push(data);
409
407
 
410
408
  // if test is added after run which is already finished
411
- if (!this.batch.intervalFunction) uploading = this.#batchUpload();
409
+ if (!this.batch.intervalFunction) uploading = this.#batchUpload();
412
410
 
413
- // return promise to be able to wait for it
411
+ // return promise to be able to wait for it
414
412
  return uploading;
415
413
  }
416
414
 
@@ -459,7 +457,7 @@ class TestomatioPipe {
459
457
  status_event,
460
458
  detach: params.detach,
461
459
  tests: params.tests,
462
- }
460
+ },
463
461
  });
464
462
 
465
463
  if (this.runUrl) {
@@ -525,9 +523,6 @@ function printCreateIssue(err) {
525
523
  console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
526
524
  console.log('```');
527
525
  });
528
-
529
526
  }
530
527
 
531
-
532
-
533
528
  export default TestomatioPipe;
@@ -62,9 +62,8 @@ function setLabel(key, value = null) {
62
62
  if (Array.isArray(value)) {
63
63
  return value.forEach(label => setLabel(key, label));
64
64
  }
65
- const labelObject = value !== null && value !== undefined && value !== ''
66
- ? { label: `${key}:${value}` }
67
- : { label: key };
65
+ const labelObject =
66
+ value !== null && value !== undefined && value !== '' ? { label: `${key}:${value}` } : { label: key };
68
67
  services.links.put([labelObject]);
69
68
  }
70
69
 
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,6 @@ export default {
35
37
  linkTest: reporterFunctions.linkTest,
36
38
  linkJira: reporterFunctions.linkJira,
37
39
 
38
- // TestomatClient,
39
- // TRConstants,
40
+ TestomatioClient: Client,
41
+ STATUS,
40
42
  };
@@ -66,4 +66,4 @@ class LinkStorage {
66
66
  }
67
67
  }
68
68
 
69
- export const linkStorage = LinkStorage.getInstance();
69
+ export const linkStorage = LinkStorage.getInstance();
package/src/uploader.js CHANGED
@@ -194,6 +194,11 @@ 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
+
197
202
  const data = { rid, file: filePath, uploaded };
198
203
  const jsonLine = `${JSON.stringify(data)}\n`;
199
204
  fs.appendFileSync(tempFilePath, jsonLine);
@@ -0,0 +1,113 @@
1
+ import createCallsiteRecord from 'callsite-record';
2
+ import { minimatch } from 'minimatch';
3
+ import pc from 'picocolors';
4
+ import { stripVTControlCharacters } from 'util';
5
+ import { sep } from 'path';
6
+ import { formatStep, truncate } from './utils.js';
7
+
8
+ const stripColors = stripVTControlCharacters || (str => str?.replace(/\x1b\[[0-9;]*m/g, '') || '');
9
+
10
+ /**
11
+ * Returns the formatted stack including the stack trace, steps, and logs.
12
+ * @param {Object} params - Parameters for formatting logs
13
+ * @param {string} params.error - Error message
14
+ * @param {Array|any} params.steps - Test steps (array or other types)
15
+ * @param {string} params.logs - Test logs
16
+ * @returns {string}
17
+ */
18
+ export function formatLogs({ error, steps, logs }) {
19
+ error = error?.trim();
20
+ logs = logs
21
+ ?.trim()
22
+ .split('\n')
23
+ .map(l => truncate(l))
24
+ .join('\n');
25
+
26
+ if (Array.isArray(steps)) {
27
+ steps = steps
28
+ .map(step => formatStep(step))
29
+ .flat()
30
+ .join('\n');
31
+ } else {
32
+ steps = null;
33
+ }
34
+
35
+ let testLogs = '';
36
+ if (steps) testLogs += `${pc.bold(pc.blue('################[ Steps ]################'))}\n${steps}\n\n`;
37
+ if (logs) testLogs += `${pc.bold(pc.gray('################[ Logs ]################'))}\n${logs}\n\n`;
38
+ if (error) testLogs += `${pc.bold(pc.red('################[ Failure ]################'))}\n${error}`;
39
+ return testLogs;
40
+ }
41
+
42
+ /**
43
+ * Formats an error with stack trace and diff information
44
+ * @param {Error & {inspect?: () => string, operator?: string, diff?: string, actual?: any, expected?: any}} error
45
+ * The error object to format
46
+ * @param {string} [message] - Optional error message override
47
+ * @returns {string}
48
+ */
49
+ export function formatError(error, message) {
50
+ if (!message) message = error.message;
51
+ // @ts-ignore - inspect is a custom property added by some testing frameworks
52
+ if (error.inspect) message = error.inspect() || '';
53
+
54
+ let stack = '';
55
+ if (error.name) stack += `${pc.red(error.name)}`;
56
+ // @ts-ignore - operator is a custom property added by assertion libraries
57
+ if (error.operator) stack += ` (${pc.red(error.operator)})`;
58
+ // add new line if something was added to stack
59
+ if (stack) stack += ': ';
60
+
61
+ stack += `${message}\n`;
62
+
63
+ // @ts-ignore - diff is a custom property added by vitest
64
+ if (error.diff) {
65
+ // diff for vitest
66
+ stack += error.diff;
67
+ stack += '\n\n';
68
+ } else if (error.actual && error.expected && error.actual !== error.expected) {
69
+ // diffs for mocha, cypress, codeceptjs style
70
+ stack += `\n\n${pc.bold(pc.green('+ expected'))} ${pc.bold(pc.red('- actual'))}`;
71
+ stack += `\n${pc.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
72
+ stack += `\n${pc.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
73
+ stack += '\n\n';
74
+ }
75
+
76
+ const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
77
+
78
+ try {
79
+ let hasFrame = false;
80
+ const record = createCallsiteRecord({
81
+ forError: error,
82
+ isCallsiteFrame: frame => {
83
+ if (customFilter && minimatch(frame.fileName, customFilter)) return false;
84
+ if (hasFrame) return false;
85
+ if (isNotInternalFrame(frame)) hasFrame = true;
86
+ return hasFrame;
87
+ },
88
+ });
89
+ // @ts-ignore
90
+ if (record && !record.filename.startsWith('http')) {
91
+ stack += record.renderSync({ stackFilter: isNotInternalFrame });
92
+ }
93
+ return stack;
94
+ } catch (e) {
95
+ console.log(e);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Checks if a stack frame is not an internal frame (node_modules or internal)
101
+ * @param {Object} frame - Stack frame object
102
+ * @returns {boolean}
103
+ */
104
+ function isNotInternalFrame(frame) {
105
+ return (
106
+ frame.getFileName() &&
107
+ frame.getFileName().includes(sep) &&
108
+ !frame.getFileName().includes('node_modules') &&
109
+ !frame.getFileName().includes('internal')
110
+ );
111
+ }
112
+
113
+ export { stripColors };