@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.
package/lib/xmlReader.js CHANGED
@@ -221,9 +221,14 @@ class XmlReader {
221
221
  _parseTRXTestDefinition(td) {
222
222
  const title = td.name.replace(/\(.*?\)/, '').trim();
223
223
  const exampleMatch = td.name.match(/\((.*?)\)/);
224
- const example = exampleMatch ? {
225
- ...exampleMatch[1].split(',').map(p => p.trim()).filter(p => p !== '')
226
- } : null;
224
+ const example = exampleMatch
225
+ ? {
226
+ ...exampleMatch[1]
227
+ .split(',')
228
+ .map(p => p.trim())
229
+ .filter(p => p !== ''),
230
+ }
231
+ : null;
227
232
  const suite = td.TestMethod.className.split(', ')[0].split('.');
228
233
  const suite_title = suite.pop();
229
234
  // Convert namespace to file path for C#
@@ -518,7 +523,12 @@ function reduceTestCases(prev, item) {
518
523
  tags ||= [];
519
524
  const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
520
525
  if (exampleMatches) {
521
- example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')).filter(v => v !== '') };
526
+ example = {
527
+ ...exampleMatches[1]
528
+ .split(',')
529
+ .map(v => v.trim().replace(/[^\w\s-]/g, ''))
530
+ .filter(v => v !== ''),
531
+ };
522
532
  title = title.replace(/\(.*?\)/, '').trim();
523
533
  }
524
534
  stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.3.8",
3
+ "version": "2.3.9",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -106,7 +106,7 @@
106
106
  "vitest": "^1.6.0"
107
107
  },
108
108
  "bin": {
109
- "@testomatio/reporter": "./lib/bin/cli.js",
109
+ "testomatio/reporter": "./lib/bin/cli.js",
110
110
  "report-xml": "./lib/bin/reportXml.js",
111
111
  "start-test-run": "./lib/bin/startTest.js",
112
112
  "upload-artifacts": "./lib/bin/uploadArtifacts.js"
@@ -17,12 +17,15 @@ if (!global.codeceptjs) {
17
17
  // @ts-ignore
18
18
  const { event, recorder, codecept, output } = global.codeceptjs;
19
19
 
20
- const [, MAJOR_VERSION, MINOR_VERSION] = codecept.version().match(/(\d+)\.(\d+)/).map(Number);
20
+ const [, MAJOR_VERSION, MINOR_VERSION] = codecept
21
+ .version()
22
+ .match(/(\d+)\.(\d+)/)
23
+ .map(Number);
21
24
 
22
25
  // Constants for hook execution order
23
26
  const HOOK_EXECUTION_ORDER = {
24
27
  PRE_TEST: ['BeforeSuiteHook', 'BeforeHook'],
25
- POST_TEST: ['AfterHook', 'AfterSuiteHook']
28
+ POST_TEST: ['AfterHook', 'AfterSuiteHook'],
26
29
  };
27
30
 
28
31
  // codeceptjs workers are self-contained
@@ -35,7 +38,9 @@ if (MAJOR_VERSION < 3) {
35
38
  }
36
39
 
37
40
  if (MAJOR_VERSION === 3 && MINOR_VERSION < 7) {
38
- console.log('🔴 CodeceptJS 3.7+ is supported, please upgrade CodeceptJS or use 1.6 version of `@testomatio/reporter`');
41
+ console.log(
42
+ '🔴 CodeceptJS 3.7+ is supported, please upgrade CodeceptJS or use 1.6 version of `@testomatio/reporter`',
43
+ );
39
44
  }
40
45
 
41
46
  function CodeceptReporter(config) {
@@ -57,18 +62,18 @@ function CodeceptReporter(config) {
57
62
  say: output.say,
58
63
  };
59
64
 
60
- output.debug = function(msg) {
65
+ output.debug = function (msg) {
61
66
  originalOutput.debug(msg);
62
67
  dataStorage.putData('log', repeat(this?.stepShift || 0) + pc.cyan(msg.toString()));
63
68
  };
64
69
 
65
- output.say = function(message, color = 'cyan') {
70
+ output.say = function (message, color = 'cyan') {
66
71
  originalOutput.say(message, color);
67
72
  const sayMsg = repeat(this?.stepShift || 0) + ` ${pc.bold(pc[color](message))}`;
68
73
  dataStorage.putData('log', sayMsg);
69
74
  };
70
75
 
71
- output.log = function(msg) {
76
+ output.log = function (msg) {
72
77
  originalOutput.log(msg);
73
78
  dataStorage.putData('log', repeat(this?.stepShift || 0) + pc.gray(msg));
74
79
  };
@@ -108,7 +113,7 @@ function CodeceptReporter(config) {
108
113
  });
109
114
 
110
115
  // Hook event listeners
111
- event.dispatcher.on(event.hook.started, (hook) => {
116
+ event.dispatcher.on(event.hook.started, hook => {
112
117
  output.stepShift = 2;
113
118
  currentHook = hook.name;
114
119
  let title = hook.hookName;
@@ -126,7 +131,6 @@ function CodeceptReporter(config) {
126
131
  services.setContext(null);
127
132
  });
128
133
 
129
-
130
134
  // mark as failed all tests inside the failed hook
131
135
  event.dispatcher.on(event.hook.failed, hook => {
132
136
  if (hook.name !== 'BeforeSuiteHook') return;
@@ -148,7 +152,6 @@ function CodeceptReporter(config) {
148
152
  }
149
153
  });
150
154
 
151
-
152
155
  event.dispatcher.on(event.suite.before, suite => {
153
156
  dataStorage.setContext(suite.fullTitle());
154
157
  });
@@ -167,7 +170,7 @@ function CodeceptReporter(config) {
167
170
  testTimeMap[test.uid] = Date.now();
168
171
  });
169
172
 
170
- event.dispatcher.on(event.all.result, async (result) => {
173
+ event.dispatcher.on(event.all.result, async result => {
171
174
  debug('waiting for all tests to be reported');
172
175
  // all tests were reported and we can upload videos
173
176
  await Promise.all(reportTestPromises);
@@ -327,7 +330,7 @@ function captureHookStep(step, currentHook, hookSteps) {
327
330
  status: step.status,
328
331
  startTime,
329
332
  endTime,
330
- helperMethod: step.helperMethod
333
+ helperMethod: step.helperMethod,
331
334
  });
332
335
  hookSteps.set(currentHook, hookStepsArray);
333
336
  }
@@ -422,13 +425,12 @@ function processTestSteps(steps, hierarchy) {
422
425
  }
423
426
  }
424
427
 
425
-
426
428
  function createSectionStep(metaStep) {
427
429
  return {
428
430
  category: 'user',
429
431
  title: metaStep.toString(), // Use built-in toString method
430
432
  duration: metaStep.duration || 0, // Use built-in duration
431
- steps: []
433
+ steps: [],
432
434
  };
433
435
  }
434
436
 
@@ -439,7 +441,7 @@ function createHookSection(hookName, steps) {
439
441
  category: 'hook',
440
442
  title: formatHookName(hookName),
441
443
  duration: 0,
442
- steps: []
444
+ steps: [],
443
445
  };
444
446
 
445
447
  for (const step of steps) {
@@ -457,7 +459,6 @@ function formatHookName(hookName) {
457
459
  return hookName.replace(/Hook$/, '');
458
460
  }
459
461
 
460
-
461
462
  // Format CodeceptJS step using its built-in methods
462
463
  function formatCodeceptStep(step) {
463
464
  if (!step) return null;
@@ -469,14 +470,14 @@ function formatCodeceptStep(step) {
469
470
  const formattedStep = {
470
471
  category,
471
472
  title,
472
- duration
473
+ duration,
473
474
  };
474
475
 
475
476
  // Add error if step failed
476
477
  if (step.status === 'failed' && step.err) {
477
478
  formattedStep.error = {
478
479
  message: step.err.message || 'Step failed',
479
- stack: step.err.stack || ''
480
+ stack: step.err.stack || '',
480
481
  };
481
482
  }
482
483
 
@@ -500,10 +501,9 @@ function formatHookStep(step) {
500
501
  return {
501
502
  category: 'hook',
502
503
  title,
503
- duration: step.duration || 0
504
+ duration: step.duration || 0,
504
505
  };
505
506
  }
506
507
 
507
-
508
508
  export { CodeceptReporter };
509
509
  export default CodeceptReporter;
@@ -84,7 +84,7 @@ function MochaReporter(runner, opts) {
84
84
  const artifacts = services.artifacts.get(test.fullTitle());
85
85
  const keyValues = services.keyValues.get(test.fullTitle());
86
86
  const links = services.links.get(test.fullTitle());
87
-
87
+
88
88
  client.addTestRun(STATUS.SKIPPED, {
89
89
  title: getTestName(test),
90
90
  suite_title: getSuiteTitle(test),
@@ -254,7 +254,7 @@ function generateTmpFilepath(filename = '') {
254
254
  */
255
255
  function extractTags(test) {
256
256
  const tagsSet = new Set();
257
-
257
+
258
258
  // Extract tags from test title (@tag format)
259
259
  const titleTagsMatch = test.title.match(/@\w+/g);
260
260
  if (titleTagsMatch) {
@@ -262,7 +262,7 @@ function extractTags(test) {
262
262
  tagsSet.add(tag.replace('@', '').toLowerCase());
263
263
  });
264
264
  }
265
-
265
+
266
266
  // Extract tags from test.tags (Playwright built-in tags)
267
267
  if (test.tags && Array.isArray(test.tags)) {
268
268
  test.tags.forEach(tag => {
package/src/bin/cli.js CHANGED
@@ -36,7 +36,7 @@ program
36
36
  .command('start')
37
37
  .description('Start a new run and return its ID')
38
38
  .option('--kind <type>', 'Specify run type: automated, manual, or mixed')
39
- .action(async (opts) => {
39
+ .action(async opts => {
40
40
  cleanLatestRunId();
41
41
 
42
42
  console.log('Starting a new Run on Testomat.io...');
package/src/client.js CHANGED
@@ -1,30 +1,18 @@
1
1
  import createDebugMessages from 'debug';
2
- import createCallsiteRecord from 'callsite-record';
3
- import { minimatch } from 'minimatch';
4
2
  import fs from 'fs';
5
3
  import pc from 'picocolors';
6
- import { randomUUID } from 'crypto';
7
4
  import { APP_PREFIX, STATUS } from './constants.js';
8
5
  import { pipesFactory } from './pipe/index.js';
9
6
  import { glob } from 'glob';
10
- import path, { sep } from 'path';
7
+ import path from 'path';
11
8
  import { fileURLToPath } from 'node:url';
12
9
  import { S3Uploader } from './uploader.js';
13
- import {
14
- formatStep,
15
- truncate,
16
- readLatestRunId,
17
- storeRunId,
18
- validateSuiteId,
19
- transformEnvVarToBoolean
20
- } from './utils/utils.js';
10
+ import { readLatestRunId, storeRunId, validateSuiteId, transformEnvVarToBoolean } from './utils/utils.js';
21
11
  import { filesize as prettyBytes } from 'filesize';
22
- import { stripVTControlCharacters } from 'util';
12
+ import { formatLogs, formatError, stripColors } from './utils/log-formatter.js';
23
13
 
24
14
  const debug = createDebugMessages('@testomatio/reporter:client');
25
15
 
26
- const stripColors = stripVTControlCharacters || ((str) => str?.replace(/\x1b\[[0-9;]*m/g, '') || '');
27
-
28
16
  // removed __dirname usage, because:
29
17
  // 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
30
18
  // 2. got error "__dirname already defined" in compiles js code (cjs dir)
@@ -163,19 +151,12 @@ class Client {
163
151
  /**
164
152
  * @type {TestData}
165
153
  */
166
- const {
167
- rid,
168
- error = null,
169
- steps: originalSteps,
170
- title,
171
- suite_title,
172
- } = testData;
154
+ const { rid, error = null, steps: originalSteps, title, suite_title } = testData;
173
155
  let steps = originalSteps;
174
156
 
175
157
  const uploadedFiles = [];
176
158
  const stackArtifactsEnabled = transformEnvVarToBoolean(process.env.TESTOMATIO_STACK_ARTIFACTS);
177
159
 
178
-
179
160
  const {
180
161
  time = 0,
181
162
  example = null,
@@ -204,24 +185,24 @@ class Client {
204
185
 
205
186
  let errorFormatted = '';
206
187
  if (error) {
207
- errorFormatted += this.formatError(error) || '';
188
+ errorFormatted += formatError(error) || '';
208
189
  message = error?.message;
209
190
  }
210
191
 
211
- let fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
192
+ let fullLogs = formatLogs({ error: errorFormatted, steps, logs: testData.logs });
212
193
 
213
194
  if (stackArtifactsEnabled && fullLogs?.trim()?.length > 0) {
214
195
  uploadedFiles.push(
215
- this.uploader.uploadFileAsBuffer(
216
- Buffer.from(stripColors(fullLogs), 'utf8'),
217
- [this.runId, rid, `logs_${+new Date}.log`]
218
- )
196
+ this.uploader.uploadFileAsBuffer(Buffer.from(stripColors(fullLogs), 'utf8'), [
197
+ this.runId,
198
+ rid,
199
+ `logs_${+new Date()}.log`,
200
+ ]),
219
201
  );
220
202
  fullLogs = '';
221
203
  steps = null;
222
204
  }
223
205
 
224
-
225
206
  if (!this.pipes || !this.pipes.length)
226
207
  this.pipes = await pipesFactory(this.paramsForPipesFactory || {}, this.pipeStore);
227
208
 
@@ -232,7 +213,7 @@ class Client {
232
213
  return [];
233
214
  }
234
215
 
235
- if (isTestShouldBeExculedFromReport(testData)) return [];
216
+ if (isTestShouldBeExcludedFromReport(testData)) return [];
236
217
 
237
218
  if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
238
219
  debug('Skipping test from report', testData?.title);
@@ -408,84 +389,6 @@ class Client {
408
389
 
409
390
  return this.queue;
410
391
  }
411
-
412
- /**
413
- * Returns the formatted stack including the stack trace, steps, and logs.
414
- * @returns {string}
415
- */
416
- formatLogs({ error, steps, logs }) {
417
- error = error?.trim();
418
- logs = logs?.trim().split('\n').map(l => truncate(l)).join('\n');
419
-
420
- if (Array.isArray(steps)) {
421
- steps = steps
422
- .map(step => formatStep(step))
423
- .flat()
424
- .join('\n');
425
- }
426
-
427
- let testLogs = '';
428
- if (steps) testLogs += `${pc.bold(pc.blue('################[ Steps ]################'))}\n${steps}\n\n`;
429
- if (logs) testLogs += `${pc.bold(pc.gray('################[ Logs ]################'))}\n${logs}\n\n`;
430
- if (error) testLogs += `${pc.bold(pc.red('################[ Failure ]################'))}\n${error}`;
431
- return testLogs;
432
- }
433
-
434
- formatError(error, message) {
435
- if (!message) message = error.message;
436
- if (error.inspect) message = error.inspect() || '';
437
-
438
- let stack = '';
439
- if (error.name) stack += `${pc.red(error.name)}`;
440
- if (error.operator) stack += ` (${pc.red(error.operator)})`;
441
- // add new line if something was added to stack
442
- if (stack) stack += ': ';
443
-
444
- stack += `${message}\n`;
445
-
446
- if (error.diff) {
447
- // diff for vitest
448
- stack += error.diff;
449
- stack += '\n\n';
450
- } else if (error.actual && error.expected && error.actual !== error.expected) {
451
- // diffs for mocha, cypress, codeceptjs style
452
- stack += `\n\n${pc.bold(pc.green('+ expected'))} ${pc.bold(pc.red('- actual'))}`;
453
- stack += `\n${pc.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
454
- stack += `\n${pc.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
455
- stack += '\n\n';
456
- }
457
-
458
- const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
459
-
460
- try {
461
- let hasFrame = false;
462
- const record = createCallsiteRecord({
463
- forError: error,
464
- isCallsiteFrame: frame => {
465
- if (customFilter && minimatch(frame.fileName, customFilter)) return false;
466
- if (hasFrame) return false;
467
- if (isNotInternalFrame(frame)) hasFrame = true;
468
- return hasFrame;
469
- },
470
- });
471
- // @ts-ignore
472
- if (record && !record.filename.startsWith('http')) {
473
- stack += record.renderSync({ stackFilter: isNotInternalFrame });
474
- }
475
- return stack;
476
- } catch (e) {
477
- console.log(e);
478
- }
479
- }
480
- }
481
-
482
- function isNotInternalFrame(frame) {
483
- return (
484
- frame.getFileName() &&
485
- frame.getFileName().includes(sep) &&
486
- !frame.getFileName().includes('node_modules') &&
487
- !frame.getFileName().includes('internal')
488
- );
489
392
  }
490
393
 
491
394
  /**
@@ -493,7 +396,7 @@ function isNotInternalFrame(frame) {
493
396
  * @param {TestData} testData
494
397
  * @returns boolean
495
398
  */
496
- function isTestShouldBeExculedFromReport(testData) {
399
+ function isTestShouldBeExcludedFromReport(testData) {
497
400
  // const fileName = path.basename(test.location?.file || '');
498
401
  const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
499
402
  if (!globExcludeFilesPattern) return false;
@@ -503,12 +406,12 @@ function isTestShouldBeExculedFromReport(testData) {
503
406
  return false;
504
407
  }
505
408
 
506
- const excludeParretnsList = globExcludeFilesPattern.split(';');
409
+ const excludePatternsList = globExcludeFilesPattern.split(';');
507
410
 
508
411
  // as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
509
412
  if (!listOfTestFilesToExcludeFromReport) {
510
413
  // list of files with relative paths
511
- listOfTestFilesToExcludeFromReport = glob.sync(excludeParretnsList, { ignore: '**/node_modules/**' });
414
+ listOfTestFilesToExcludeFromReport = glob.sync(excludePatternsList, { ignore: '**/node_modules/**' });
512
415
  debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
513
416
  }
514
417
 
@@ -8,7 +8,10 @@ class CSharpAdapter extends Adapter {
8
8
  const exampleMatch = t.title.match(/\((.*?)\)/);
9
9
  if (exampleMatch) {
10
10
  // Extract parameters as object with numeric keys for API
11
- const params = exampleMatch[1].split(',').map(param => param.trim()).filter(param => param !== '');
11
+ const params = exampleMatch[1]
12
+ .split(',')
13
+ .map(param => param.trim())
14
+ .filter(param => param !== '');
12
15
  t.example = {};
13
16
  params.forEach((param, index) => {
14
17
  t.example[index] = param;
@@ -393,13 +393,15 @@ export class NUnitXmlParser {
393
393
  }
394
394
 
395
395
  // Clean up parameters - remove quotes if they wrap the entire parameter and filter empty ones
396
- return parameters.map(param => {
397
- param = param.trim();
398
- if ((param.startsWith('"') && param.endsWith('"')) || (param.startsWith("'") && param.endsWith("'"))) {
399
- return param.slice(1, -1);
400
- }
401
- return param;
402
- }).filter(p => !!p);
396
+ return parameters
397
+ .map(param => {
398
+ param = param.trim();
399
+ if ((param.startsWith('"') && param.endsWith('"')) || (param.startsWith("'") && param.endsWith("'"))) {
400
+ return param.slice(1, -1);
401
+ }
402
+ return param;
403
+ })
404
+ .filter(p => !!p);
403
405
  }
404
406
 
405
407
  /**
@@ -44,8 +44,8 @@ export class BitbucketPipe {
44
44
  baseURL: 'https://api.bitbucket.org/2.0',
45
45
  headers: {
46
46
  'Content-Type': 'application/json',
47
- 'Authorization': `Bearer ${this.token}`
48
- }
47
+ Authorization: `Bearer ${this.token}`,
48
+ },
49
49
  });
50
50
 
51
51
  debug('Bitbucket Pipe: Enabled');
@@ -186,7 +186,7 @@ export class BitbucketPipe {
186
186
  const addCommentResponse = await this.client.request({
187
187
  method: 'POST',
188
188
  url: commentsRequestURL,
189
- data: { content: { raw: body } }
189
+ data: { content: { raw: body } },
190
190
  });
191
191
 
192
192
  const commentID = addCommentResponse.data.id;
@@ -221,7 +221,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
221
221
  try {
222
222
  const response = await client.request({
223
223
  method: 'GET',
224
- url: commentsRequestURL
224
+ url: commentsRequestURL,
225
225
  });
226
226
  comments = response.data.values;
227
227
  } catch (e) {
@@ -238,7 +238,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
238
238
  const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
239
239
  await client.request({
240
240
  method: 'DELETE',
241
- url: deleteCommentURL
241
+ url: deleteCommentURL,
242
242
  });
243
243
  } catch (e) {
244
244
  console.warn(`Can't delete previously added comment with testomat.io report. Ignored.`);
package/src/pipe/debug.js CHANGED
@@ -93,8 +93,7 @@ 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
- }
97
- else this.batch.tests.push(data);
96
+ } else this.batch.tests.push(data);
98
97
 
99
98
  if (!this.batch.intervalFunction) await this.batchUpload();
100
99
  }
@@ -49,7 +49,7 @@ class GitLabPipe {
49
49
  baseURL: 'https://gitlab.com/api/v4',
50
50
  headers: {
51
51
  'Content-Type': 'application/json',
52
- }
52
+ },
53
53
  });
54
54
 
55
55
  debug('GitLab Pipe: Enabled');
@@ -176,7 +176,7 @@ class GitLabPipe {
176
176
  method: 'POST',
177
177
  url: commentsRequestURL,
178
178
  params: { access_token: this.token },
179
- data: { body }
179
+ data: { body },
180
180
  });
181
181
 
182
182
  const commentID = addCommentResponse.data.id;
@@ -212,7 +212,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
212
212
  const response = await client.request({
213
213
  method: 'GET',
214
214
  url: commentsRequestURL,
215
- params: { access_token: token }
215
+ params: { access_token: token },
216
216
  });
217
217
  comments = response.data;
218
218
  } catch (e) {
@@ -230,7 +230,7 @@ async function deletePreviousReport(client, commentsRequestURL, hiddenCommentDat
230
230
  await client.request({
231
231
  method: 'DELETE',
232
232
  url: deleteCommentURL,
233
- params: { access_token: token }
233
+ params: { access_token: token },
234
234
  });
235
235
  } catch (e) {
236
236
  console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);