@testomatio/reporter 2.3.8 → 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.
@@ -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.
@@ -216,7 +214,7 @@ class TestomatioPipe {
216
214
  method: 'PUT',
217
215
  url: `/api/reporter/${this.runId}`,
218
216
  data: runParams,
219
- responseType: 'json'
217
+ responseType: 'json',
220
218
  });
221
219
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
222
220
  return;
@@ -229,7 +227,7 @@ class TestomatioPipe {
229
227
  url: '/api/reporter',
230
228
  data: runParams,
231
229
  maxContentLength: Infinity,
232
- responseType: 'json'
230
+ responseType: 'json',
233
231
  });
234
232
 
235
233
  this.runId = resp.data.uid;
@@ -288,44 +286,44 @@ class TestomatioPipe {
288
286
 
289
287
  debug('Adding test', json);
290
288
 
291
- return this.client.request({
292
- method: 'POST',
293
- url: `/api/reporter/${this.runId}/testrun`,
294
- data: json,
295
- headers: {
296
- 'Content-Type': 'application/json',
297
- },
298
- maxContentLength: Infinity
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: '' };
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
+ }
305
315
  console.log(
306
316
  APP_PREFIX,
307
- pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
308
- pc.gray(data?.title || ''),
317
+ pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
318
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
309
319
  );
310
- if (err.response?.data?.message?.includes('could not be matched')) {
311
- this.hasUnmatchedTests = true;
312
- }
313
- return;
320
+ printCreateIssue(err);
321
+ } else {
322
+ console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
314
323
  }
315
- console.log(
316
- APP_PREFIX,
317
- pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
318
- `Report couldn't be processed: ${err?.response?.data?.message}`,
319
- );
320
- printCreateIssue(err);
321
- } else {
322
- console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
323
- }
324
- });
324
+ });
325
325
  };
326
326
 
327
-
328
-
329
327
  /**
330
328
  * Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
331
329
  */
@@ -350,43 +348,42 @@ class TestomatioPipe {
350
348
  const testsToSend = this.batch.tests.splice(0);
351
349
  debug('📨 Batch upload', testsToSend.length, 'tests');
352
350
 
353
- return this.client.request({
354
- method: 'POST',
355
- url: `/api/reporter/${this.runId}/testrun`,
356
- data: {
357
- api_key: this.apiKey,
358
- tests: testsToSend,
359
- batch_index: this.batch.batchIndex
360
- },
361
- headers: {
362
- 'Content-Type': 'application/json',
363
- },
364
- maxContentLength: Infinity
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: '' };
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
+ }
371
377
  console.log(
372
378
  APP_PREFIX,
373
- 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}`,
374
381
  );
375
- if (err.response?.data?.message?.includes('could not be matched')) {
376
- this.hasUnmatchedTests = true;
377
- }
378
- return;
382
+ printCreateIssue(err);
383
+ } else {
384
+ console.log(APP_PREFIX, "Report couldn't be processed", err);
379
385
  }
380
- console.log(
381
- APP_PREFIX,
382
- pc.yellow(`Warning: (${err.response?.status})`),
383
- `Report couldn't be processed: ${err?.response?.data?.message}`,
384
- );
385
- printCreateIssue(err);
386
- } else {
387
- console.log(APP_PREFIX, "Report couldn't be processed", err);
388
- }
389
- });
386
+ });
390
387
  };
391
388
 
392
389
  /**
@@ -409,9 +406,9 @@ class TestomatioPipe {
409
406
  else this.batch.tests.push(data);
410
407
 
411
408
  // if test is added after run which is already finished
412
- if (!this.batch.intervalFunction) uploading = this.#batchUpload();
409
+ if (!this.batch.intervalFunction) uploading = this.#batchUpload();
413
410
 
414
- // return promise to be able to wait for it
411
+ // return promise to be able to wait for it
415
412
  return uploading;
416
413
  }
417
414
 
@@ -460,7 +457,7 @@ class TestomatioPipe {
460
457
  status_event,
461
458
  detach: params.detach,
462
459
  tests: params.tests,
463
- }
460
+ },
464
461
  });
465
462
 
466
463
  if (this.runUrl) {
@@ -526,9 +523,6 @@ function printCreateIssue(err) {
526
523
  console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
527
524
  console.log('```');
528
525
  });
529
-
530
526
  }
531
527
 
532
-
533
-
534
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,5 +1,5 @@
1
1
  import Client from './client.js';
2
- import * as TestomatioConstants from './constants.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
 
@@ -39,5 +39,4 @@ export default {
39
39
 
40
40
  TestomatioClient: Client,
41
41
  STATUS,
42
-
43
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();
@@ -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 };
package/src/xmlReader.js CHANGED
@@ -274,9 +274,14 @@ class XmlReader {
274
274
  _parseTRXTestDefinition(td) {
275
275
  const title = td.name.replace(/\(.*?\)/, '').trim();
276
276
  const exampleMatch = td.name.match(/\((.*?)\)/);
277
- const example = exampleMatch ? {
278
- ...exampleMatch[1].split(',').map(p => p.trim()).filter(p => p !== '')
279
- } : null;
277
+ const example = exampleMatch
278
+ ? {
279
+ ...exampleMatch[1]
280
+ .split(',')
281
+ .map(p => p.trim())
282
+ .filter(p => p !== ''),
283
+ }
284
+ : null;
280
285
 
281
286
  const suite = td.TestMethod.className.split(', ')[0].split('.');
282
287
  const suite_title = suite.pop();
@@ -600,7 +605,12 @@ function reduceTestCases(prev, item) {
600
605
 
601
606
  const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
602
607
  if (exampleMatches) {
603
- example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')).filter(v => v !== '') };
608
+ example = {
609
+ ...exampleMatches[1]
610
+ .split(',')
611
+ .map(v => v.trim().replace(/[^\w\s-]/g, ''))
612
+ .filter(v => v !== ''),
613
+ };
604
614
  title = title.replace(/\(.*?\)/, '').trim();
605
615
  }
606
616