@testomatio/reporter 1.6.0-beta-artifacts → 1.6.0

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.
@@ -66,7 +66,7 @@ function MochaReporter(runner, opts) {
66
66
  test_id: testId,
67
67
  suite_title: getSuiteTitle(test),
68
68
  title: getTestName(test),
69
- code: test.body.toString(),
69
+ code: process.env.TESTOMATIO_UPDATE_CODE ? test.body.toString() : '',
70
70
  file: getFile(test),
71
71
  time: test.duration,
72
72
  logs,
@@ -82,7 +82,7 @@ function MochaReporter(runner, opts) {
82
82
  client.addTestRun(STATUS.SKIPPED, {
83
83
  title: getTestName(test),
84
84
  suite_title: getSuiteTitle(test),
85
- code: test.body.toString(),
85
+ code: process.env.TESTOMATIO_UPDATE_CODE ? test.body.toString() : '',
86
86
  file: getFile(test),
87
87
  test_id: testId,
88
88
  time: test.duration,
@@ -102,7 +102,7 @@ function MochaReporter(runner, opts) {
102
102
  file: getFile(test),
103
103
  test_id: testId,
104
104
  title: getTestName(test),
105
- code: test.body.toString(),
105
+ code: process.env.TESTOMATIO_UPDATE_CODE ? test.body.toString() : '',
106
106
  time: test.duration,
107
107
  logs,
108
108
  });
@@ -104,7 +104,6 @@ class PlaywrightReporter {
104
104
  if (!this.client) return;
105
105
 
106
106
  await Promise.all(reportTestPromises);
107
- await this.client.updateRunStatus(checkStatus(result.status));
108
107
 
109
108
  if (!this.uploads.length) return;
110
109
 
@@ -112,6 +111,7 @@ class PlaywrightReporter {
112
111
 
113
112
  const promises = [];
114
113
 
114
+ // ? possible move to addTestRun (needs investigation if files are ready)
115
115
  for (const upload of this.uploads) {
116
116
  const { rid, file, title } = upload;
117
117
 
@@ -136,6 +136,8 @@ class PlaywrightReporter {
136
136
  );
137
137
  }
138
138
  await Promise.all(promises);
139
+
140
+ await this.client.updateRunStatus(checkStatus(result.status));
139
141
  }
140
142
  }
141
143
 
@@ -70,10 +70,10 @@ class WebdriverReporter extends WDIOReporter {
70
70
  .map(el => Buffer.from(el.result.value, 'base64'));
71
71
 
72
72
  await this.client.addTestRun(state, {
73
- // manuallyAttachedArtifacts: test.artifacts,
73
+ manuallyAttachedArtifacts: test.artifacts,
74
74
  error,
75
75
  logs: test.logs,
76
- // meta: test.meta,
76
+ meta: test.meta,
77
77
  title,
78
78
  test_id: testId,
79
79
  time: duration,
package/lib/bin/cli.js CHANGED
@@ -10,6 +10,7 @@ const { APP_PREFIX, STATUS } = require('../constants');
10
10
  const { version } = require('../../package.json');
11
11
  const config = require('../config');
12
12
  const { readLatestRunId } = require('../utils/utils');
13
+ const { filesize: prettyBytes } = require('filesize');
13
14
 
14
15
  console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
15
16
 
@@ -193,7 +194,7 @@ program
193
194
  if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
194
195
 
195
196
  if (!testruns.length) {
196
- console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
197
+ console.log(APP_PREFIX, '🗄️ Total artifacts:', numTotalArtifacts);
197
198
  if (numTotalArtifacts) {
198
199
  console.log(APP_PREFIX, 'No new artifacts to upload');
199
200
  console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
@@ -211,16 +212,38 @@ program
211
212
 
212
213
  await client.createRun();
213
214
  client.uploader.checkEnabled();
214
- client.uploader.disbleLogStorage();
215
+ client.uploader.disableLogStorage();
215
216
 
216
217
  for (const rid in testrunsByRid) {
217
218
  const files = testrunsByRid[rid];
218
219
  await client.addTestRun(undefined, { rid, files });
219
220
  }
220
221
 
221
- console.log(APP_PREFIX, client.uploader.totalUploadsCount, 'artifacts uploaded');
222
- if (client.uploader.failedUploadsCount) {
223
- console.log(APP_PREFIX, client.uploader.failedUploadsCount, 'artifacts failed to upload');
222
+ console.log(APP_PREFIX, '🗄️', client.uploader.totalSuccessfulUploadsCount, 'artifacts 🟢uploaded');
223
+ const filesizeStrMaxLength = 7;
224
+
225
+ if (client.uploader.failedUploads.length) {
226
+ console.log(
227
+ '\n',
228
+ APP_PREFIX,
229
+ '🗄️',
230
+ client.uploader.failedUploads.length,
231
+ `artifacts 🔴${chalk.bold('failed')} to upload`,
232
+ );
233
+
234
+ const failedUploads = client.uploader.failedUploads.map(({ path, size }) => ({
235
+ relativePath: path.replace(process.cwd(), ''),
236
+ sizePretty: prettyBytes(size, { round: 0 }).toString(),
237
+ }));
238
+
239
+ const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
240
+ failedUploads.forEach(upload => {
241
+ console.log(
242
+ ` ${chalk.gray('|')} 🔴 ${upload.relativePath.padEnd(pathPadding)} ${chalk.gray(
243
+ `| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
244
+ )}`,
245
+ );
246
+ });
224
247
  }
225
248
  });
226
249
 
@@ -64,7 +64,7 @@ program
64
64
  await client.createRun();
65
65
 
66
66
  client.uploader.checkEnabled();
67
- client.uploader.disbleLogStorage();
67
+ client.uploader.disableLogStorage();
68
68
 
69
69
  for (const rid in testrunsByRid) {
70
70
  const files = testrunsByRid[rid];
@@ -74,9 +74,9 @@ program
74
74
  });
75
75
  }
76
76
 
77
- console.log(APP_PREFIX, client.uploader.totalUploadsCount, 'artifacts uploaded');
78
- if (client.uploader.failedUploadsCount) {
79
- console.log(APP_PREFIX, client.uploader.failedUploadsCount, 'artifacts failed to upload');
77
+ console.log(APP_PREFIX, client.uploader.totalSuccessfulUploadsCount, 'artifacts uploaded');
78
+ if (client.uploader.failedUploads.length) {
79
+ console.log(APP_PREFIX, client.uploader.failedUploads.length, 'artifacts failed to upload');
80
80
  }
81
81
  });
82
82
 
package/lib/client.js CHANGED
@@ -10,7 +10,8 @@ const { APP_PREFIX, STATUS } = require('./constants');
10
10
  const pipesFactory = require('./pipe');
11
11
  const { glob } = require('glob');
12
12
  const path = require('path');
13
- const { storeRunId } = require('./utils/utils');
13
+ const { storeRunId, formatStep } = require('./utils/utils');
14
+ const { filesize: prettyBytes } = require('filesize');
14
15
 
15
16
  let listOfTestFilesToExcludeFromReport = null;
16
17
 
@@ -250,32 +251,65 @@ class Client {
250
251
  this.queue = this.queue
251
252
  .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
252
253
  .then(() => {
253
- debug('TOTAL artifacts', this.uploader.totalUploadsCount);
254
- debug(`${this.uploader.skippedUploadsCount} artifacts skipped`);
255
-
256
- if (this.uploader.totalUploadsCount && this.uploader.isEnabled) {
254
+ if (this.uploader.totalSuccessfulUploadsCount && this.uploader.isEnabled) {
257
255
  console.log(
258
256
  APP_PREFIX,
259
- `🗄️ ${this.uploader.totalUploadsCount} artifacts ${
257
+ `🗄️ ${this.uploader.totalSuccessfulUploadsCount} artifacts ${
260
258
  process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
261
- } uploaded to S3 bucket`,
259
+ } 🟢 uploaded to S3 bucket`,
262
260
  );
263
261
  }
264
262
 
265
- if (this.uploader.failedUploadsCount) {
263
+ const filesizeStrMaxLength = 7;
264
+
265
+ if (this.uploader.failedUploads.length) {
266
266
  console.log(
267
+ '\n',
267
268
  APP_PREFIX,
268
- chalk.gray('[CLIENT]'),
269
- `${this.uploader.failedUploadsCount} artifacts failed to upload`,
269
+ `🗄️ ${this.uploader.failedUploads.length} artifacts 🔴${chalk.bold('failed')} to upload`,
270
270
  );
271
+ const failedUploads = this.uploader.failedUploads.map(file => ({
272
+ relativePath: file.path.replace(process.cwd(), ''),
273
+ sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
274
+ }));
275
+
276
+ const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
277
+
278
+ failedUploads.forEach(upload => {
279
+ console.log(
280
+ ` ${chalk.gray('|')} 🔴 ${upload.relativePath.padEnd(pathPadding)} ${chalk.gray(
281
+ `| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
282
+ )}`,
283
+ );
284
+ });
271
285
  }
272
286
 
273
- if (this.uploader.isEnabled && this.uploader.skippedUploadsCount) {
274
- console.log(APP_PREFIX, `${chalk.bold(this.uploader.skippedUploadsCount)} artifacts skipped to upload`);
287
+ if (this.uploader.isEnabled && this.uploader.skippedUploads.length) {
288
+ console.log(
289
+ '\n',
290
+ APP_PREFIX,
291
+ `🗄️ ${chalk.bold(this.uploader.skippedUploads.length)} artifacts uploading 🟡${chalk.bold(
292
+ 'skipped',
293
+ )} (due to large size)`,
294
+ );
295
+ const skippedUploads = this.uploader.skippedUploads.map(file => ({
296
+ relativePath: file.path.replace(process.cwd(), ''),
297
+ sizePretty: file.size === null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
298
+ }));
299
+ const pathPadding = Math.max(...skippedUploads.map(upload => upload.relativePath.length)) + 1;
300
+ skippedUploads.forEach(upload => {
301
+ console.log(
302
+ ` ${chalk.gray('|')} 🟡 ${upload.relativePath.padEnd(pathPadding)} ${chalk.gray(
303
+ `| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
304
+ )}`,
305
+ );
306
+ });
275
307
  }
276
308
 
277
- if (this.uploader.skippedUploadsCount || this.uploader.failedUploadsCount) {
278
- const command = `TESTOMATIO_RUN=${this.runId} npx @testomatio/reporter upload-artifacts`;
309
+ if (this.uploader.skippedUploads.length || this.uploader.failedUploads.length) {
310
+ const command = `TESTOMATIO=<your_api_key> TESTOMATIO_RUN=${
311
+ this.runId
312
+ } npx @testomatio/reporter upload-artifacts`;
279
313
  console.log(
280
314
  APP_PREFIX,
281
315
  `Run "${chalk.magenta(command)}" with valid S3 credentials to upload skipped & failed artifacts`,
@@ -365,24 +399,6 @@ function isNotInternalFrame(frame) {
365
399
  );
366
400
  }
367
401
 
368
- function formatStep(step, shift = 0) {
369
- const prefix = ' '.repeat(shift);
370
-
371
- const lines = [];
372
-
373
- if (step.error) {
374
- lines.push(`${prefix}${chalk.red(step.title)} ${chalk.gray(`${step.duration}ms`)}`);
375
- } else {
376
- lines.push(`${prefix}${step.title} ${chalk.gray(`${step.duration}ms`)}`);
377
- }
378
-
379
- for (const child of step.steps || []) {
380
- lines.push(...formatStep(child, shift + 2));
381
- }
382
-
383
- return lines;
384
- }
385
-
386
402
  /**
387
403
  *
388
404
  * @param {TestData} testData
package/lib/constants.js CHANGED
@@ -3,7 +3,11 @@ const os = require('os');
3
3
  const path = require('path');
4
4
 
5
5
  const APP_PREFIX = chalk.gray('[TESTOMATIO]');
6
- const AXIOS_TIMEOUT = 20 * 1000; // sum = 20sec
6
+ const TESTOMATIO_REQUEST_TIMEOUT = parseInt(process.env.TESTOMATIO_REQUEST_TIMEOUT, 10);
7
+ if (TESTOMATIO_REQUEST_TIMEOUT) {
8
+ console.log(`${APP_PREFIX} Request timeout is set to ${TESTOMATIO_REQUEST_TIMEOUT / 1000}s`);
9
+ }
10
+ const AXIOS_TIMEOUT = TESTOMATIO_REQUEST_TIMEOUT || 20 * 1000;
7
11
 
8
12
  const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
9
13
 
@@ -0,0 +1,102 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const { APP_PREFIX } = require('../constants');
5
+ const debug = require('debug')('@testomatio/reporter:pipe:debug');
6
+ // upgrade to latest for ESM
7
+ const prettyMs = require('pretty-ms');
8
+
9
+ class DebugPipe {
10
+ constructor(params, store) {
11
+ this.isEnabled = !!process.env.TESTOMATIO_DEBUG;
12
+ if (this.isEnabled) {
13
+ this.batch = {
14
+ isEnabled: params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
15
+ intervalFunction: null,
16
+ intervalTime: 5000,
17
+ tests: [],
18
+ batchIndex: 0,
19
+ };
20
+ this.logFilePath = path.join(os.tmpdir(), `testomatio.debug.${Date.now()}.json`);
21
+ this.store = store || {};
22
+
23
+ debug('Creating debug file:', this.logFilePath);
24
+ fs.writeFileSync(this.logFilePath, '');
25
+ console.log(APP_PREFIX, '🪲. Debug created:');
26
+ this.testomatioEnvVars = Object.keys(process.env)
27
+ .filter(key => key.startsWith('TESTOMATIO_'))
28
+ .reduce((acc, key) => {
29
+ acc[key] = process.env[key];
30
+ return acc;
31
+ }, {});
32
+ this.logToFile({ datetime: new Date().toISOString(), timestamp: Date.now() });
33
+ this.logToFile({ data: 'variables', testomatioEnvVars: this.testomatioEnvVars });
34
+ this.logToFile({ data: 'store', store: this.store || {} });
35
+ // Bind batchUpload to the instance
36
+ this.batchUpload = this.batchUpload.bind(this);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Logs data to a file if logging is enabled.
42
+ *
43
+ * @param {Object} logData - The data to be logged.
44
+ * @returns {Promise<void>} A promise that resolves when the log data has been appended to the file.
45
+ */
46
+ logToFile(logData) {
47
+ if (!this.isEnabled) return;
48
+ const timePassedFromLastAction = Date.now() - (this.lastActionTimestamp || Date.now());
49
+ this.lastActionTimestamp = Date.now();
50
+
51
+ const logLine = JSON.stringify({ t: `+${prettyMs(timePassedFromLastAction)}`, ...logData });
52
+ fs.appendFileSync(this.logFilePath, `${logLine}\n`);
53
+ }
54
+
55
+ async prepareRun(opts) {
56
+ if (!this.isEnabled) return [];
57
+
58
+ this.logToFile({ action: 'prepareRun', data: opts });
59
+ }
60
+
61
+ async createRun(params = {}) {
62
+ if (params.isBatchEnabled === true || params.isBatchEnabled === false) this.batch.isEnabled = params.isBatchEnabled;
63
+
64
+ if (!this.isEnabled) return {};
65
+ if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.batchUpload, this.batch.intervalTime);
66
+
67
+ this.logToFile({ action: 'createRun', params });
68
+ }
69
+
70
+ async addTest(data) {
71
+ if (!this.isEnabled) return;
72
+
73
+ if (!this.batch.isEnabled) this.logToFile({ action: 'addTest', testId: data });
74
+ else this.batch.tests.push(data);
75
+
76
+ if (!this.batch.intervalFunction) await this.batchUpload();
77
+ }
78
+
79
+ async batchUpload() {
80
+ this.batch.batchIndex++;
81
+ if (!this.batch.isEnabled) return;
82
+ if (!this.batch.tests.length) return;
83
+
84
+ const testsToSend = this.batch.tests.splice(0);
85
+
86
+ this.logToFile({ action: 'addTestsBatch', tests: testsToSend });
87
+ }
88
+
89
+ async finishRun(params) {
90
+ if (!this.isEnabled) return;
91
+ this.logToFile({ actions: 'finishRun', params });
92
+ await this.batchUpload();
93
+ if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
94
+ console.log(APP_PREFIX, '🪲. Debug Saved to', this.logFilePath);
95
+ }
96
+
97
+ toString() {
98
+ return 'Debug Reporter';
99
+ }
100
+ }
101
+
102
+ module.exports = DebugPipe;
package/lib/pipe/html.js CHANGED
@@ -5,8 +5,7 @@ const path = require('path');
5
5
  const chalk = require('chalk');
6
6
  const handlebars = require('handlebars');
7
7
  const fileUrl = require('file-url');
8
-
9
- const { fileSystem, isSameTest, ansiRegExp } = require('../utils/utils');
8
+ const { fileSystem, isSameTest, ansiRegExp, formatStep } = require('../utils/utils');
10
9
  const { HTML_REPORT } = require('../constants');
11
10
 
12
11
  class HtmlPipe {
@@ -123,6 +122,14 @@ class HtmlPipe {
123
122
  }
124
123
 
125
124
  tests.forEach(test => {
125
+ // steps could be an array or a string
126
+ test.steps = Array.isArray(test.steps)
127
+ ? (test.steps = test.steps
128
+ .map(step => formatStep(step))
129
+ .flat()
130
+ .join('\n'))
131
+ : test.steps;
132
+
126
133
  if (!test.message?.trim()) {
127
134
  test.message = "This test has no 'message' code";
128
135
  }
package/lib/pipe/index.js CHANGED
@@ -8,6 +8,7 @@ const GitLabPipe = require('./gitlab');
8
8
  const CsvPipe = require('./csv');
9
9
  const HtmlPipe = require('./html');
10
10
  const BitbucketPipe = require('./bitbucket');
11
+ const DebugPipe = require('./debug');
11
12
 
12
13
  function PipeFactory(params, opts) {
13
14
  const extraPipes = [];
@@ -47,6 +48,7 @@ function PipeFactory(params, opts) {
47
48
  new CsvPipe(params, opts),
48
49
  new HtmlPipe(params, opts),
49
50
  new BitbucketPipe(params, opts),
51
+ new DebugPipe(params, opts),
50
52
  ...extraPipes,
51
53
  ];
52
54
 
@@ -218,11 +218,13 @@ class TestomatioPipe {
218
218
  process.env.runId = this.runId;
219
219
  debug('Run created', this.runId);
220
220
  } catch (err) {
221
- const errorInfo = `${err?.response?.statusText} ${chalk.red(err?.status)} ${err.message}`;
221
+ const errorText = err.response?.data?.message || err.message;
222
+ console.log(errorText || err);
223
+ if (!this.apiKey) console.error('Testomat.io API key is not set');
224
+ if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
222
225
  console.error(
223
226
  APP_PREFIX,
224
- 'Error creating Testomat.io report, please check if your API key is valid. Skipping report\n',
225
- errorInfo || JSON.stringify(err),
227
+ 'Error creating Testomat.io report (see details above), please check if your API key is valid. Skipping report'
226
228
  );
227
229
  printCreateIssue(err);
228
230
  }
package/lib/reporter.js CHANGED
@@ -27,4 +27,4 @@ module.exports.logger = services.logger;
27
27
  module.exports.meta = reporterFunctions.keyValue;
28
28
  module.exports.step = reporterFunctions.step;
29
29
  module.exports.TestomatClient = TestomatClient;
30
- module.exports.TRConstants = TRConstants;
30
+ module.exports.TRConstants = TRConstants;
package/lib/uploader.js CHANGED
@@ -7,6 +7,7 @@ const path = require('path');
7
7
  const promiseRetry = require('promise-retry');
8
8
  const chalk = require('chalk');
9
9
  const { APP_PREFIX } = require('./constants');
10
+ const { filesize: prettyBytes } = require('filesize');
10
11
 
11
12
  class S3Uploader {
12
13
  constructor() {
@@ -15,9 +16,12 @@ class S3Uploader {
15
16
  this.config = undefined;
16
17
 
17
18
  // counters
18
- this.skippedUploadsCount = 0;
19
- this.failedUploadsCount = 0;
20
- this.totalUploadsCount = 0;
19
+ /**
20
+ * @type {{path: string, size: number}[]}
21
+ */
22
+ this.skippedUploads = [];
23
+ this.failedUploads = [];
24
+ this.totalSuccessfulUploadsCount = 0;
21
25
 
22
26
  this.succesfulUploads = {};
23
27
 
@@ -75,11 +79,18 @@ class S3Uploader {
75
79
  this.storeEnabled = true;
76
80
  }
77
81
 
78
- disbleLogStorage() {
82
+ disableLogStorage() {
79
83
  this.storeEnabled = false;
80
84
  }
81
85
 
82
- async #uploadToS3(Body, Key) {
86
+ /**
87
+ *
88
+ * @param {*} Body
89
+ * @param {*} Key
90
+ * @param {{path: string, size?: number}} file
91
+ * @returns
92
+ */
93
+ async #uploadToS3(Body, Key, file) {
83
94
  const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
84
95
  const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
85
96
 
@@ -108,20 +119,20 @@ class S3Uploader {
108
119
  });
109
120
 
110
121
  const link = await this.getS3LocationLink(upload);
111
- this.totalUploadsCount++;
122
+ this.totalSuccessfulUploadsCount++;
112
123
  this.succesfulUploads[Key] = link;
113
124
  return link;
114
125
  } catch (e) {
115
- this.failedUploadsCount++;
126
+ this.failedUploads.push({ path: file.path, size: file.size });
116
127
  debug('S3 uploading error:', e);
117
- console.log(APP_PREFIX, 'Upload failed:', e.message, this.getMaskedConfig());
128
+ console.log(APP_PREFIX, 'Upload failed:', e.message, '\nConfig:\n', this.getMaskedConfig());
118
129
  }
119
130
  }
120
131
 
121
132
  /**
122
- * Returns an array of uploaded files list
123
- *
124
- * @returns {{rid: string, file: string, uploaded: boolean}[]}
133
+ * Returns an array of uploaded files
134
+ *
135
+ * @returns {{rid: string, file: string, uploaded: boolean}[]}
125
136
  */
126
137
  readUploadedFiles(runId) {
127
138
  const tempFilePath = this.#getFilePathWithUploadsList(runId);
@@ -181,19 +192,30 @@ class S3Uploader {
181
192
  }
182
193
 
183
194
  /**
184
- *
185
- * @param {*} filePath
195
+ *
196
+ * @param {*} filePath
186
197
  * @param {*} pathInS3 contains runId, rid and filename
187
- * @returns
198
+ * @returns
188
199
  */
189
200
  async uploadFileByPath(filePath, pathInS3) {
190
201
  const [runId, rid] = pathInS3;
191
202
 
192
203
  if (!filePath) return;
193
204
 
205
+ let fileSize = null;
206
+ let fileSizeInMb = null;
207
+
208
+ try {
209
+ // file may not exist
210
+ fileSize = fs.statSync(filePath).size;
211
+ fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
212
+ } catch (e) {
213
+ debug(`File ${filePath} does not exist`);
214
+ }
215
+
194
216
  if (!this.isEnabled) {
195
217
  this.storeUploadedFile(filePath, runId, rid, false);
196
- this.skippedUploadsCount++;
218
+ this.skippedUploads.push({ path: filePath, size: fileSize });
197
219
  return;
198
220
  }
199
221
 
@@ -208,26 +230,37 @@ class S3Uploader {
208
230
  return;
209
231
  }
210
232
 
211
- const fileSize = fs.statSync(filePath).size;
212
- const fileSizeInMb = fileSize / (1024 * 1024);
213
-
214
- if (TESTOMATIO_ARTIFACT_MAX_SIZE_MB && fileSizeInMb > parseInt(TESTOMATIO_ARTIFACT_MAX_SIZE_MB, 10)) {
215
- this.skippedUploadsCount++;
216
- console.error(chalk.yellow(`Artifacts file ${filePath} exceeds the maximum allowed size. Skipping...`));
233
+ // skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
234
+ if (
235
+ this.storeEnabled &&
236
+ TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
237
+ fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)
238
+ ) {
239
+ const skippedArtifact = { path: filePath, size: fileSize };
240
+ this.storeUploadedFile(filePath, runId, rid, false);
241
+ this.skippedUploads.push(skippedArtifact);
242
+ debug(
243
+ chalk.yellow(`Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size. Skipping.`),
244
+ );
217
245
  return;
218
246
  }
219
- debug('File:', filePath, 'exists, size:', fileSizeInMb.toFixed(2), 'MB');
247
+ debug('File:', filePath, 'exists, size:', prettyBytes(fileSize));
220
248
 
221
249
  const fileStream = fs.createReadStream(filePath);
222
250
  const Key = pathInS3.join('/');
223
251
 
224
- const link = await this.#uploadToS3(fileStream, Key);
252
+ const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
225
253
 
226
254
  this.storeUploadedFile(filePath, runId, rid, !!link);
227
255
 
228
256
  return link;
229
257
  }
230
258
 
259
+ /**
260
+ * @param {Buffer} buffer
261
+ * @param {string[]} pathInS3
262
+ * @returns
263
+ */
231
264
  async uploadFileAsBuffer(buffer, pathInS3) {
232
265
  if (!this.isEnabled) return;
233
266
 
@@ -238,7 +271,7 @@ class S3Uploader {
238
271
  Key = `${Key}.${ext}`;
239
272
  }
240
273
 
241
- return this.#uploadToS3(buffer, Key);
274
+ return this.#uploadToS3(buffer, Key, { path: Key });
242
275
  }
243
276
 
244
277
  async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
@@ -340,6 +340,24 @@ function readLatestRunId() {
340
340
  }
341
341
  }
342
342
 
343
+ function formatStep(step, shift = 0) {
344
+ const prefix = ' '.repeat(shift);
345
+
346
+ const lines = [];
347
+
348
+ if (step.error) {
349
+ lines.push(`${prefix}${chalk.red(step.title)} ${chalk.gray(`${step.duration}ms`)}`);
350
+ } else {
351
+ lines.push(`${prefix}${step.title} ${chalk.gray(`${step.duration}ms`)}`);
352
+ }
353
+
354
+ for (const child of step.steps || []) {
355
+ lines.push(...formatStep(child, shift + 2));
356
+ }
357
+
358
+ return lines;
359
+ }
360
+
343
361
  module.exports = {
344
362
  storeRunId,
345
363
  readLatestRunId,
@@ -350,6 +368,7 @@ module.exports = {
350
368
  fetchIdFromOutput,
351
369
  fetchFilesFromStackTrace,
352
370
  fileSystem,
371
+ formatStep,
353
372
  getCurrentDateTime,
354
373
  specificTestInfo,
355
374
  isValidUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.6.0-beta-artifacts",
3
+ "version": "1.6.0",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",
@@ -23,6 +23,7 @@
23
23
  "dotenv": "^16.0.1",
24
24
  "fast-xml-parser": "^4.4.1",
25
25
  "file-url": "3.0.0",
26
+ "filesize": "^10.1.6",
26
27
  "glob": "^10.3",
27
28
  "handlebars": "^4.7.8",
28
29
  "has-flag": "^5.0.1",
@@ -32,6 +33,7 @@
32
33
  "lodash.memoize": "^4.1.2",
33
34
  "lodash.merge": "^4.6.2",
34
35
  "minimatch": "^9.0.3",
36
+ "pretty-ms": "^7.0.1",
35
37
  "promise-retry": "^2.0.1",
36
38
  "strip-ansi": "^7.1.0",
37
39
  "uuid": "^9.0.0"