@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.
- package/lib/adapter/mocha.js +3 -3
- package/lib/adapter/playwright.js +3 -1
- package/lib/adapter/webdriver.js +2 -2
- package/lib/bin/cli.js +28 -5
- package/lib/bin/uploadArtifacts.js +4 -4
- package/lib/client.js +48 -32
- package/lib/constants.js +5 -1
- package/lib/pipe/debug.js +102 -0
- package/lib/pipe/html.js +9 -2
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.js +5 -3
- package/lib/reporter.js +1 -1
- package/lib/uploader.js +57 -24
- package/lib/utils/utils.js +19 -0
- package/package.json +3 -1
package/lib/adapter/mocha.js
CHANGED
|
@@ -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
|
|
package/lib/adapter/webdriver.js
CHANGED
|
@@ -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
|
-
|
|
73
|
+
manuallyAttachedArtifacts: test.artifacts,
|
|
74
74
|
error,
|
|
75
75
|
logs: test.logs,
|
|
76
|
-
|
|
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.
|
|
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.
|
|
222
|
-
|
|
223
|
-
|
|
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.
|
|
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.
|
|
78
|
-
if (client.uploader.
|
|
79
|
-
console.log(APP_PREFIX, client.uploader.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
263
|
+
const filesizeStrMaxLength = 7;
|
|
264
|
+
|
|
265
|
+
if (this.uploader.failedUploads.length) {
|
|
266
266
|
console.log(
|
|
267
|
+
'\n',
|
|
267
268
|
APP_PREFIX,
|
|
268
|
-
chalk.
|
|
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.
|
|
274
|
-
console.log(
|
|
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.
|
|
278
|
-
const command = `TESTOMATIO_RUN=${
|
|
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
|
|
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
|
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
82
|
+
disableLogStorage() {
|
|
79
83
|
this.storeEnabled = false;
|
|
80
84
|
}
|
|
81
85
|
|
|
82
|
-
|
|
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.
|
|
122
|
+
this.totalSuccessfulUploadsCount++;
|
|
112
123
|
this.succesfulUploads[Key] = link;
|
|
113
124
|
return link;
|
|
114
125
|
} catch (e) {
|
|
115
|
-
this.
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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.
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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:',
|
|
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) {
|
package/lib/utils/utils.js
CHANGED
|
@@ -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
|
|
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"
|