@testomatio/reporter 1.5.1-beta β†’ 1.6.0-beta-1-artifacts

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
@@ -11,7 +11,7 @@ Testomat.io Reporter (this npm package) supports:
11
11
  - πŸ„ Integarion with all popular [JavaScript/TypeScript frameworks](./docs/frameworks.md)
12
12
  - πŸ—„οΈ Screenshots, videos, traces [uploaded into S3 bucket](./docs/artifacts.md)
13
13
  - πŸ”Ž [Stack traces](./docs/stacktrace.md) and error messages
14
- - πŸ™ [GitHub](./docs/pipes/github.md) & [GitLab](./docs/pipes/gitlab.md) integration
14
+ - πŸ™ [GitHub](./docs/pipes/github.md), [GitLab](./docs/pipes/gitlab.md) & [Bitbucket](./docs/pipes/bitbucket.md) integration
15
15
  - πŸš… Realtime reports
16
16
  - πŸ—ƒοΈ Other test frameworks supported via [JUNit XML](./docs/junit.md)
17
17
  - πŸšΆβ€β™€οΈ Steps _(work in progress)_
@@ -128,6 +128,7 @@ Bring this reporter on CI and never lose test results again!
128
128
  - [Gitlab](./docs/pipes/gitlab.md)
129
129
  - [CSV](./docs/pipes/csv.md)
130
130
  - [HTML report](./docs/pipes/html.md)
131
+ - [Bitbucket](./docs/pipes/bitbucket.md)
131
132
  - πŸ““ [JUnit](./docs/junit.md)
132
133
  - πŸ—„οΈ [Artifacts](./docs/artifacts.md)
133
134
  - πŸ”‚ [Workflows](./docs/workflows.md)
@@ -2,7 +2,6 @@ const debug = require('debug')('@testomatio/reporter:adapter:codeceptjs');
2
2
  const chalk = require('chalk');
3
3
  const TestomatClient = require('../client');
4
4
  const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
5
- const upload = require('../fileUploader');
6
5
  const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
7
6
  const { services } = require('../services');
8
7
 
@@ -128,10 +127,8 @@ function CodeceptReporter(config) {
128
127
  // all tests were reported and we can upload videos
129
128
  await Promise.all(reportTestPromises);
130
129
 
131
- if (upload.isArtifactsEnabled()) {
132
- await uploadAttachments(client, videos, '🎞️ Uploading', 'video');
133
- await uploadAttachments(client, traces, 'πŸ“ Uploading', 'trace');
134
- }
130
+ await uploadAttachments(client, videos, '🎞️ Uploading', 'video');
131
+ await uploadAttachments(client, traces, 'πŸ“ Uploading', 'trace');
135
132
 
136
133
  const status = failedTests.length === 0 ? STATUS.PASSED : STATUS.FAILED;
137
134
  client.updateRunStatus(status);
@@ -304,13 +301,17 @@ function CodeceptReporter(config) {
304
301
  }
305
302
 
306
303
  async function uploadAttachments(client, attachments, messagePrefix, attachmentType) {
307
- if (!attachments?.length) return;
304
+ if (!attachments?.length) return;
308
305
 
309
- console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
306
+ if (client.uploader.isEnabled) console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
310
307
 
311
308
  const promises = attachments.map(async attachment => {
312
309
  const { rid, title, path, type } = attachment;
313
310
  const file = { path, type, title };
311
+
312
+ // we are storing file if upload is disabled
313
+ if (!client.uploader.isEnabled) return client.uploader.storeUploadedFile(path, client.runId, rid, false);
314
+
314
315
  return client.addTestRun(undefined, {
315
316
  ...stripExampleFromTitle(title),
316
317
  rid,
@@ -32,16 +32,14 @@ class CucumberReporter extends Formatter {
32
32
 
33
33
  this.client = new TestomatClient({ apiKey: options.apiKey || config.TESTOMATIO });
34
34
  this.status = STATUS.PASSED;
35
+ this.client.createRun();
35
36
  }
36
37
 
37
38
  parseEnvelope(envelope) {
38
39
  if (envelope.testRunStarted) {
39
40
  fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
40
41
  }
41
- if (envelope.testCaseStarted && this.client) {
42
- this.client.createRun();
43
- this.onTestCaseStarted(envelope.testCaseStarted);
44
- }
42
+ if (envelope.testCaseStarted && this.client) this.onTestCaseStarted(envelope.testCaseStarted);
45
43
  if (envelope.testCaseFinished) this.onTestCaseFinished(envelope.testCaseFinished);
46
44
  if (envelope.testRunFinished) this.onTestRunFinished(envelope);
47
45
  }
@@ -6,7 +6,6 @@ const { v4: uuidv4 } = require('uuid');
6
6
  const fs = require('fs');
7
7
  const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
8
8
  const TestomatioClient = require('../client');
9
- const { isArtifactsEnabled } = require('../fileUploader');
10
9
  const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
11
10
  // const debug = require('debug')('@testomatio/reporter:adapter:playwright');
12
11
  const { services } = require('../services');
@@ -106,31 +105,37 @@ class PlaywrightReporter {
106
105
 
107
106
  await Promise.all(reportTestPromises);
108
107
 
109
- if (this.uploads.length && isArtifactsEnabled()) {
110
- console.log(APP_PREFIX, `🎞️ Uploading ${this.uploads.length} files...`);
108
+ if (!this.uploads.length) return;
109
+
110
+ if (this.client.uploader.isEnabled) console.log(APP_PREFIX, `🎞️ Uploading ${this.uploads.length} files...`);
111
111
 
112
- const promises = [];
112
+ const promises = [];
113
113
 
114
- for (const upload of this.uploads) {
115
- const { rid, file, title } = upload;
114
+ for (const upload of this.uploads) {
115
+ const { rid, file, title } = upload;
116
116
 
117
- const files = upload.files.map(attachment => ({
118
- path: this.#getArtifactPath(attachment),
119
- title,
120
- type: attachment.contentType,
121
- }));
122
-
123
- promises.push(
124
- this.client.addTestRun(undefined, {
125
- rid,
126
- title,
127
- files,
128
- file,
129
- }),
130
- );
117
+ const files = upload.files.map(attachment => ({
118
+ path: this.#getArtifactPath(attachment),
119
+ title,
120
+ type: attachment.contentType,
121
+ }));
122
+
123
+ if (!this.client.uploader.isEnabled) {
124
+ files.forEach(f => this.client.uploader.storeUploadedFile(f, this.client.runId, rid, false));
125
+ continue;
131
126
  }
132
- await Promise.all(promises);
127
+
128
+ promises.push(
129
+ this.client.addTestRun(undefined, {
130
+ rid,
131
+ title,
132
+ files,
133
+ file,
134
+ }),
135
+ );
133
136
  }
137
+ await Promise.all(promises);
138
+
134
139
 
135
140
  await this.client.updateRunStatus(checkStatus(result.status));
136
141
  }
@@ -147,6 +152,9 @@ function checkStatus(status) {
147
152
  }
148
153
 
149
154
  function appendStep(step, shift = 0) {
155
+ // nesting too deep, ignore those steps
156
+ if (shift >= 10) return;
157
+
150
158
  let newCategory = step.category;
151
159
  switch (newCategory) {
152
160
  case 'test.step':
@@ -176,7 +184,7 @@ function appendStep(step, shift = 0) {
176
184
  };
177
185
 
178
186
  if (formattedSteps.length) {
179
- resultStep.steps = formattedSteps;
187
+ resultStep.steps = formattedSteps.filter(s => !!s);
180
188
  }
181
189
 
182
190
  if (step.error !== undefined) {
package/lib/bin/cli.js ADDED
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ const { program } = require('commander');
3
+ const spawn = require('cross-spawn');
4
+ const chalk = require('chalk');
5
+ const glob = require('glob');
6
+ const debug = require('debug')('@testomatio/reporter:cli');
7
+ const TestomatClient = require('../client');
8
+ const XmlReader = require('../xmlReader');
9
+ const { APP_PREFIX, STATUS } = require('../constants');
10
+ const { version } = require('../../package.json');
11
+ const config = require('../config');
12
+
13
+ console.log(chalk.cyan.bold(` 🀩 Testomat.io Reporter v${version}`));
14
+
15
+ program
16
+ .version(version)
17
+ .option('--env-file <envfile>', 'Load environment variables from env file')
18
+ .hook('preAction', (thisCommand) => {
19
+ const opts = thisCommand.opts();
20
+ if (opts.envFile) {
21
+ require('dotenv').config({ path: opts.envFile });
22
+ } else {
23
+ require('dotenv').config();
24
+ }
25
+ });
26
+
27
+ program
28
+ .command('start')
29
+ .description('Start a new run and return its ID')
30
+ .action(async () => {
31
+ console.log('Starting a new Run on Testomat.io...');
32
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
33
+ const client = new TestomatClient({ apiKey });
34
+
35
+ client.createRun().then(() => {
36
+ console.log(process.env.runId);
37
+ process.exit(0);
38
+ });
39
+ });
40
+
41
+ program
42
+ .command('finish')
43
+ .description('Finish Run by its ID')
44
+ .action(async () => {
45
+ if (!process.env.TESTOMATIO_RUN) {
46
+ console.log('TESTOMATIO_RUN environment variable must be set.');
47
+ return process.exit(1);
48
+ }
49
+
50
+ console.log('Finishing Run on Testomat.io...');
51
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
52
+ const client = new TestomatClient({ apiKey });
53
+
54
+ client.updateRunStatus(STATUS.FINISHED).then(() => {
55
+ console.log(chalk.yellow(`Run ${process.env.TESTOMATIO_RUN} was finished`));
56
+ process.exit(0);
57
+ });
58
+ });
59
+
60
+ program
61
+ .command('run')
62
+ .description('Run tests with the specified command')
63
+ .argument('<command>', 'Test runner command')
64
+ .option('--filter <filter>', 'Additional execution filter')
65
+ .action(async (command, opts) => {
66
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
67
+ const title = process.env.TESTOMATIO_TITLE;
68
+
69
+ if (!command || !command.split) {
70
+ console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
71
+ return process.exit(255);
72
+ }
73
+
74
+ const client = new TestomatClient({ apiKey, title, parallel: true });
75
+
76
+ if (opts.filter) {
77
+ const [pipe, ...optsArray] = opts.filter.split(':');
78
+ const pipeOptions = optsArray.join(':');
79
+
80
+ try {
81
+ const tests = await client.prepareRun({ pipe, pipeOptions });
82
+ if (tests && tests.length > 0) {
83
+ command += ` --grep (${tests.join('|')})`;
84
+ }
85
+ } catch (err) {
86
+ console.log(APP_PREFIX, err);
87
+ }
88
+ }
89
+
90
+ console.log(APP_PREFIX, `πŸš€ Running`, chalk.green(command));
91
+
92
+ const runTests = () => {
93
+ const testCmds = command.split(' ');
94
+ const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
95
+
96
+ cmd.on('close', code => {
97
+ const emoji = code === 0 ? '🟒' : 'πŸ”΄';
98
+ console.log(APP_PREFIX, emoji, `Runner exited with ${chalk.bold(code)}`);
99
+ if (apiKey) {
100
+ const status = code === 0 ? 'passed' : 'failed';
101
+ client.updateRunStatus(status, true);
102
+ }
103
+ process.exit(code);
104
+ });
105
+ };
106
+
107
+ if (apiKey) {
108
+ client.createRun().then(runTests);
109
+ } else {
110
+ runTests();
111
+ }
112
+ });
113
+
114
+ program
115
+ .command('xml')
116
+ .description('Parse XML reports and upload to Testomat.io')
117
+ .argument('<pattern>', 'XML file pattern')
118
+ .option('-d, --dir <dir>', 'Project directory')
119
+ .option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
120
+ .option('--lang <lang>', 'Language used (python, ruby, java)')
121
+ .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
122
+ .action(async (pattern, opts) => {
123
+ if (!pattern.endsWith('.xml')) {
124
+ pattern += '.xml';
125
+ }
126
+ let { javaTests, lang } = opts;
127
+ if (javaTests === true) javaTests = 'src/test/java';
128
+ lang = lang?.toLowerCase();
129
+ const runReader = new XmlReader({ javaTests, lang });
130
+ const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
131
+ if (!files.length) {
132
+ console.log(APP_PREFIX, `Report can't be created. No XML files found πŸ˜₯`);
133
+ process.exit(1);
134
+ }
135
+
136
+ for (const file of files) {
137
+ console.log(APP_PREFIX, `Parsed ${file}`);
138
+ runReader.parse(file);
139
+ }
140
+
141
+ let timeoutTimer;
142
+ if (opts.timelimit) {
143
+ timeoutTimer = setTimeout(() => {
144
+ console.log(`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
145
+ process.exit(0);
146
+ }, parseInt(opts.timelimit, 10) * 1000);
147
+ }
148
+
149
+ try {
150
+ await runReader.createRun();
151
+ await runReader.uploadData();
152
+ } catch (err) {
153
+ console.log(APP_PREFIX, 'Error updating status, skipping...', err);
154
+ }
155
+
156
+ if (timeoutTimer) clearTimeout(timeoutTimer);
157
+ });
158
+
159
+ program
160
+ .command('upload-artifacts')
161
+ .description('Upload artifacts to Testomat.io')
162
+ .option('--force', 'Re-upload artifacts even if they were uploaded before')
163
+ .action(async (opts) => {
164
+ const apiKey = config.TESTOMATIO;
165
+ process.env.TESTOMATIO_DISABLE_ARTIFACTS = '';
166
+
167
+ const client = new TestomatClient({
168
+ apiKey,
169
+ isBatchEnabled: false,
170
+ });
171
+
172
+ if (!process.env.TESTOMATIO_RUN) {
173
+ console.log(APP_PREFIX, 'No run ID provided');
174
+ process.exit(1);
175
+ }
176
+
177
+ let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
178
+ const numTotalArtifacts = testruns.length;
179
+
180
+ debug('Found testruns:', testruns);
181
+
182
+ if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
183
+
184
+ if (!testruns.length) {
185
+ console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
186
+ if (numTotalArtifacts) {
187
+ console.log(APP_PREFIX, 'No new artifacts to upload');
188
+ console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
189
+ }
190
+ process.exit(0);
191
+ }
192
+
193
+ const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
194
+ if (!acc[rid]) {
195
+ acc[rid] = [];
196
+ }
197
+ if (!acc[rid].includes(file)) acc[rid].push(file);
198
+ return acc;
199
+ }, {});
200
+
201
+ await client.createRun();
202
+ client.uploader.checkEnabled();
203
+ client.uploader.disbleLogStorage();
204
+
205
+ for (const rid in testrunsByRid) {
206
+ const files = testrunsByRid[rid];
207
+ await client.addTestRun(undefined, { rid, files });
208
+ }
209
+
210
+ console.log(APP_PREFIX, client.uploader.totalUploaded, 'artifacts uploaded');
211
+ if (client.uploader.failedUpload) {
212
+ console.log(APP_PREFIX, client.uploader.failedUpload, 'artifacts failed to upload');
213
+ }
214
+ });
215
+
216
+ program.parse(process.argv);
217
+
218
+ if (!process.argv.slice(2).length) {
219
+ program.outputHelp();
220
+ }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const { spawn } = require('child_process');
2
+ const spawn = require('cross-spawn');
3
3
  const program = require('commander');
4
4
  const chalk = require('chalk');
5
5
  const TestomatClient = require('../client');
@@ -45,7 +45,7 @@ program
45
45
 
46
46
  const client = new TestomatClient({ apiKey });
47
47
 
48
- client.updateRunStatus(STATUS.FINISHED, true).then(() => {
48
+ client.updateRunStatus(STATUS.FINISHED).then(() => {
49
49
  console.log(chalk.yellow(`Run ${process.env.TESTOMATIO_RUN} was finished`));
50
50
  process.exit(0);
51
51
  });
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ const program = require('commander');
3
+ const chalk = require('chalk');
4
+ const debug = require('debug')('@testomatio/reporter:upload-cli');
5
+ const TestomatClient = require('../client');
6
+ const { APP_PREFIX } = require('../constants');
7
+ const { version } = require('../../package.json');
8
+ const config = require('../config');
9
+
10
+ console.log(chalk.cyan.bold(` 🀩 Testomat.io Reporter v${version}`));
11
+
12
+ program
13
+ .option('--env-file <envfile>', 'Load environment variables from env file')
14
+ .option('--force', 'Re-upload artifacts even if they were uploaded before')
15
+ .action(async opts => {
16
+
17
+ if (opts.envFile) {
18
+ require('dotenv').config(opts.envFile); // eslint-disable-line
19
+ } else {
20
+ // try to load from env file
21
+ require('dotenv').config(); // eslint-disable-line
22
+ }
23
+
24
+ const apiKey = config.TESTOMATIO;
25
+
26
+ process.env.TESTOMATIO_DISABLE_ARTIFACTS = '';
27
+ // process.env.TESTOMATIO_ARTIFACTS_SIZE = null;
28
+
29
+ const client = new TestomatClient({
30
+ apiKey,
31
+ isBatchEnabled: false,
32
+ });
33
+
34
+ if (!process.env.TESTOMATIO_RUN) {
35
+ console.log(APP_PREFIX, 'No run ID provided');
36
+ process.exit(1);
37
+ }
38
+
39
+ let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
40
+
41
+ const numTotalArtifacts = testruns.length;
42
+
43
+ debug('Found testruns:', testruns);
44
+
45
+ if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
46
+
47
+ if (!testruns.length) {
48
+ console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
49
+ if (numTotalArtifacts) {
50
+ console.log(APP_PREFIX, 'No new artifacts to upload');
51
+ console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
52
+ }
53
+ process.exit(0);
54
+ }
55
+
56
+ const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
57
+ if (!acc[rid]) {
58
+ acc[rid] = [];
59
+ }
60
+ if (!acc[rid].includes(file)) acc[rid].push(file);
61
+ return acc;
62
+ }, {});
63
+
64
+ let numArtifacts = 0;
65
+
66
+ // we need to obtain S3 credentials
67
+ await client.createRun();
68
+
69
+ client.uploader.checkEnabled();
70
+ client.uploader.disbleLogStorage();
71
+
72
+ for (const rid in testrunsByRid) {
73
+ const files = testrunsByRid[rid];
74
+ numArtifacts += files.length;
75
+ await client.addTestRun(undefined, {
76
+ rid,
77
+ files,
78
+ });
79
+ }
80
+
81
+ console.log(APP_PREFIX, client.uploader.totalUploaded, 'artifacts uploaded');
82
+ if (client.uploader.failedUpload) {
83
+ console.log(APP_PREFIX, client.uploader.failedUpload, 'artifacts failed to upload');
84
+ }
85
+ });
86
+
87
+ if (process.argv.length <= 1) {
88
+ program.outputHelp();
89
+ }
90
+
91
+ program.parse(process.argv);
package/lib/client.js CHANGED
@@ -5,7 +5,7 @@ const { minimatch } = require('minimatch');
5
5
  const fs = require('fs');
6
6
  const chalk = require('chalk');
7
7
  const { randomUUID } = require('crypto');
8
- const upload = require('./fileUploader');
8
+ const S3Uploader = require('./uploader');
9
9
  const { APP_PREFIX, STATUS } = require('./constants');
10
10
  const pipesFactory = require('./pipe');
11
11
  const { glob } = require('glob');
@@ -26,14 +26,14 @@ class Client {
26
26
  * @param {*} params
27
27
  */
28
28
  constructor(params = {}) {
29
- const store = {};
30
- this.uuid = randomUUID();
31
- this.pipes = pipesFactory(params, store);
29
+ this.pipeStore = {};
30
+ this.runId = randomUUID(); // will be replaced by real run id
31
+ this.pipes = pipesFactory(params, this.pipeStore);
32
32
  this.queue = Promise.resolve();
33
- this.totalUploaded = 0;
34
- this.failedToUpload = 0;
35
33
  this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
36
34
  this.executionList = Promise.resolve();
35
+ this.uploader = new S3Uploader();
36
+ this.uploader.checkEnabled();
37
37
 
38
38
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
39
39
  }
@@ -104,7 +104,13 @@ class Client {
104
104
  this.queue = this.queue
105
105
  .then(() => Promise.all(this.pipes.map(p => p.createRun())))
106
106
  .catch(err => console.log(APP_PREFIX, err))
107
+ .then(() => {
108
+ const runId = this.pipeStore?.runId;
109
+ if (runId) this.runId = runId;
110
+ this.uploader.checkEnabled();
111
+ })
107
112
  .then(() => undefined); // fixes return type
113
+
108
114
  // debug('Run', this.queue);
109
115
  return this.queue;
110
116
  }
@@ -169,24 +175,28 @@ class Client {
169
175
 
170
176
  const uploadedFiles = [];
171
177
 
172
- for (const f of files) {
173
- uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
178
+ for (let f of files) {
179
+ if (typeof f === 'object') {
180
+ if (!f.path) continue;
181
+
182
+ f = f.path;
183
+ }
184
+
185
+ uploadedFiles.push(this.uploader.uploadFileByPath(f, [
186
+ this.runId,
187
+ rid,
188
+ path.basename(f)
189
+ ]));
190
+
174
191
  }
175
192
 
176
193
  for (const [idx, buffer] of filesBuffers.entries()) {
177
194
  const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
178
- uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
195
+ uploadedFiles.push(this.uploader.uploadFileAsBuffer(buffer, [this.runId, rid, fileName]));
179
196
  }
180
197
 
181
198
  const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
182
199
 
183
- if (artifacts.length < uploadedFiles.length) {
184
- const failedUploading = uploadedFiles.length - artifacts.length;
185
- this.failedToUpload += failedUploading;
186
- }
187
-
188
- this.totalUploaded += artifacts.length;
189
-
190
200
  const data = {
191
201
  rid,
192
202
  files,
@@ -242,28 +252,30 @@ class Client {
242
252
  this.queue = this.queue
243
253
  .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
244
254
  .then(() => {
245
- debug('TOTAL artifacts', this.totalUploaded);
246
- if (this.totalUploaded && !upload.isArtifactsEnabled())
247
- debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
255
+ debug('TOTAL artifacts', this.uploader.totalUploaded);
256
+ debug(`${this.uploader.skippedUpload} artifacts skipped`);
248
257
 
249
- if (this.totalUploaded && upload.isArtifactsEnabled()) {
258
+ if (this.uploader.totalUploaded && this.uploader.isEnabled) {
250
259
  console.log(
251
260
  APP_PREFIX,
252
- `πŸ—„οΈ ${this.totalUploaded} artifacts ${
261
+ `πŸ—„οΈ ${this.uploader.totalUploaded} artifacts ${
253
262
  process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
254
263
  } uploaded to S3 bucket`,
255
- );
256
-
257
- if (this.failedToUpload > 0) {
258
- console.log(
259
- APP_PREFIX,
260
- chalk.yellow(
261
- `Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded.
262
- Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
263
- ),
264
- );
265
- }
264
+ );
265
+ }
266
+
267
+ if (this.uploader.failedUpload) {
268
+ console.log(APP_PREFIX, `${this.uploader.failedUpload} artifacts failed to upload`);
269
+ }
270
+
271
+ if (this.uploader.isEnabled && this.uploader.skippedUpload) {
272
+ console.log(APP_PREFIX, `${chalk.bold(this.uploader.skippedUpload)} artifacts skipped to upload`);
266
273
  }
274
+
275
+ if (this.uploader.skippedUpload || this.uploader.failedUpload) {
276
+ console.log(APP_PREFIX, `Run "${chalk.magenta(`TESTOMATIO_RUN=${this.runId} npx upload-artifacts`)}" with valid S3 credentials to upload skipped & failed artifacts`);
277
+ }
278
+
267
279
  })
268
280
  .catch(err => console.log(APP_PREFIX, err));
269
281
 
@@ -279,7 +291,10 @@ class Client {
279
291
  logs = logs?.trim();
280
292
 
281
293
  if (Array.isArray(steps)) {
282
- steps = steps.map(step => formatStep(step)).flat().join('\n');
294
+ steps = steps
295
+ .map(step => formatStep(step))
296
+ .flat()
297
+ .join('\n');
283
298
  }
284
299
 
285
300
  let testLogs = '';
@@ -358,8 +373,8 @@ function formatStep(step, shift = 0) {
358
373
 
359
374
  for (const child of step.steps || []) {
360
375
  lines.push(...formatStep(child, shift + 2));
361
- }
362
-
376
+ }
377
+
363
378
  return lines;
364
379
  }
365
380