@testomatio/reporter 2.0.0-beta-esm → 2.0.1-beta-esm
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/codecept.d.ts +2 -0
- package/lib/adapter/codecept.js +31 -24
- package/lib/adapter/cucumber/current.d.ts +14 -0
- package/lib/adapter/cucumber/legacy.d.ts +0 -0
- package/lib/adapter/cucumber.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.js +11 -9
- package/lib/adapter/jasmine.d.ts +11 -0
- package/lib/adapter/jest.d.ts +13 -0
- package/lib/adapter/mocha.d.ts +2 -0
- package/lib/adapter/mocha.js +4 -3
- package/lib/adapter/playwright.d.ts +14 -0
- package/lib/adapter/playwright.js +58 -33
- package/lib/adapter/vitest.d.ts +35 -0
- package/lib/adapter/vitest.js +6 -6
- package/lib/adapter/webdriver.d.ts +24 -0
- package/lib/adapter/webdriver.js +34 -6
- package/lib/bin/cli.d.ts +2 -0
- package/lib/bin/cli.js +228 -0
- package/lib/bin/reportXml.d.ts +2 -0
- package/lib/bin/reportXml.js +11 -9
- package/lib/bin/startTest.d.ts +2 -0
- package/lib/bin/startTest.js +9 -5
- package/lib/bin/uploadArtifacts.d.ts +2 -0
- package/lib/bin/uploadArtifacts.js +81 -0
- package/lib/client.d.ts +76 -0
- package/lib/client.js +111 -45
- package/lib/config.d.ts +1 -0
- package/lib/constants.d.ts +25 -0
- package/lib/constants.js +5 -1
- package/lib/data-storage.d.ts +34 -0
- package/lib/data-storage.js +2 -2
- package/lib/junit-adapter/adapter.d.ts +9 -0
- package/lib/junit-adapter/csharp.d.ts +4 -0
- package/lib/junit-adapter/index.d.ts +3 -0
- package/lib/junit-adapter/java.d.ts +5 -0
- package/lib/junit-adapter/javascript.d.ts +4 -0
- package/lib/junit-adapter/python.d.ts +5 -0
- package/lib/junit-adapter/ruby.d.ts +4 -0
- package/lib/output.d.ts +11 -0
- package/lib/package.json +3 -1
- package/lib/pipe/bitbucket.d.ts +23 -0
- package/lib/pipe/bitbucket.js +2 -2
- package/lib/pipe/csv.d.ts +47 -0
- package/lib/pipe/csv.js +2 -2
- package/lib/pipe/debug.d.ts +29 -0
- package/lib/pipe/debug.js +108 -0
- package/lib/pipe/github.d.ts +30 -0
- package/lib/pipe/github.js +2 -2
- package/lib/pipe/gitlab.d.ts +23 -0
- package/lib/pipe/gitlab.js +2 -2
- package/lib/pipe/html.d.ts +34 -0
- package/lib/pipe/html.js +8 -1
- package/lib/pipe/index.d.ts +1 -0
- package/lib/pipe/index.js +3 -3
- package/lib/pipe/testomatio.d.ts +70 -0
- package/lib/pipe/testomatio.js +50 -30
- package/lib/reporter-functions.d.ts +34 -0
- package/lib/reporter-functions.js +17 -7
- package/lib/reporter.d.ts +232 -0
- package/lib/reporter.js +19 -33
- package/lib/services/artifacts.d.ts +33 -0
- package/lib/services/index.d.ts +9 -0
- package/lib/services/key-values.d.ts +27 -0
- package/lib/services/key-values.js +1 -1
- package/lib/services/logger.d.ts +64 -0
- package/lib/template/testomatio.hbs +651 -1366
- package/lib/uploader.d.ts +60 -0
- package/lib/uploader.js +312 -0
- package/lib/utils/pipe_utils.d.ts +41 -0
- package/lib/utils/pipe_utils.js +3 -5
- package/lib/utils/utils.d.ts +45 -0
- package/lib/utils/utils.js +69 -2
- package/lib/xmlReader.d.ts +92 -0
- package/lib/xmlReader.js +22 -12
- package/package.json +15 -9
- package/src/adapter/codecept.js +30 -24
- package/src/adapter/cypress-plugin/index.js +5 -3
- package/src/adapter/mocha.cjs +1 -1
- package/src/adapter/mocha.js +4 -3
- package/src/adapter/playwright.js +59 -31
- package/src/adapter/vitest.js +6 -6
- package/src/adapter/webdriver.js +41 -10
- package/src/bin/cli.js +280 -0
- package/src/bin/reportXml.js +15 -8
- package/src/bin/startTest.js +7 -3
- package/src/bin/uploadArtifacts.js +90 -0
- package/src/client.js +137 -56
- package/src/constants.js +5 -1
- package/src/data-storage.js +2 -2
- package/src/pipe/bitbucket.js +2 -2
- package/src/pipe/csv.js +3 -3
- package/src/pipe/debug.js +104 -0
- package/src/pipe/github.js +2 -3
- package/src/pipe/gitlab.js +6 -6
- package/src/pipe/html.js +11 -3
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +72 -67
- package/src/reporter-functions.js +18 -7
- package/src/reporter.cjs_decprecated +21 -0
- package/src/reporter.js +20 -11
- package/src/services/key-values.js +1 -1
- package/src/services/logger.js +4 -2
- package/src/template/testomatio.hbs +651 -1366
- package/src/uploader.js +371 -0
- package/src/utils/pipe_utils.js +4 -12
- package/src/utils/utils.js +48 -6
- package/src/xmlReader.js +26 -15
- package/lib/adapter/jasmine/jasmine.js +0 -63
- package/lib/adapter/mocha/mocha.js +0 -125
- package/lib/fileUploader.js +0 -245
- package/lib/utils/chalk.js +0 -10
- package/src/fileUploader.js +0 -307
- package/src/reporter.cjs +0 -22
- package/src/utils/chalk.js +0 -13
package/src/client.js
CHANGED
|
@@ -4,24 +4,26 @@ import { minimatch } from 'minimatch';
|
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { randomUUID } from 'crypto';
|
|
7
|
-
import {upload} from './fileUploader.js';
|
|
8
7
|
import { APP_PREFIX, STATUS } from './constants.js';
|
|
9
8
|
import { pipesFactory } from './pipe/index.js';
|
|
10
9
|
import { glob } from 'glob';
|
|
11
|
-
import path, { sep} from 'path';
|
|
10
|
+
import path, { sep } from 'path';
|
|
12
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { S3Uploader } from './uploader.js';
|
|
13
|
+
import { formatStep, storeRunId } from './utils/utils.js';
|
|
14
|
+
import { filesize as prettyBytes } from 'filesize';
|
|
13
15
|
|
|
14
16
|
const debug = createDebugMessages('@testomatio/reporter:client');
|
|
15
17
|
|
|
16
18
|
// removed __dirname usage, because:
|
|
17
19
|
// 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
|
|
18
|
-
// 2. got error "__dirname already defined" in compiles js code (cjs dir)
|
|
20
|
+
// 2. got error "__dirname already defined" in compiles js code (cjs dir)
|
|
19
21
|
|
|
20
22
|
let listOfTestFilesToExcludeFromReport = null;
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
|
-
* @typedef {import('../types').TestData} TestData
|
|
24
|
-
* @typedef {import('../types').PipeResult} PipeResult
|
|
25
|
+
* @typedef {import('../types/types.js').TestData} TestData
|
|
26
|
+
* @typedef {import('../types/types.js').PipeResult} PipeResult
|
|
25
27
|
*/
|
|
26
28
|
|
|
27
29
|
class Client {
|
|
@@ -29,12 +31,12 @@ class Client {
|
|
|
29
31
|
* Create a Testomat client instance
|
|
30
32
|
* @returns
|
|
31
33
|
*/
|
|
32
|
-
// eslint-disable-next-line
|
|
34
|
+
// eslint-disable-next-line
|
|
33
35
|
constructor(params = {}) {
|
|
34
|
-
this.
|
|
36
|
+
this.paramsForPipesFactory = params;
|
|
37
|
+
this.pipeStore = {};
|
|
38
|
+
this.runId = randomUUID(); // will be replaced by real run id
|
|
35
39
|
this.queue = Promise.resolve();
|
|
36
|
-
this.totalUploaded = 0;
|
|
37
|
-
this.failedToUpload = 0;
|
|
38
40
|
|
|
39
41
|
// @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
|
|
40
42
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -47,6 +49,7 @@ class Client {
|
|
|
47
49
|
}
|
|
48
50
|
this.executionList = Promise.resolve();
|
|
49
51
|
|
|
52
|
+
this.uploader = new S3Uploader();
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
/**
|
|
@@ -66,8 +69,7 @@ class Client {
|
|
|
66
69
|
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
67
70
|
*/
|
|
68
71
|
async prepareRun(params) {
|
|
69
|
-
|
|
70
|
-
this.pipes = await pipesFactory(params, store);
|
|
72
|
+
this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
|
|
71
73
|
const { pipe, pipeOptions } = params;
|
|
72
74
|
// all pipes disabled, skipping
|
|
73
75
|
if (!this.pipes.some(p => p.isEnabled)) {
|
|
@@ -110,7 +112,8 @@ class Client {
|
|
|
110
112
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
111
113
|
*/
|
|
112
114
|
async createRun(params) {
|
|
113
|
-
if (!this.pipes || !this.pipes.length)
|
|
115
|
+
if (!this.pipes || !this.pipes.length)
|
|
116
|
+
this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
|
|
114
117
|
debug('Creating run...');
|
|
115
118
|
// all pipes disabled, skipping
|
|
116
119
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
@@ -118,6 +121,12 @@ class Client {
|
|
|
118
121
|
this.queue = this.queue
|
|
119
122
|
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
120
123
|
.catch(err => console.log(APP_PREFIX, err))
|
|
124
|
+
.then(() => {
|
|
125
|
+
const runId = this.pipeStore?.runId;
|
|
126
|
+
if (runId) this.runId = runId;
|
|
127
|
+
storeRunId(this.runId);
|
|
128
|
+
})
|
|
129
|
+
.then(() => this.uploader.checkEnabled())
|
|
121
130
|
.then(() => undefined); // fixes return type
|
|
122
131
|
// debug('Run', this.queue);
|
|
123
132
|
return this.queue;
|
|
@@ -165,9 +174,45 @@ class Client {
|
|
|
165
174
|
suite_id,
|
|
166
175
|
test_id,
|
|
167
176
|
manuallyAttachedArtifacts,
|
|
168
|
-
meta,
|
|
169
177
|
} = testData;
|
|
170
|
-
let { message = '' } = testData;
|
|
178
|
+
let { message = '', meta = {} } = testData;
|
|
179
|
+
|
|
180
|
+
// stringify meta values and limit keys and values length to 255
|
|
181
|
+
meta = Object.entries(meta)
|
|
182
|
+
.filter(([, value]) => value !== null && value !== undefined)
|
|
183
|
+
.map(([key, value]) => {
|
|
184
|
+
try {
|
|
185
|
+
if (typeof value === 'object') {
|
|
186
|
+
value = JSON.stringify(value);
|
|
187
|
+
} else if (typeof value !== 'string') {
|
|
188
|
+
try {
|
|
189
|
+
value = value.toString();
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.warn(APP_PREFIX, `Can't convert meta value to string`, err);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (value?.length > 255) {
|
|
196
|
+
value = value.substring(0, 255);
|
|
197
|
+
debug(APP_PREFIX, `Meta info value "${value}" is too long, trimmed to 255 characters`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (key?.length > 255) {
|
|
201
|
+
const newKey = key.substring(0, 255);
|
|
202
|
+
debug(APP_PREFIX, `Meta info key "${key}" is too long, trimmed to 255 characters`);
|
|
203
|
+
return [newKey, value];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return [key, value];
|
|
207
|
+
} catch (err) {
|
|
208
|
+
debug(APP_PREFIX, `Error while processing meta info key ${key}`, err);
|
|
209
|
+
return [null, null];
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
.reduce((acc, [key, value]) => {
|
|
213
|
+
if (key) acc[key] = value;
|
|
214
|
+
return acc;
|
|
215
|
+
}, {});
|
|
171
216
|
|
|
172
217
|
let errorFormatted = '';
|
|
173
218
|
if (error) {
|
|
@@ -183,24 +228,24 @@ class Client {
|
|
|
183
228
|
|
|
184
229
|
const uploadedFiles = [];
|
|
185
230
|
|
|
186
|
-
for (
|
|
187
|
-
|
|
231
|
+
for (let f of files) {
|
|
232
|
+
if (!f) continue; // f === null
|
|
233
|
+
if (typeof f === 'object') {
|
|
234
|
+
if (!f.path) continue;
|
|
235
|
+
|
|
236
|
+
f = f.path;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
uploadedFiles.push(this.uploader.uploadFileByPath(f, [this.runId, rid, path.basename(f)]));
|
|
188
240
|
}
|
|
189
241
|
|
|
190
242
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
191
243
|
const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
|
|
192
|
-
uploadedFiles.push(
|
|
244
|
+
uploadedFiles.push(this.uploader.uploadFileAsBuffer(buffer, [this.runId, rid, fileName]));
|
|
193
245
|
}
|
|
194
246
|
|
|
195
247
|
const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
|
|
196
248
|
|
|
197
|
-
if (artifacts.length < uploadedFiles.length) {
|
|
198
|
-
const failedUploading = uploadedFiles.length - artifacts.length;
|
|
199
|
-
this.failedToUpload += failedUploading;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
this.totalUploaded += artifacts.length;
|
|
203
|
-
|
|
204
249
|
const data = {
|
|
205
250
|
rid,
|
|
206
251
|
files,
|
|
@@ -258,27 +303,81 @@ class Client {
|
|
|
258
303
|
this.queue = this.queue
|
|
259
304
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
260
305
|
.then(() => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
306
|
+
if (!this.uploader.isEnabled) return;
|
|
307
|
+
|
|
308
|
+
const filesizeStrMaxLength = 7;
|
|
309
|
+
|
|
310
|
+
if (this.uploader.successfulUploads.length) {
|
|
311
|
+
debug('\n', APP_PREFIX, `🗄️ ${this.uploader.successfulUploads.length} artifacts uploaded to S3 bucket`);
|
|
312
|
+
const uploadedArtifacts = this.uploader.successfulUploads.map(file => ({
|
|
313
|
+
relativePath: file.path.replace(process.cwd(), ''),
|
|
314
|
+
link: file.link,
|
|
315
|
+
sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
|
|
316
|
+
}));
|
|
317
|
+
|
|
318
|
+
uploadedArtifacts.forEach(upload => {
|
|
319
|
+
debug(
|
|
320
|
+
`🟢Uploaded artifact`,
|
|
321
|
+
`${upload.relativePath},`,
|
|
322
|
+
'size:',
|
|
323
|
+
`${upload.sizePretty},`,
|
|
324
|
+
'link:',
|
|
325
|
+
`${upload.link}`,
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
264
329
|
|
|
265
|
-
if (this.
|
|
330
|
+
if (this.uploader.failedUploads.length) {
|
|
266
331
|
console.log(
|
|
267
332
|
APP_PREFIX,
|
|
268
|
-
`🗄️ ${this.
|
|
269
|
-
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : pc.bold('publicly')
|
|
270
|
-
} uploaded to S3 bucket`,
|
|
333
|
+
`🗄️ ${this.uploader.failedUploads.length} artifacts 🔴${pc.bold('failed')} to upload`,
|
|
271
334
|
);
|
|
335
|
+
const failedUploads = this.uploader.failedUploads.map(file => ({
|
|
336
|
+
relativePath: file.path.replace(process.cwd(), ''),
|
|
337
|
+
sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
|
|
338
|
+
}));
|
|
272
339
|
|
|
273
|
-
|
|
340
|
+
const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
|
|
341
|
+
|
|
342
|
+
failedUploads.forEach(upload => {
|
|
274
343
|
console.log(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
|
|
279
|
-
),
|
|
344
|
+
` ${pc.gray('|')} 🔴 ${upload.relativePath.padEnd(pathPadding)} ${pc.gray(
|
|
345
|
+
`| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
|
|
346
|
+
)}`,
|
|
280
347
|
);
|
|
281
|
-
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (this.uploader.skippedUploads.length) {
|
|
352
|
+
console.log(
|
|
353
|
+
'\n',
|
|
354
|
+
APP_PREFIX,
|
|
355
|
+
`🗄️ ${pc.bold(this.uploader.skippedUploads.length)} artifacts uploading 🟡${pc.bold('skipped')}`,
|
|
356
|
+
);
|
|
357
|
+
const skippedUploads = this.uploader.skippedUploads.map(file => ({
|
|
358
|
+
relativePath: file.path.replace(process.cwd(), ''),
|
|
359
|
+
sizePretty: file.size === null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
|
|
360
|
+
}));
|
|
361
|
+
const pathPadding = Math.max(...skippedUploads.map(upload => upload.relativePath.length)) + 1;
|
|
362
|
+
skippedUploads.forEach(upload => {
|
|
363
|
+
console.log(
|
|
364
|
+
` ${pc.gray('|')} 🟡 ${upload.relativePath.padEnd(pathPadding)} ${pc.gray(
|
|
365
|
+
`| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
|
|
366
|
+
)}`,
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (this.uploader.skippedUploads.length || this.uploader.failedUploads.length) {
|
|
372
|
+
const command = `TESTOMATIO=<your_api_key> TESTOMATIO_RUN=${
|
|
373
|
+
this.runId
|
|
374
|
+
} npx @testomatio/reporter upload-artifacts`;
|
|
375
|
+
const numberOfNotUploadedArtifacts = this.uploader.skippedUploads.length + this.uploader.failedUploads.length;
|
|
376
|
+
console.log(
|
|
377
|
+
APP_PREFIX,
|
|
378
|
+
`${numberOfNotUploadedArtifacts} artifacts were not uploaded.
|
|
379
|
+
Run "${pc.magenta(command)}" with valid S3 credentials to upload skipped & failed artifacts`,
|
|
380
|
+
);
|
|
282
381
|
}
|
|
283
382
|
})
|
|
284
383
|
.catch(err => console.log(APP_PREFIX, err));
|
|
@@ -365,24 +464,6 @@ function isNotInternalFrame(frame) {
|
|
|
365
464
|
);
|
|
366
465
|
}
|
|
367
466
|
|
|
368
|
-
function formatStep(step, shift = 0) {
|
|
369
|
-
const prefix = ' '.repeat(shift);
|
|
370
|
-
|
|
371
|
-
const lines = [];
|
|
372
|
-
|
|
373
|
-
if (step.error) {
|
|
374
|
-
lines.push(`${prefix}${pc.red(step.title)} ${pc.gray(`${step.duration}ms`)}`);
|
|
375
|
-
} else {
|
|
376
|
-
lines.push(`${prefix}${step.title} ${pc.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
467
|
/**
|
|
387
468
|
*
|
|
388
469
|
* @param {TestData} testData
|
package/src/constants.js
CHANGED
|
@@ -3,7 +3,11 @@ import os from 'os';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
5
5
|
const APP_PREFIX = pc.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
|
|
package/src/data-storage.js
CHANGED
|
@@ -52,7 +52,7 @@ class DataStorage {
|
|
|
52
52
|
|
|
53
53
|
context = context || this.context || testRunnerHelper.getNameOfCurrentlyRunningTest();
|
|
54
54
|
if (!context) {
|
|
55
|
-
debug(`No context provided for "${dataType}" data:`, data);
|
|
55
|
+
// debug(`No context provided for "${dataType}" data:`, data);
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
58
|
const contextHash = stringToMD5Hash(context);
|
|
@@ -101,7 +101,7 @@ class DataStorage {
|
|
|
101
101
|
if (testDataFromFile.length) {
|
|
102
102
|
return testDataFromFile;
|
|
103
103
|
}
|
|
104
|
-
debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
|
|
104
|
+
// debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
|
|
105
105
|
|
|
106
106
|
// in case no data found for context
|
|
107
107
|
return null;
|
package/src/pipe/bitbucket.js
CHANGED
|
@@ -15,8 +15,8 @@ const debug = createDebugMessages('@testomatio/reporter:pipe:bitbucket');
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @class BitbucketPipe
|
|
18
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
19
|
-
* @typedef {import('../../types').TestData} TestData
|
|
18
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
19
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
20
20
|
*/
|
|
21
21
|
export class BitbucketPipe {
|
|
22
22
|
constructor(params, store = {}) {
|
package/src/pipe/csv.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import createDebugMessages from 'debug';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
-
import {createObjectCsvWriter} from 'csv-writer';
|
|
4
|
+
import { createObjectCsvWriter } from 'csv-writer';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import merge from 'lodash.merge';
|
|
7
7
|
import { isSameTest, getCurrentDateTime, ansiRegExp } from '../utils/utils.js';
|
|
@@ -9,8 +9,8 @@ import { CSV_HEADERS } from '../constants.js';
|
|
|
9
9
|
|
|
10
10
|
const debug = createDebugMessages('@testomatio/reporter:pipe:csv');
|
|
11
11
|
/**
|
|
12
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
13
|
-
* @typedef {import('../../types').TestData} TestData
|
|
12
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
13
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
14
14
|
* @class CsvPipe
|
|
15
15
|
* @implements {Pipe}
|
|
16
16
|
*/
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import createDebugMessages from 'debug';
|
|
5
|
+
import { APP_PREFIX } from '../constants.js';
|
|
6
|
+
import prettyMs from 'pretty-ms';
|
|
7
|
+
|
|
8
|
+
const debug = createDebugMessages('@testomatio/reporter:pipe:debug');
|
|
9
|
+
|
|
10
|
+
export class DebugPipe {
|
|
11
|
+
constructor(params, store) {
|
|
12
|
+
this.params = params || {};
|
|
13
|
+
this.store = store || {};
|
|
14
|
+
|
|
15
|
+
this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
|
|
16
|
+
if (this.isEnabled) {
|
|
17
|
+
this.batch = {
|
|
18
|
+
isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
19
|
+
intervalFunction: null,
|
|
20
|
+
intervalTime: 5000,
|
|
21
|
+
tests: [],
|
|
22
|
+
batchIndex: 0,
|
|
23
|
+
};
|
|
24
|
+
this.logFilePath = path.join(os.tmpdir(), `testomatio.debug.${Date.now()}.json`);
|
|
25
|
+
|
|
26
|
+
debug('Creating debug file:', this.logFilePath);
|
|
27
|
+
fs.writeFileSync(this.logFilePath, '');
|
|
28
|
+
console.log(APP_PREFIX, '🪲. Debug created:');
|
|
29
|
+
this.testomatioEnvVars = Object.keys(process.env)
|
|
30
|
+
.filter(key => key.startsWith('TESTOMATIO_'))
|
|
31
|
+
.reduce((acc, key) => {
|
|
32
|
+
acc[key] = process.env[key];
|
|
33
|
+
return acc;
|
|
34
|
+
}, {});
|
|
35
|
+
this.logToFile({ datetime: new Date().toISOString(), timestamp: Date.now() });
|
|
36
|
+
this.logToFile({ data: 'variables', testomatioEnvVars: this.testomatioEnvVars });
|
|
37
|
+
this.logToFile({ data: 'store', store: this.store || {} });
|
|
38
|
+
// Bind batchUpload to the instance
|
|
39
|
+
this.batchUpload = this.batchUpload.bind(this);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Logs data to a file if logging is enabled.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} logData - The data to be logged.
|
|
47
|
+
* @returns {Promise<void>} A promise that resolves when the log data has been appended to the file.
|
|
48
|
+
*/
|
|
49
|
+
logToFile(logData) {
|
|
50
|
+
if (!this.isEnabled) return;
|
|
51
|
+
const timePassedFromLastAction = Date.now() - (this.lastActionTimestamp || Date.now());
|
|
52
|
+
this.lastActionTimestamp = Date.now();
|
|
53
|
+
|
|
54
|
+
const logLine = JSON.stringify({ t: `+${prettyMs(timePassedFromLastAction)}`, ...logData });
|
|
55
|
+
fs.appendFileSync(this.logFilePath, `${logLine}\n`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async prepareRun(opts) {
|
|
59
|
+
if (!this.isEnabled) return [];
|
|
60
|
+
|
|
61
|
+
this.logToFile({ action: 'prepareRun', data: opts });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async createRun(params = {}) {
|
|
65
|
+
if (!this.isEnabled) return;
|
|
66
|
+
if (params.isBatchEnabled === true || params.isBatchEnabled === false) this.batch.isEnabled = params.isBatchEnabled;
|
|
67
|
+
|
|
68
|
+
if (!this.isEnabled) return {};
|
|
69
|
+
if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.batchUpload, this.batch.intervalTime);
|
|
70
|
+
|
|
71
|
+
this.logToFile({ action: 'createRun', params });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async addTest(data) {
|
|
75
|
+
if (!this.isEnabled) return;
|
|
76
|
+
|
|
77
|
+
if (!this.batch.isEnabled) this.logToFile({ action: 'addTest', testId: data });
|
|
78
|
+
else this.batch.tests.push(data);
|
|
79
|
+
|
|
80
|
+
if (!this.batch.intervalFunction) await this.batchUpload();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async batchUpload() {
|
|
84
|
+
this.batch.batchIndex++;
|
|
85
|
+
if (!this.batch.isEnabled) return;
|
|
86
|
+
if (!this.batch.tests.length) return;
|
|
87
|
+
|
|
88
|
+
const testsToSend = this.batch.tests.splice(0);
|
|
89
|
+
|
|
90
|
+
this.logToFile({ action: 'addTestsBatch', tests: testsToSend });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async finishRun(params) {
|
|
94
|
+
if (!this.isEnabled) return;
|
|
95
|
+
this.logToFile({ actions: 'finishRun', params });
|
|
96
|
+
await this.batchUpload();
|
|
97
|
+
if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
|
|
98
|
+
console.log(APP_PREFIX, '🪲. Debug Saved to', this.logFilePath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
toString() {
|
|
102
|
+
return 'Debug Reporter';
|
|
103
|
+
}
|
|
104
|
+
}
|
package/src/pipe/github.js
CHANGED
|
@@ -10,10 +10,9 @@ import { statusEmoji, fullName } from '../utils/pipe_utils.js';
|
|
|
10
10
|
|
|
11
11
|
const debug = createDebugMessages('@testomatio/reporter:pipe:github');
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
/**
|
|
15
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
16
|
-
* @typedef {import('../../types').TestData} TestData
|
|
14
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
15
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
17
16
|
* @class GitHubPipe
|
|
18
17
|
* @implements {Pipe}
|
|
19
18
|
*/
|
package/src/pipe/gitlab.js
CHANGED
|
@@ -15,8 +15,8 @@ const debug = createDebugMessages('@testomatio/reporter:pipe:gitlab');
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @class GitLabPipe
|
|
18
|
-
* @typedef {import('../../types').Pipe} Pipe
|
|
19
|
-
* @typedef {import('../../types').TestData} TestData
|
|
18
|
+
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
19
|
+
* @typedef {import('../../types/types.js').TestData} TestData
|
|
20
20
|
*/
|
|
21
21
|
class GitLabPipe {
|
|
22
22
|
constructor(params, store = {}) {
|
|
@@ -81,13 +81,13 @@ class GitLabPipe {
|
|
|
81
81
|
let summary = `${this.hiddenCommentData}
|
|
82
82
|
|
|
83
83
|
| [](https://testomat.io) | ${statusEmoji(
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
runParams.status,
|
|
85
|
+
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
86
86
|
| --- | --- |
|
|
87
87
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
88
88
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
'passed',
|
|
90
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
91
91
|
| Duration | 🕐 **${humanizeDuration(
|
|
92
92
|
parseInt(
|
|
93
93
|
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
package/src/pipe/html.js
CHANGED
|
@@ -5,9 +5,9 @@ import path from 'path';
|
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import handlebars from 'handlebars';
|
|
7
7
|
import fileUrl from 'file-url';
|
|
8
|
-
import { fileSystem, isSameTest, ansiRegExp } from '../utils/utils.js';
|
|
8
|
+
import { fileSystem, isSameTest, ansiRegExp, formatStep } from '../utils/utils.js';
|
|
9
9
|
import { HTML_REPORT } from '../constants.js';
|
|
10
|
-
import { fileURLToPath } from
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
11
|
|
|
12
12
|
const debug = createDebugMessages('@testomatio/reporter:pipe:html');
|
|
13
13
|
|
|
@@ -72,7 +72,7 @@ class HtmlPipe {
|
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
74
|
* Add test data to the result array for saving. As a result of this function, we get a result object to save.
|
|
75
|
-
* @param {import('../../types').RunData} test - object which includes each test entry.
|
|
75
|
+
* @param {import('../../types/types.js').RunData} test - object which includes each test entry.
|
|
76
76
|
*/
|
|
77
77
|
addTest(test) {
|
|
78
78
|
if (!this.isEnabled) return;
|
|
@@ -128,6 +128,14 @@ class HtmlPipe {
|
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
tests.forEach(test => {
|
|
131
|
+
// steps could be an array or a string
|
|
132
|
+
test.steps = Array.isArray(test.steps)
|
|
133
|
+
? (test.steps = test.steps
|
|
134
|
+
.map(step => formatStep(step))
|
|
135
|
+
.flat()
|
|
136
|
+
.join('\n'))
|
|
137
|
+
: test.steps;
|
|
138
|
+
|
|
131
139
|
if (!test.message?.trim()) {
|
|
132
140
|
test.message = "This test has no 'message' code";
|
|
133
141
|
}
|
package/src/pipe/index.js
CHANGED
|
@@ -7,7 +7,8 @@ import GitHubPipe from './github.js';
|
|
|
7
7
|
import GitLabPipe from './gitlab.js';
|
|
8
8
|
import CsvPipe from './csv.js';
|
|
9
9
|
import HtmlPipe from './html.js';
|
|
10
|
-
import {BitbucketPipe} from './bitbucket.js';
|
|
10
|
+
import { BitbucketPipe } from './bitbucket.js';
|
|
11
|
+
import { DebugPipe } from './debug.js';
|
|
11
12
|
|
|
12
13
|
export async function pipesFactory(params, opts) {
|
|
13
14
|
const extraPipes = [];
|
|
@@ -47,6 +48,7 @@ export async function pipesFactory(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
|
|
|
@@ -55,13 +57,9 @@ export async function pipesFactory(params, opts) {
|
|
|
55
57
|
console.log(
|
|
56
58
|
APP_PREFIX,
|
|
57
59
|
pc.cyan('Pipes:'),
|
|
58
|
-
pc.cyan(
|
|
59
|
-
pipesEnabled
|
|
60
|
-
.map(p => p.toString())
|
|
61
|
-
.join(', ') || 'No pipes enabled',
|
|
62
|
-
),
|
|
60
|
+
pc.cyan(pipesEnabled.map(p => p.toString()).join(', ') || 'No pipes enabled'),
|
|
63
61
|
);
|
|
64
|
-
|
|
62
|
+
|
|
65
63
|
if (!pipesEnabled.length) {
|
|
66
64
|
console.log(
|
|
67
65
|
APP_PREFIX,
|