@testomatio/reporter 2.7.1 → 2.7.2-beta.1
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 +2 -1
- package/lib/adapter/codecept.js +81 -26
- package/lib/adapter/playwright.d.ts +1 -1
- package/lib/adapter/playwright.js +54 -34
- package/lib/adapter/utils/step-formatter.d.ts +134 -0
- package/lib/adapter/utils/step-formatter.js +237 -0
- package/lib/bin/cli.js +28 -31
- package/lib/bin/reportXml.js +5 -6
- package/lib/bin/uploadArtifacts.js +6 -6
- package/lib/client.d.ts +8 -0
- package/lib/client.js +71 -10
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +7 -1
- package/lib/pipe/bitbucket.js +2 -1
- package/lib/pipe/coverage.js +16 -15
- package/lib/pipe/debug.js +3 -3
- package/lib/pipe/github.js +3 -2
- package/lib/pipe/gitlab.js +2 -1
- package/lib/pipe/index.js +5 -5
- package/lib/pipe/testomatio.js +21 -24
- package/lib/uploader.js +3 -2
- package/lib/utils/log.d.ts +45 -0
- package/lib/utils/log.js +98 -0
- package/lib/utils/pipe_utils.js +5 -5
- package/lib/utils/utils.d.ts +10 -0
- package/lib/utils/utils.js +16 -1
- package/lib/xmlReader.js +5 -4
- package/package.json +1 -1
- package/src/adapter/codecept.js +99 -29
- package/src/adapter/playwright.js +64 -39
- package/src/adapter/utils/step-formatter.js +232 -0
- package/src/bin/cli.js +34 -31
- package/src/bin/reportXml.js +5 -6
- package/src/bin/uploadArtifacts.js +6 -6
- package/src/client.js +76 -26
- package/src/constants.js +4 -0
- package/src/pipe/bitbucket.js +2 -1
- package/src/pipe/coverage.js +16 -15
- package/src/pipe/debug.js +3 -3
- package/src/pipe/github.js +4 -3
- package/src/pipe/gitlab.js +2 -1
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +32 -25
- package/src/uploader.js +3 -2
- package/src/utils/log.js +87 -0
- package/src/utils/pipe_utils.js +5 -5
- package/src/utils/utils.js +14 -0
- package/src/xmlReader.js +5 -4
- package/types/types.d.ts +3 -0
package/src/client.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import createDebugMessages from 'debug';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
-
import { APP_PREFIX, STATUS } from './constants.js';
|
|
4
|
+
import { APP_PREFIX, STATUS, SCREENSHOTS_ON_STEPS } from './constants.js';
|
|
5
5
|
import { pipesFactory } from './pipe/index.js';
|
|
6
6
|
import { glob } from 'glob';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { S3Uploader } from './uploader.js';
|
|
10
|
-
import { readLatestRunId, storeRunId, validateSuiteId, transformEnvVarToBoolean } from './utils/utils.js';
|
|
10
|
+
import { readLatestRunId, storeRunId, validateSuiteId, transformEnvVarToBoolean, isHttpUrl } from './utils/utils.js';
|
|
11
|
+
import { generateShortFilename } from './adapter/utils/step-formatter.js';
|
|
11
12
|
import { filesize as prettyBytes } from 'filesize';
|
|
12
13
|
import { formatLogs, formatError, stripColors } from './utils/log-formatter.js';
|
|
14
|
+
import { log } from './utils/log.js';
|
|
13
15
|
|
|
14
16
|
const debug = createDebugMessages('@testomatio/reporter:client');
|
|
15
17
|
|
|
@@ -40,7 +42,7 @@ class Client {
|
|
|
40
42
|
const pathToPackageJSON = path.join(__dirname, '../package.json');
|
|
41
43
|
try {
|
|
42
44
|
this.version = JSON.parse(fs.readFileSync(pathToPackageJSON).toString()).version;
|
|
43
|
-
|
|
45
|
+
log.info(`Testomatio Reporter v${this.version}`);
|
|
44
46
|
} catch (e) {
|
|
45
47
|
// do nothing
|
|
46
48
|
}
|
|
@@ -71,7 +73,7 @@ class Client {
|
|
|
71
73
|
|
|
72
74
|
// ❗ Validation: pipe is required
|
|
73
75
|
if (!pipe || !pipeOptions) {
|
|
74
|
-
|
|
76
|
+
log.warn(`❗ No valid pipe found in filter cmd. Expected format: <pipe>:<options>
|
|
75
77
|
Examples:
|
|
76
78
|
--filter "testomatio:tag-name=frontend"
|
|
77
79
|
--filter "coverage:file=coverage.yml"
|
|
@@ -92,10 +94,7 @@ class Client {
|
|
|
92
94
|
// const p = this.pipes.find(p => p.id === `${pipe.toLowerCase()}`); TODO: as future updates
|
|
93
95
|
|
|
94
96
|
if (!p?.isEnabled) {
|
|
95
|
-
|
|
96
|
-
APP_PREFIX,
|
|
97
|
-
"🚫 No active pipes were found in the system. Execution aborted!"
|
|
98
|
-
);
|
|
97
|
+
log.warn('🚫 No active pipes were found in the system. Execution aborted!');
|
|
99
98
|
return;
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -107,7 +106,7 @@ class Client {
|
|
|
107
106
|
|
|
108
107
|
return result;
|
|
109
108
|
} catch (err) {
|
|
110
|
-
|
|
109
|
+
log.error(err);
|
|
111
110
|
}
|
|
112
111
|
}
|
|
113
112
|
|
|
@@ -125,7 +124,7 @@ class Client {
|
|
|
125
124
|
|
|
126
125
|
this.queue = this.queue
|
|
127
126
|
.then(() => Promise.all(this.pipes.map(p => p.createRun(params))))
|
|
128
|
-
.catch(err =>
|
|
127
|
+
.catch(err => log.info(err))
|
|
129
128
|
.then(() => {
|
|
130
129
|
const runId = this.pipeStore?.runId;
|
|
131
130
|
if (runId) this.runId = runId;
|
|
@@ -137,6 +136,60 @@ class Client {
|
|
|
137
136
|
return this.queue;
|
|
138
137
|
}
|
|
139
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Recursively uploads artifacts from steps
|
|
141
|
+
*
|
|
142
|
+
* @param {*} steps - Steps payload (validated inside function)
|
|
143
|
+
* @param {string} testRid - Test/result ID
|
|
144
|
+
* @returns {Promise<void>}
|
|
145
|
+
*/
|
|
146
|
+
async uploadStepArtifacts(steps, testRid) {
|
|
147
|
+
if (!steps || !Array.isArray(steps)) return;
|
|
148
|
+
if (!this.uploader.isEnabled || !SCREENSHOTS_ON_STEPS) return;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
for (const step of steps) {
|
|
152
|
+
if (!(step.artifacts && Array.isArray(step.artifacts))) {
|
|
153
|
+
if (step.steps) {
|
|
154
|
+
await this.uploadStepArtifacts(step.steps, testRid);
|
|
155
|
+
}
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const uploadedArtifacts = [];
|
|
160
|
+
for (const artifact of step.artifacts) {
|
|
161
|
+
if (typeof artifact === 'string' && !isHttpUrl(artifact)) {
|
|
162
|
+
const filename = generateShortFilename(artifact);
|
|
163
|
+
try {
|
|
164
|
+
const uploadResult = await this.uploader.uploadFileByPath(
|
|
165
|
+
artifact,
|
|
166
|
+
[this.runId, testRid, 'steps', filename]
|
|
167
|
+
);
|
|
168
|
+
if (uploadResult) {
|
|
169
|
+
uploadedArtifacts.push(uploadResult);
|
|
170
|
+
} else {
|
|
171
|
+
uploadedArtifacts.push(artifact);
|
|
172
|
+
}
|
|
173
|
+
} catch (uploadErr) {
|
|
174
|
+
uploadedArtifacts.push(artifact);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
uploadedArtifacts.push(artifact);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
step.artifacts = uploadedArtifacts;
|
|
181
|
+
|
|
182
|
+
if (step.steps) {
|
|
183
|
+
await this.uploadStepArtifacts(step.steps, testRid);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error(APP_PREFIX, 'Error in uploadStepArtifacts for testRid', testRid, ':', err);
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
140
193
|
/**
|
|
141
194
|
* Updates test status and its data
|
|
142
195
|
*
|
|
@@ -162,6 +215,13 @@ class Client {
|
|
|
162
215
|
const { rid, error = null, steps: originalSteps, title, suite_title } = testData;
|
|
163
216
|
let steps = originalSteps;
|
|
164
217
|
|
|
218
|
+
// Upload artifacts from steps
|
|
219
|
+
try {
|
|
220
|
+
await this.uploadStepArtifacts(steps, rid);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.log(APP_PREFIX, 'Failed to upload step artifacts:', err);
|
|
223
|
+
}
|
|
224
|
+
|
|
165
225
|
const uploadedFiles = [];
|
|
166
226
|
const stackArtifactsEnabled = transformEnvVarToBoolean(process.env.TESTOMATIO_STACK_ARTIFACTS);
|
|
167
227
|
|
|
@@ -286,7 +346,7 @@ class Client {
|
|
|
286
346
|
const result = await pipe.addTest(data);
|
|
287
347
|
return { pipe: pipe.toString(), result };
|
|
288
348
|
} catch (err) {
|
|
289
|
-
|
|
349
|
+
log.info(pipe.toString(), err);
|
|
290
350
|
}
|
|
291
351
|
}),
|
|
292
352
|
),
|
|
@@ -341,10 +401,7 @@ class Client {
|
|
|
341
401
|
}
|
|
342
402
|
|
|
343
403
|
if (this.uploader.failedUploads.length) {
|
|
344
|
-
|
|
345
|
-
APP_PREFIX,
|
|
346
|
-
`🗄️ ${this.uploader.failedUploads.length} artifacts 🔴${pc.bold('failed')} to upload`,
|
|
347
|
-
);
|
|
404
|
+
log.info(`🗄️ ${this.uploader.failedUploads.length} artifacts 🔴${pc.bold('failed')} to upload`);
|
|
348
405
|
const failedUploads = this.uploader.failedUploads.map(file => ({
|
|
349
406
|
relativePath: file.path.replace(process.cwd(), ''),
|
|
350
407
|
sizePretty: file.size == null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
|
|
@@ -362,11 +419,7 @@ class Client {
|
|
|
362
419
|
}
|
|
363
420
|
|
|
364
421
|
if (this.uploader.skippedUploads.length) {
|
|
365
|
-
|
|
366
|
-
'\n',
|
|
367
|
-
APP_PREFIX,
|
|
368
|
-
`🗄️ ${pc.bold(this.uploader.skippedUploads.length)} artifacts uploading 🟡${pc.bold('skipped')}`,
|
|
369
|
-
);
|
|
422
|
+
log.info(`🗄️ ${pc.bold(this.uploader.skippedUploads.length)} artifacts uploading 🟡${pc.bold('skipped')}`);
|
|
370
423
|
const skippedUploads = this.uploader.skippedUploads.map(file => ({
|
|
371
424
|
relativePath: file.path.replace(process.cwd(), ''),
|
|
372
425
|
sizePretty: file.size === null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
|
|
@@ -386,14 +439,11 @@ class Client {
|
|
|
386
439
|
this.runId
|
|
387
440
|
} npx @testomatio/reporter upload-artifacts`;
|
|
388
441
|
const numberOfNotUploadedArtifacts = this.uploader.skippedUploads.length + this.uploader.failedUploads.length;
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
`${numberOfNotUploadedArtifacts} artifacts were not uploaded.
|
|
392
|
-
Run "${pc.magenta(command)}" with valid S3 credentials to upload skipped & failed artifacts`,
|
|
393
|
-
);
|
|
442
|
+
log.info(`${numberOfNotUploadedArtifacts} artifacts were not uploaded.
|
|
443
|
+
Run "${pc.magenta(command)}" with valid S3 credentials to upload skipped & failed artifacts`);
|
|
394
444
|
}
|
|
395
445
|
})
|
|
396
|
-
.catch(err =>
|
|
446
|
+
.catch(err => log.info(err));
|
|
397
447
|
|
|
398
448
|
return this.queue;
|
|
399
449
|
}
|
package/src/constants.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import os from 'os';
|
|
3
3
|
import path from 'path';
|
|
4
|
+
import { transformEnvVarToBoolean } from './utils/utils.js';
|
|
4
5
|
|
|
5
6
|
const APP_PREFIX = pc.gray('[TESTOMATIO]');
|
|
6
7
|
const TESTOMATIO_REQUEST_TIMEOUT = parseInt(process.env.TESTOMATIO_REQUEST_TIMEOUT, 10);
|
|
@@ -8,6 +9,8 @@ if (TESTOMATIO_REQUEST_TIMEOUT) {
|
|
|
8
9
|
console.log(`${APP_PREFIX} Request timeout is set to ${TESTOMATIO_REQUEST_TIMEOUT / 1000}s`);
|
|
9
10
|
}
|
|
10
11
|
const AXIOS_TIMEOUT = TESTOMATIO_REQUEST_TIMEOUT || 20 * 1000;
|
|
12
|
+
const SCREENSHOTS_ON_STEPS = process.env.TESTOMATIO_SCREENSHOTS_ON_STEPS == null
|
|
13
|
+
|| transformEnvVarToBoolean(process.env.TESTOMATIO_SCREENSHOTS_ON_STEPS);
|
|
11
14
|
|
|
12
15
|
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
13
16
|
|
|
@@ -50,4 +53,5 @@ export {
|
|
|
50
53
|
AXIOS_TIMEOUT,
|
|
51
54
|
testomatLogoURL,
|
|
52
55
|
REPORTER_REQUEST_RETRIES,
|
|
56
|
+
SCREENSHOTS_ON_STEPS,
|
|
53
57
|
};
|
package/src/pipe/bitbucket.js
CHANGED
|
@@ -7,6 +7,7 @@ import humanizeDuration from 'humanize-duration';
|
|
|
7
7
|
import merge from 'lodash.merge';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import createDebugMessages from 'debug';
|
|
10
|
+
import { log } from '../utils/log.js';
|
|
10
11
|
|
|
11
12
|
const debug = createDebugMessages('@testomatio/reporter:pipe:bitbucket');
|
|
12
13
|
|
|
@@ -193,7 +194,7 @@ export class BitbucketPipe {
|
|
|
193
194
|
// eslint-disable-next-line max-len
|
|
194
195
|
const commentURL = `https://bitbucket.org/${this.ENV.BITBUCKET_WORKSPACE}/${this.ENV.BITBUCKET_REPO_SLUG}/pull-requests/${this.ENV.BITBUCKET_PR_ID}#comment-${commentID}`;
|
|
195
196
|
|
|
196
|
-
|
|
197
|
+
log.info(pc.yellow('Bitbucket'), `Report created: ${pc.magenta(commentURL)}`);
|
|
197
198
|
} catch (err) {
|
|
198
199
|
console.error(
|
|
199
200
|
APP_PREFIX,
|
package/src/pipe/coverage.js
CHANGED
|
@@ -9,6 +9,7 @@ import { generateFilterRequestParams } from '../utils/pipe_utils.js';
|
|
|
9
9
|
import { parsePipeOptions } from '../utils/pipe_utils.js';
|
|
10
10
|
import { config } from '../config.js';
|
|
11
11
|
import createDebugMessages from 'debug';
|
|
12
|
+
import { log } from '../utils/log.js';
|
|
12
13
|
|
|
13
14
|
const debug = createDebugMessages('@testomatio/reporter:pipe:csv');
|
|
14
15
|
|
|
@@ -136,11 +137,11 @@ class CoveragePipe { // or Changes for the future???
|
|
|
136
137
|
// Step 2: Extract all available tests and compare with coverage file
|
|
137
138
|
const lines = await this.extractRelevantTestsFromChanges();
|
|
138
139
|
if (this.store?.filterList && lines.size > 0) {
|
|
139
|
-
|
|
140
|
+
log.info( `Matched files: ${[...lines].join(', ')}`);
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
if (lines.size === 0) {
|
|
143
|
-
|
|
144
|
+
log.info( 'ℹ️ No matching entries in coverage file for provided Git changes.');
|
|
144
145
|
return [];
|
|
145
146
|
}
|
|
146
147
|
|
|
@@ -164,7 +165,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
if (this.tests.size === 0 && this.suiteIds.size === 0) {
|
|
167
|
-
|
|
168
|
+
log.info( 'ℹ️ No tests found for execution based on Git changes.');
|
|
168
169
|
return [];
|
|
169
170
|
}
|
|
170
171
|
|
|
@@ -235,7 +236,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
235
236
|
});
|
|
236
237
|
|
|
237
238
|
if (!Array.isArray(resp.data?.tests) && resp.data?.tests?.length === 0) {
|
|
238
|
-
|
|
239
|
+
log.info( `🔍 No test by ${type}=${id} were found on the Testomat.io server side!`);
|
|
239
240
|
|
|
240
241
|
return undefined;
|
|
241
242
|
}
|
|
@@ -275,7 +276,7 @@ class CoveragePipe { // or Changes for the future???
|
|
|
275
276
|
const errorMessage = err.message || '';
|
|
276
277
|
// Git edge: Not a git repository or other error
|
|
277
278
|
if (errorMessage.includes('Not a git repository')) {
|
|
278
|
-
|
|
279
|
+
log.error( '❌ Error: This folder is not a Git repository.');
|
|
279
280
|
}
|
|
280
281
|
else {
|
|
281
282
|
throw new Error(`❌ Git command failed ("${cmd}"):\n`, errorMessage);
|
|
@@ -319,11 +320,11 @@ class CoveragePipe { // or Changes for the future???
|
|
|
319
320
|
cmd = this.#buildGitCommand();
|
|
320
321
|
}
|
|
321
322
|
catch (err) {
|
|
322
|
-
|
|
323
|
+
log.error( err.message);
|
|
323
324
|
return undefined;
|
|
324
325
|
}
|
|
325
326
|
|
|
326
|
-
|
|
327
|
+
log.error( `ℹ️ We will use '${cmd}' Git command.`);
|
|
327
328
|
|
|
328
329
|
try {
|
|
329
330
|
// For clear unit testing process -> Like test_defaultGitChangedFile = todomvc-tests/edit-todos_test.js
|
|
@@ -344,12 +345,12 @@ class CoveragePipe { // or Changes for the future???
|
|
|
344
345
|
}
|
|
345
346
|
}
|
|
346
347
|
catch (err) {
|
|
347
|
-
|
|
348
|
-
|
|
348
|
+
log.error( err.message);
|
|
349
|
+
log.error( "🔍 Pls, check this Git command manually to understand the original problem.");
|
|
349
350
|
return undefined;
|
|
350
351
|
}
|
|
351
352
|
|
|
352
|
-
|
|
353
|
+
log.info( `📑 GIT changed files:\n - ${this.changedFiles.join('\n - ')}`);
|
|
353
354
|
return this;
|
|
354
355
|
}
|
|
355
356
|
|
|
@@ -369,20 +370,20 @@ class CoveragePipe { // or Changes for the future???
|
|
|
369
370
|
validateCoverageFile() {
|
|
370
371
|
// Validate the presence of the coverage filepath
|
|
371
372
|
if (!fs.existsSync(this.coverageFilePath)) {
|
|
372
|
-
|
|
373
|
+
log.info( '❌ Coverage file not found:', this.coverageFilePath);
|
|
373
374
|
return undefined;
|
|
374
375
|
}
|
|
375
376
|
|
|
376
377
|
// Ensure the given path is a file (not a directory or other type)
|
|
377
378
|
const stat = fs.statSync(this.coverageFilePath);
|
|
378
379
|
if (!stat.isFile()) {
|
|
379
|
-
|
|
380
|
+
log.info( '❌ Provided coverage path is not a file:', this.coverageFilePath);
|
|
380
381
|
return undefined;
|
|
381
382
|
}
|
|
382
383
|
|
|
383
384
|
// Validate the file extension to be ".yml" to ensure it's a YAML file
|
|
384
385
|
if (path.extname(this.coverageFilePath) !== ".yml") {
|
|
385
|
-
|
|
386
|
+
log.info( '❌ Coverage file must have a .yml extension:', this.coverageFilePath);
|
|
386
387
|
return undefined;
|
|
387
388
|
}
|
|
388
389
|
|
|
@@ -408,12 +409,12 @@ class CoveragePipe { // or Changes for the future???
|
|
|
408
409
|
this.parsedCoverage = yaml.load(rawYml) || {};
|
|
409
410
|
|
|
410
411
|
debug(`Coverage filepath = ${this.coverageFilePath})`);
|
|
411
|
-
|
|
412
|
+
log.info( `✅ Coverage file parsed successfully: ${this.coverageFilePath}`);
|
|
412
413
|
|
|
413
414
|
return this;
|
|
414
415
|
}
|
|
415
416
|
catch (err) {
|
|
416
|
-
|
|
417
|
+
log.error( '❌ Failed to parse YAML:', err.message);
|
|
417
418
|
return undefined;
|
|
418
419
|
}
|
|
419
420
|
}
|
package/src/pipe/debug.js
CHANGED
|
@@ -2,8 +2,8 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import createDebugMessages from 'debug';
|
|
5
|
-
import { APP_PREFIX } from '../constants.js';
|
|
6
5
|
import prettyMs from 'pretty-ms';
|
|
6
|
+
import { log } from '../utils/log.js';
|
|
7
7
|
|
|
8
8
|
const debug = createDebugMessages('@testomatio/reporter:pipe:debug');
|
|
9
9
|
|
|
@@ -40,7 +40,7 @@ export class DebugPipe {
|
|
|
40
40
|
debug('Failed to create symlink:', err.message);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
log.info('🪲 Debug file created');
|
|
44
44
|
this.testomatioEnvVars = Object.keys(process.env)
|
|
45
45
|
.filter(key => key.startsWith('TESTOMATIO_'))
|
|
46
46
|
.reduce((acc, key) => {
|
|
@@ -115,7 +115,7 @@ export class DebugPipe {
|
|
|
115
115
|
await this.sync();
|
|
116
116
|
if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
|
|
117
117
|
this.logToFile({ action: 'finishRun', params });
|
|
118
|
-
|
|
118
|
+
log.info('🪲 Debug Saved to', this.logFilePath);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
async sync() {
|
package/src/pipe/github.js
CHANGED
|
@@ -3,9 +3,10 @@ import path from 'path';
|
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import humanizeDuration from 'humanize-duration';
|
|
5
5
|
import merge from 'lodash.merge';
|
|
6
|
-
import {
|
|
6
|
+
import { testomatLogoURL } from '../constants.js';
|
|
7
7
|
import { ansiRegExp, isSameTest } from '../utils/utils.js';
|
|
8
8
|
import { statusEmoji, fullName } from '../utils/pipe_utils.js';
|
|
9
|
+
import { log } from '../utils/log.js';
|
|
9
10
|
|
|
10
11
|
const debug = createDebugMessages('@testomatio/reporter:pipe:github');
|
|
11
12
|
|
|
@@ -191,9 +192,9 @@ class GitHubPipe {
|
|
|
191
192
|
debug('Comment URL:', url);
|
|
192
193
|
this.store.githubUrl = url;
|
|
193
194
|
|
|
194
|
-
|
|
195
|
+
log.info(pc.yellow('GitHub'), `Report created: ${pc.magenta(url)}`);
|
|
195
196
|
} catch (err) {
|
|
196
|
-
|
|
197
|
+
log.info(pc.yellow('GitHub'), `Couldn't create GitHub report ${err}`);
|
|
197
198
|
}
|
|
198
199
|
}
|
|
199
200
|
|
package/src/pipe/gitlab.js
CHANGED
|
@@ -7,6 +7,7 @@ import path from 'path';
|
|
|
7
7
|
import { APP_PREFIX, testomatLogoURL } from '../constants.js';
|
|
8
8
|
import { ansiRegExp, isSameTest } from '../utils/utils.js';
|
|
9
9
|
import { statusEmoji, fullName } from '../utils/pipe_utils.js';
|
|
10
|
+
import { log } from '../utils/log.js';
|
|
10
11
|
|
|
11
12
|
const debug = createDebugMessages('@testomatio/reporter:pipe:gitlab');
|
|
12
13
|
|
|
@@ -183,7 +184,7 @@ class GitLabPipe {
|
|
|
183
184
|
// eslint-disable-next-line max-len
|
|
184
185
|
const commentURL = `${this.ENV.CI_PROJECT_URL}/-/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}#note_${commentID}`;
|
|
185
186
|
|
|
186
|
-
|
|
187
|
+
log.info(pc.yellow('GitLab'), `Report created: ${pc.magenta(commentURL)}`);
|
|
187
188
|
} catch (err) {
|
|
188
189
|
console.error(
|
|
189
190
|
APP_PREFIX,
|
package/src/pipe/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
-
import { APP_PREFIX } from '../constants.js';
|
|
5
4
|
import TestomatioPipe from './testomatio.js';
|
|
6
5
|
import GitHubPipe from './github.js';
|
|
7
6
|
import GitLabPipe from './gitlab.js';
|
|
@@ -10,6 +9,7 @@ import HtmlPipe from './html.js';
|
|
|
10
9
|
import CoveragePipe from './coverage.js';
|
|
11
10
|
import { BitbucketPipe } from './bitbucket.js';
|
|
12
11
|
import { DebugPipe } from './debug.js';
|
|
12
|
+
import { log } from '../utils/log.js';
|
|
13
13
|
|
|
14
14
|
export async function pipesFactory(params, opts) {
|
|
15
15
|
const extraPipes = [];
|
|
@@ -29,14 +29,14 @@ export async function pipesFactory(params, opts) {
|
|
|
29
29
|
try {
|
|
30
30
|
PipeClass = await import(pipeDef);
|
|
31
31
|
} catch (err) {
|
|
32
|
-
|
|
32
|
+
log.info(`Can't load module Testomatio pipe module from ${pipeDef}`);
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
37
|
extraPipes.push(new PipeClass(params, opts));
|
|
38
38
|
} catch (err) {
|
|
39
|
-
|
|
39
|
+
log.info(`Can't instantiate Testomatio for ${pipeDef}`, err);
|
|
40
40
|
continue;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -56,15 +56,13 @@ export async function pipesFactory(params, opts) {
|
|
|
56
56
|
|
|
57
57
|
const pipesEnabled = pipes.filter(p => p.isEnabled);
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
APP_PREFIX,
|
|
59
|
+
log.info(
|
|
61
60
|
pc.cyan('Pipes:'),
|
|
62
61
|
pc.cyan(pipesEnabled.map(p => p.toString()).join(', ') || 'No pipes enabled'),
|
|
63
62
|
);
|
|
64
63
|
|
|
65
64
|
if (!pipesEnabled.length) {
|
|
66
|
-
|
|
67
|
-
APP_PREFIX,
|
|
65
|
+
log.info(
|
|
68
66
|
pc.dim('If you want to use Testomatio reporter, pass your token as TESTOMATIO env variable'),
|
|
69
67
|
);
|
|
70
68
|
}
|
package/src/pipe/testomatio.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '../utils/utils.js';
|
|
13
13
|
import { parseFilterParams, generateFilterRequestParams, setS3Credentials } from '../utils/pipe_utils.js';
|
|
14
14
|
import { config } from '../config.js';
|
|
15
|
+
import { log } from '../utils/log.js';
|
|
15
16
|
|
|
16
17
|
const debug = createDebugMessages('@testomatio/reporter:pipe:testomatio');
|
|
17
18
|
|
|
@@ -65,10 +66,12 @@ class TestomatioPipe {
|
|
|
65
66
|
const sha = getGitCommitSha();
|
|
66
67
|
if (sha) {
|
|
67
68
|
this.title = `Shared Run - ${sha}`;
|
|
68
|
-
|
|
69
|
+
log.info(`🔄 Auto-generated title for shared run: ${this.title}`);
|
|
69
70
|
} else {
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
log.warn(
|
|
72
|
+
pc.red('Failed to resolve git commit SHA for shared run title.'),
|
|
73
|
+
'Please run the tests inside a Git repository or set TESTOMATIO_TITLE explicitly.',
|
|
74
|
+
);
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
|
|
@@ -106,7 +109,7 @@ class TestomatioPipe {
|
|
|
106
109
|
|
|
107
110
|
if (!isValidUrl(this.url.trim())) {
|
|
108
111
|
this.isEnabled = false;
|
|
109
|
-
|
|
112
|
+
log.error(pc.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`));
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -117,7 +120,7 @@ class TestomatioPipe {
|
|
|
117
120
|
*/
|
|
118
121
|
#formatData(data) {
|
|
119
122
|
data.api_key = this.apiKey;
|
|
120
|
-
data.create = this.createNewTests;
|
|
123
|
+
if (data.create === undefined) data.create = this.createNewTests;
|
|
121
124
|
|
|
122
125
|
// add test ID + run ID
|
|
123
126
|
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
|
@@ -177,9 +180,9 @@ class TestomatioPipe {
|
|
|
177
180
|
return resp.data.tests;
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
|
|
183
|
+
log.warn(`⛔ No tests found for your --filter --> ${type}=${id}`);
|
|
181
184
|
} catch (err) {
|
|
182
|
-
|
|
185
|
+
log.error(`🚩 Error getting Testomat.io test grepList: ${err}`);
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
188
|
|
|
@@ -262,8 +265,8 @@ class TestomatioPipe {
|
|
|
262
265
|
this.runPublicUrl = resp.data.public_url;
|
|
263
266
|
this.store.runUrl = this.runUrl;
|
|
264
267
|
this.store.runPublicUrl = this.runPublicUrl;
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
log.info('📊 Using existing run. Report ID:', this.runId);
|
|
269
|
+
log.info('📊 Report URL:', pc.magenta(this.runUrl));
|
|
267
270
|
}
|
|
268
271
|
return;
|
|
269
272
|
}
|
|
@@ -287,7 +290,7 @@ class TestomatioPipe {
|
|
|
287
290
|
this.store.runUrl = this.runUrl;
|
|
288
291
|
this.store.runPublicUrl = this.runPublicUrl;
|
|
289
292
|
this.store.runId = this.runId;
|
|
290
|
-
|
|
293
|
+
log.info('📊 Report created. Report ID:', this.runId);
|
|
291
294
|
process.env.runId = this.runId;
|
|
292
295
|
debug('Run created', this.runId);
|
|
293
296
|
} catch (err) {
|
|
@@ -355,7 +358,7 @@ class TestomatioPipe {
|
|
|
355
358
|
this.#logFailedResponse(err);
|
|
356
359
|
printCreateIssue();
|
|
357
360
|
} else {
|
|
358
|
-
|
|
361
|
+
log.info(pc.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
359
362
|
}
|
|
360
363
|
});
|
|
361
364
|
};
|
|
@@ -406,7 +409,7 @@ class TestomatioPipe {
|
|
|
406
409
|
this.#logFailedResponse(err);
|
|
407
410
|
printCreateIssue();
|
|
408
411
|
} else {
|
|
409
|
-
|
|
412
|
+
log.info("Report couldn't be processed", err);
|
|
410
413
|
}
|
|
411
414
|
});
|
|
412
415
|
};
|
|
@@ -420,7 +423,7 @@ class TestomatioPipe {
|
|
|
420
423
|
|
|
421
424
|
this.runId = this.runId || process.env.runId || this.store.runId || readLatestRunId();
|
|
422
425
|
if (!this.runId) {
|
|
423
|
-
|
|
426
|
+
log.warn(pc.red('Run ID is not set, skipping test reporting'));
|
|
424
427
|
return;
|
|
425
428
|
}
|
|
426
429
|
|
|
@@ -495,21 +498,21 @@ class TestomatioPipe {
|
|
|
495
498
|
});
|
|
496
499
|
|
|
497
500
|
if (this.runUrl) {
|
|
498
|
-
|
|
501
|
+
log.warn('📊 Report URL:', pc.magenta(this.runUrl));
|
|
499
502
|
}
|
|
500
503
|
if (this.runPublicUrl) {
|
|
501
|
-
|
|
504
|
+
log.info('🌟 Public URL:', pc.magenta(this.runPublicUrl));
|
|
502
505
|
}
|
|
503
506
|
}
|
|
504
507
|
if (this.runUrl && this.proceed) {
|
|
505
508
|
const notFinishedMessage = pc.yellow(pc.bold('Run was not finished because of $TESTOMATIO_PROCEED'));
|
|
506
|
-
|
|
507
|
-
|
|
509
|
+
log.warn(`📊 ${notFinishedMessage}. Report URL: ${pc.magenta(this.runUrl)}`);
|
|
510
|
+
log.warn(`🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx @testomatio/reporter finish`);
|
|
508
511
|
}
|
|
509
512
|
|
|
510
513
|
if (this.hasUnmatchedTests) {
|
|
511
514
|
console.log('');
|
|
512
|
-
|
|
515
|
+
log.warn(pc.yellow(pc.bold('⚠️ Some reported tests were not found in Testomat.io project')));
|
|
513
516
|
console.log(
|
|
514
517
|
APP_PREFIX,
|
|
515
518
|
`If you use Testomat.io as a reporter only, please re-run tests using ${pc.bold('TESTOMATIO_CREATE=1')}`,
|
|
@@ -518,14 +521,18 @@ class TestomatioPipe {
|
|
|
518
521
|
APP_PREFIX,
|
|
519
522
|
`But to keep your tests consistent it is recommended to ${pc.bold('import tests first')}`,
|
|
520
523
|
);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
524
|
+
log.info(
|
|
525
|
+
'If tests were imported but still not matched, assign test IDs to your tests.',
|
|
526
|
+
'You can do that automatically via command line tools:',
|
|
527
|
+
pc.bold('npx check-tests ... --update-ids'),
|
|
528
|
+
'See: https://bit.ly/js-update-ids',
|
|
529
|
+
'or for Cucumber:',
|
|
530
|
+
pc.bold('npx check-cucumber ... --update-ids'),
|
|
531
|
+
'See: https://bit.ly/bdd-update-ids',
|
|
532
|
+
);
|
|
526
533
|
}
|
|
527
534
|
} catch (err) {
|
|
528
|
-
|
|
535
|
+
log.info('Error updating status, skipping...', err);
|
|
529
536
|
if (process.env.DEBUG || process.env.TESTOMATIO_DEBUG) this.#logFailedResponse(err);
|
|
530
537
|
printCreateIssue();
|
|
531
538
|
}
|
|
@@ -541,7 +548,7 @@ class TestomatioPipe {
|
|
|
541
548
|
clearInterval(this.batch.intervalFunction);
|
|
542
549
|
this.batch.intervalFunction = null;
|
|
543
550
|
this.batch.isEnabled = false;
|
|
544
|
-
}
|
|
551
|
+
}
|
|
545
552
|
this.batch.tests = [];
|
|
546
553
|
}
|
|
547
554
|
|
package/src/uploader.js
CHANGED
|
@@ -8,6 +8,7 @@ import promiseRetry from 'promise-retry';
|
|
|
8
8
|
import pc from 'picocolors';
|
|
9
9
|
import { APP_PREFIX } from './constants.js';
|
|
10
10
|
import { filesize as prettyBytes } from 'filesize';
|
|
11
|
+
import { log } from './utils/log.js';
|
|
11
12
|
|
|
12
13
|
const debug = createDebugMessages('@testomatio/reporter:file-uploader');
|
|
13
14
|
|
|
@@ -133,7 +134,7 @@ export class S3Uploader {
|
|
|
133
134
|
} catch (e) {
|
|
134
135
|
this.failedUploads.push({ path: file.path, size: file.size });
|
|
135
136
|
debug('S3 uploading error:', e);
|
|
136
|
-
|
|
137
|
+
log.info('Upload failed:', e.message, '\nConfig:\n', this.getMaskedConfig());
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -160,7 +161,7 @@ export class S3Uploader {
|
|
|
160
161
|
const diffHours = diff / 1000 / 60 / 60;
|
|
161
162
|
debug('Diff hours:', diffHours);
|
|
162
163
|
if (diffHours > 3) {
|
|
163
|
-
|
|
164
|
+
log.info("Artifacts file is too old, can't process artifacts. Please re-run the tests.");
|
|
164
165
|
return [];
|
|
165
166
|
}
|
|
166
167
|
|