@testomatio/reporter 2.0.0-beta-esm → 2.0.0-beta.1-xml
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 -26
- 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 +10 -10
- 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 -4
- package/lib/adapter/nightwatch.d.ts +4 -0
- package/lib/adapter/nightwatch.js +80 -0
- 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 +51 -14
- package/lib/bin/cli.d.ts +2 -0
- package/lib/bin/cli.js +250 -0
- package/lib/bin/reportXml.d.ts +2 -0
- package/lib/bin/reportXml.js +15 -11
- package/lib/bin/startTest.d.ts +2 -0
- package/lib/bin/startTest.js +12 -7
- package/lib/bin/uploadArtifacts.d.ts +2 -0
- package/lib/bin/uploadArtifacts.js +82 -0
- package/lib/client.d.ts +76 -0
- package/lib/client.js +128 -53
- package/lib/config.d.ts +1 -0
- package/lib/config.js +2 -2
- 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 +19 -9
- package/lib/junit-adapter/adapter.d.ts +9 -0
- package/lib/junit-adapter/csharp.d.ts +5 -0
- package/lib/junit-adapter/csharp.js +11 -1
- 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 +19 -9
- 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 +37 -5
- package/lib/pipe/gitlab.d.ts +23 -0
- package/lib/pipe/gitlab.js +2 -3
- package/lib/pipe/html.d.ts +35 -0
- package/lib/pipe/html.js +9 -4
- package/lib/pipe/index.d.ts +1 -0
- package/lib/pipe/index.js +20 -10
- package/lib/pipe/testomatio.d.ts +70 -0
- package/lib/pipe/testomatio.js +54 -39
- 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/services/logger.js +1 -2
- 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 +47 -0
- package/lib/utils/utils.js +99 -12
- package/lib/xmlReader.d.ts +92 -0
- package/lib/xmlReader.js +64 -25
- package/package.json +19 -13
- package/src/adapter/codecept.js +30 -26
- package/src/adapter/cypress-plugin/index.js +5 -5
- package/src/adapter/mocha.cjs +1 -1
- package/src/adapter/mocha.js +4 -4
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/playwright.js +59 -31
- package/src/adapter/vitest.js +6 -6
- package/src/adapter/webdriver.js +42 -12
- package/src/bin/cli.js +303 -0
- package/src/bin/reportXml.js +19 -9
- package/src/bin/startTest.js +9 -4
- package/src/bin/uploadArtifacts.js +91 -0
- package/src/client.js +137 -57
- package/src/config.js +2 -2
- package/src/constants.js +5 -1
- package/src/data-storage.js +2 -2
- package/src/junit-adapter/csharp.js +13 -1
- 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 +3 -5
- package/src/pipe/gitlab.js +6 -7
- package/src/pipe/html.js +14 -7
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +75 -76
- 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 +5 -4
- 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 +64 -15
- package/src/xmlReader.js +76 -26
- 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/xmlReader.js
CHANGED
|
@@ -13,11 +13,12 @@ import {
|
|
|
13
13
|
fetchSourceCodeFromStackTrace,
|
|
14
14
|
fetchIdFromCode,
|
|
15
15
|
humanize,
|
|
16
|
+
TEST_ID_REGEX,
|
|
16
17
|
} from './utils/utils.js';
|
|
17
|
-
import {pipesFactory} from './pipe/index.js';
|
|
18
|
+
import { pipesFactory } from './pipe/index.js';
|
|
18
19
|
import adapterFactory from './junit-adapter/index.js';
|
|
19
|
-
import {config} from './config.js';
|
|
20
|
-
import {
|
|
20
|
+
import { config } from './config.js';
|
|
21
|
+
import { S3Uploader } from './uploader.js';
|
|
21
22
|
|
|
22
23
|
// @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
|
|
23
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -26,7 +27,9 @@ const debug = createDebugMessages('@testomatio/reporter:xml');
|
|
|
26
27
|
const ridRunId = randomUUID();
|
|
27
28
|
|
|
28
29
|
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
29
|
-
const { TESTOMATIO_RUNGROUP_TITLE,
|
|
30
|
+
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE,
|
|
31
|
+
TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV,
|
|
32
|
+
TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } = process.env;
|
|
30
33
|
|
|
31
34
|
const options = {
|
|
32
35
|
ignoreDeclaration: true,
|
|
@@ -36,6 +39,8 @@ const options = {
|
|
|
36
39
|
parseTagValue: true,
|
|
37
40
|
};
|
|
38
41
|
|
|
42
|
+
const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
|
|
43
|
+
|
|
39
44
|
const reduceOptions = {};
|
|
40
45
|
|
|
41
46
|
class XmlReader {
|
|
@@ -46,6 +51,7 @@ class XmlReader {
|
|
|
46
51
|
title: TESTOMATIO_TITLE,
|
|
47
52
|
env: TESTOMATIO_ENV,
|
|
48
53
|
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
54
|
+
detach: TESTOMATIO_MARK_DETACHED,
|
|
49
55
|
// batch uploading is implemented for xml already
|
|
50
56
|
isBatchEnabled: false,
|
|
51
57
|
};
|
|
@@ -61,7 +67,7 @@ class XmlReader {
|
|
|
61
67
|
this.tests = [];
|
|
62
68
|
this.stats = {};
|
|
63
69
|
this.stats.language = opts.lang?.toLowerCase();
|
|
64
|
-
this.
|
|
70
|
+
this.uploader = new S3Uploader();
|
|
65
71
|
|
|
66
72
|
// @ts-ignore
|
|
67
73
|
const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
|
|
@@ -89,7 +95,7 @@ class XmlReader {
|
|
|
89
95
|
];
|
|
90
96
|
|
|
91
97
|
for (const regex of cutRegexes) {
|
|
92
|
-
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0,
|
|
98
|
+
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, MAX_OUTPUT_LENGTH)}${p3}`);
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
const jsonResult = this.parser.parse(xmlData);
|
|
@@ -122,17 +128,25 @@ class XmlReader {
|
|
|
122
128
|
const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
|
|
123
129
|
const status = failures > 0 || errors > 0 || hasFailures ? 'failed' : 'passed';
|
|
124
130
|
|
|
131
|
+
const time = testsuite.time || 0;
|
|
132
|
+
// debug('time', jsonSuite, time)
|
|
133
|
+
if (time) {
|
|
134
|
+
if (!this.stats.duration) this.stats.duration = 0;
|
|
135
|
+
this.stats.duration += parseFloat(time);
|
|
136
|
+
}
|
|
137
|
+
|
|
125
138
|
this.tests = this.tests.concat(resultTests);
|
|
126
139
|
|
|
127
140
|
return {
|
|
128
|
-
status,
|
|
129
141
|
create_tests: true,
|
|
142
|
+
duration: parseFloat(time),
|
|
143
|
+
failed_count: parseInt(failures, 10),
|
|
130
144
|
name,
|
|
131
|
-
tests_count: parseInt(tests, 10),
|
|
132
145
|
passed_count: parseInt(tests, 10) - parseInt(failures, 10),
|
|
133
|
-
failed_count: parseInt(failures, 10),
|
|
134
146
|
skipped_count: 0,
|
|
147
|
+
status,
|
|
135
148
|
tests: resultTests,
|
|
149
|
+
tests_count: parseInt(tests, 10),
|
|
136
150
|
};
|
|
137
151
|
}
|
|
138
152
|
|
|
@@ -216,7 +230,7 @@ class XmlReader {
|
|
|
216
230
|
|
|
217
231
|
return {
|
|
218
232
|
status,
|
|
219
|
-
create_tests:
|
|
233
|
+
create_tests: !process.env.IGNORE_NEW_TESTS,
|
|
220
234
|
tests_count: parseInt(counters.total, 10),
|
|
221
235
|
passed_count: parseInt(counters.passed, 10),
|
|
222
236
|
skipped_count: parseInt(counters.notExecuted, 10),
|
|
@@ -300,6 +314,7 @@ class XmlReader {
|
|
|
300
314
|
calculateStats() {
|
|
301
315
|
this.stats = {
|
|
302
316
|
...this.stats,
|
|
317
|
+
detach: this.requestParams.detach,
|
|
303
318
|
status: 'passed',
|
|
304
319
|
create_tests: true,
|
|
305
320
|
tests_count: 0,
|
|
@@ -330,6 +345,7 @@ class XmlReader {
|
|
|
330
345
|
if (file.endsWith('.rb')) this.stats.language = 'ruby';
|
|
331
346
|
if (file.endsWith('.js')) this.stats.language = 'js';
|
|
332
347
|
if (file.endsWith('.ts')) this.stats.language = 'ts';
|
|
348
|
+
if (file.endsWith('.cs')) this.stats.language = 'csharp';
|
|
333
349
|
}
|
|
334
350
|
|
|
335
351
|
if (!fs.existsSync(file)) {
|
|
@@ -383,13 +399,14 @@ class XmlReader {
|
|
|
383
399
|
async uploadArtifacts() {
|
|
384
400
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
385
401
|
let files = [];
|
|
386
|
-
if (test.files?.length)
|
|
387
|
-
|
|
402
|
+
if (!test.files?.length) continue;
|
|
403
|
+
|
|
404
|
+
files = test.files.map(f => path.isAbsolute(f) ? f : path.join(process.cwd(), f));
|
|
388
405
|
|
|
389
406
|
if (!files.length) continue;
|
|
390
407
|
|
|
391
408
|
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
392
|
-
test.artifacts = await Promise.all(files.map(f =>
|
|
409
|
+
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId, path.basename(f)])));
|
|
393
410
|
console.log(APP_PREFIX, `🗄️ Uploaded ${pc.bold(`${files.length} artifacts`)} for test ${test.title}`);
|
|
394
411
|
}
|
|
395
412
|
}
|
|
@@ -406,7 +423,9 @@ class XmlReader {
|
|
|
406
423
|
debug('Run', runParams);
|
|
407
424
|
this.pipes = this.pipes || (await this.pipesPromise);
|
|
408
425
|
|
|
409
|
-
|
|
426
|
+
const run = await Promise.all(this.pipes.map(p => p.createRun(runParams)));
|
|
427
|
+
this.uploader.checkEnabled();
|
|
428
|
+
return run;
|
|
410
429
|
}
|
|
411
430
|
|
|
412
431
|
async uploadData() {
|
|
@@ -417,18 +436,16 @@ class XmlReader {
|
|
|
417
436
|
this.formatErrors();
|
|
418
437
|
this.formatTests();
|
|
419
438
|
|
|
420
|
-
debug('Uploading data', {
|
|
421
|
-
...this.stats,
|
|
422
|
-
tests: this.tests,
|
|
423
|
-
});
|
|
424
|
-
|
|
425
439
|
const dataString = {
|
|
426
440
|
...this.stats,
|
|
427
441
|
api_key: this.requestParams.apiKey,
|
|
428
442
|
status: 'finished',
|
|
443
|
+
duration: this.stats.duration,
|
|
429
444
|
tests: this.tests,
|
|
430
445
|
};
|
|
431
446
|
|
|
447
|
+
debug('Uploading data', dataString);
|
|
448
|
+
|
|
432
449
|
this.pipes = this.pipes || (await this.pipesPromise);
|
|
433
450
|
return Promise.all(this.pipes.map(p => p.finishRun(dataString)));
|
|
434
451
|
}
|
|
@@ -449,14 +466,18 @@ function reduceTestCases(prev, item) {
|
|
|
449
466
|
}
|
|
450
467
|
|
|
451
468
|
// suite inside test case
|
|
452
|
-
|
|
469
|
+
const testCase = item['test-suite']?.['test-case'];
|
|
470
|
+
if (testCase) {
|
|
471
|
+
const nestedCases = Array.isArray(testCase) ? testCase : [testCase];
|
|
472
|
+
testCases.push(...nestedCases);
|
|
473
|
+
}
|
|
453
474
|
|
|
454
475
|
const suiteOutput = item['system-out'] || item.output || item.log || '';
|
|
455
476
|
const suiteErr = item['system-err'] || item.output || item.log || '';
|
|
456
477
|
testCases
|
|
457
478
|
.filter(t => !!t)
|
|
458
479
|
.forEach(testCaseItem => {
|
|
459
|
-
const file = testCaseItem.file || item.filepath || '';
|
|
480
|
+
const file = testCaseItem.file || item.filepath || item.fullname || '';
|
|
460
481
|
|
|
461
482
|
let stack = '';
|
|
462
483
|
let message = '';
|
|
@@ -487,19 +508,41 @@ function reduceTestCases(prev, item) {
|
|
|
487
508
|
title = title.replace(/\(.*?\)/, '').trim();
|
|
488
509
|
}
|
|
489
510
|
|
|
490
|
-
// eslint-disable-next-line
|
|
491
511
|
stack = `${
|
|
492
512
|
testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
|
|
493
513
|
}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
|
|
494
|
-
|
|
514
|
+
let testId = fetchIdFromOutput(stack);
|
|
515
|
+
|
|
516
|
+
if (tags?.length && !testId) {
|
|
517
|
+
testId = tags.filter(t => t.startsWith('T')).map(t => `@${t}`).find(t => t.match(TEST_ID_REGEX))?.slice(2);
|
|
518
|
+
}
|
|
495
519
|
|
|
496
520
|
let status = STATUS.PASSED.toString();
|
|
497
521
|
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
498
522
|
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
523
|
+
if (testCaseItem.result && Object.values(STATUS).includes(testCaseItem.result.toLowerCase())) {
|
|
524
|
+
status = testCaseItem.result.toLowerCase();
|
|
525
|
+
}
|
|
499
526
|
|
|
500
527
|
let rid = null;
|
|
501
528
|
if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
|
|
502
529
|
|
|
530
|
+
// Extract attachments
|
|
531
|
+
let files = [];
|
|
532
|
+
if (testCaseItem.attachments) {
|
|
533
|
+
const attachments = Array.isArray(testCaseItem.attachments.attachment)
|
|
534
|
+
? testCaseItem.attachments.attachment
|
|
535
|
+
: [testCaseItem.attachments.attachment];
|
|
536
|
+
|
|
537
|
+
files = attachments
|
|
538
|
+
.filter(a => a && a.filePath)
|
|
539
|
+
.map(a => a.filePath);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Extract files from stack trace using existing utility
|
|
543
|
+
const stackFiles = fetchFilesFromStackTrace(stack);
|
|
544
|
+
files = [...new Set([...files, ...stackFiles])]; // Remove duplicates
|
|
545
|
+
|
|
503
546
|
prev.push({
|
|
504
547
|
rid,
|
|
505
548
|
file,
|
|
@@ -514,7 +557,9 @@ function reduceTestCases(prev, item) {
|
|
|
514
557
|
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
515
558
|
status,
|
|
516
559
|
title,
|
|
560
|
+
root_suite_id: TESTOMATIO_SUITE,
|
|
517
561
|
suite_title: suiteTitle,
|
|
562
|
+
files,
|
|
518
563
|
});
|
|
519
564
|
});
|
|
520
565
|
return prev;
|
|
@@ -541,10 +586,15 @@ function fetchProperties(item) {
|
|
|
541
586
|
|
|
542
587
|
if (!item.properties) return {};
|
|
543
588
|
|
|
544
|
-
|
|
589
|
+
// Handle both single property and array of properties
|
|
590
|
+
const properties = Array.isArray(item.properties.property)
|
|
591
|
+
? item.properties.property
|
|
592
|
+
: [item.properties.property].filter(Boolean);
|
|
593
|
+
|
|
594
|
+
const prop = properties.find(p => p.name === 'Description');
|
|
545
595
|
if (prop) title = prop.value;
|
|
546
|
-
|
|
547
|
-
|
|
596
|
+
|
|
597
|
+
properties
|
|
548
598
|
.filter(p => p.name === 'Category')
|
|
549
599
|
.forEach(p => tags.push(p.value));
|
|
550
600
|
return { title, tags };
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.JasmineReporter = void 0;
|
|
7
|
-
const client_js_1 = __importDefault(require("../../client.js"));
|
|
8
|
-
const utils_js_1 = require("../../utils/utils.js");
|
|
9
|
-
const constants_js_1 = require("../../constants.js");
|
|
10
|
-
class JasmineReporter {
|
|
11
|
-
constructor(options) {
|
|
12
|
-
this.testTimeMap = {};
|
|
13
|
-
this.client = new client_js_1.default({ apiKey: options?.apiKey });
|
|
14
|
-
this.client.createRun();
|
|
15
|
-
}
|
|
16
|
-
getDuration(test) {
|
|
17
|
-
if (this.testTimeMap[test.id]) {
|
|
18
|
-
return Date.now() - this.testTimeMap[test.id];
|
|
19
|
-
}
|
|
20
|
-
return 0;
|
|
21
|
-
}
|
|
22
|
-
specStarted(result) {
|
|
23
|
-
this.testTimeMap[result.id] = Date.now();
|
|
24
|
-
}
|
|
25
|
-
specDone(result) {
|
|
26
|
-
if (!this.client)
|
|
27
|
-
return;
|
|
28
|
-
const title = result.description;
|
|
29
|
-
const { status } = result;
|
|
30
|
-
let errorMessage = '';
|
|
31
|
-
for (let i = 0; i < result.failedExpectations.length; i += 1) {
|
|
32
|
-
errorMessage = `${errorMessage}Failure: ${result.failedExpectations[i].message}\n`;
|
|
33
|
-
errorMessage = `${errorMessage}\n ${result.failedExpectations[i].stack}`;
|
|
34
|
-
}
|
|
35
|
-
console.log(`${title} : ${constants_js_1.STATUS.PASSED}`);
|
|
36
|
-
console.log(errorMessage);
|
|
37
|
-
const testId = (0, utils_js_1.getTestomatIdFromTestTitle)(title);
|
|
38
|
-
errorMessage = errorMessage.replace((0, utils_js_1.ansiRegExp)(), '');
|
|
39
|
-
this.client.addTestRun(status, {
|
|
40
|
-
error: result.failedExpectations[0],
|
|
41
|
-
message: errorMessage,
|
|
42
|
-
test_id: testId,
|
|
43
|
-
title,
|
|
44
|
-
time: this.getDuration(result),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
jasmineDone(suiteInfo, done) {
|
|
48
|
-
if (!this.client)
|
|
49
|
-
return;
|
|
50
|
-
const { overallStatus } = suiteInfo;
|
|
51
|
-
const status = overallStatus === 'failed' ? constants_js_1.STATUS.FAILED : constants_js_1.STATUS.PASSED;
|
|
52
|
-
// @ts-ignore
|
|
53
|
-
this.client.updateRunStatus(status).then(() => done);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
exports.JasmineReporter = JasmineReporter;
|
|
57
|
-
module.exports = JasmineReporter;
|
|
58
|
-
|
|
59
|
-
module.exports.JasmineReporter = JasmineReporter;
|
|
60
|
-
|
|
61
|
-
module.exports.JasmineReporter = JasmineReporter;
|
|
62
|
-
|
|
63
|
-
module.exports.exports = exports;
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
7
|
-
const mocha_1 = __importDefault(require("mocha"));
|
|
8
|
-
const client_js_1 = __importDefault(require("../../client.js"));
|
|
9
|
-
const constants_js_1 = require("../../constants.js");
|
|
10
|
-
const utils_js_1 = require("../../utils/utils.js");
|
|
11
|
-
const config_js_1 = require("../../config.js");
|
|
12
|
-
const index_js_1 = require("../../services/index.js");
|
|
13
|
-
const picocolors_1 = __importDefault(require("picocolors"));
|
|
14
|
-
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING, EVENT_SUITE_BEGIN, EVENT_SUITE_END, EVENT_TEST_BEGIN, EVENT_TEST_END, } = mocha_1.default.Runner.constants;
|
|
15
|
-
function MochaReporter(runner, opts) {
|
|
16
|
-
mocha_1.default.reporters.Base.call(this, runner);
|
|
17
|
-
let passes = 0;
|
|
18
|
-
let failures = 0;
|
|
19
|
-
let skipped = 0;
|
|
20
|
-
// let artifactStore;
|
|
21
|
-
const apiKey = opts?.reporterOptions?.apiKey || config_js_1.config.TESTOMATIO;
|
|
22
|
-
const client = new client_js_1.default({ apiKey });
|
|
23
|
-
runner.on(EVENT_RUN_BEGIN, () => {
|
|
24
|
-
client.createRun();
|
|
25
|
-
utils_js_1.fileSystem.clearDir(constants_js_1.TESTOMAT_TMP_STORAGE_DIR);
|
|
26
|
-
});
|
|
27
|
-
runner.on(EVENT_SUITE_BEGIN, async (suite) => {
|
|
28
|
-
index_js_1.services.setContext(suite.fullTitle());
|
|
29
|
-
});
|
|
30
|
-
runner.on(EVENT_SUITE_END, async () => {
|
|
31
|
-
index_js_1.services.setContext(null);
|
|
32
|
-
});
|
|
33
|
-
runner.on(EVENT_TEST_BEGIN, async (test) => {
|
|
34
|
-
index_js_1.services.setContext(test.fullTitle());
|
|
35
|
-
});
|
|
36
|
-
runner.on(EVENT_TEST_END, async () => {
|
|
37
|
-
index_js_1.services.setContext(null);
|
|
38
|
-
});
|
|
39
|
-
runner.on(EVENT_TEST_PASS, async (test) => {
|
|
40
|
-
passes += 1;
|
|
41
|
-
console.log(picocolors_1.default.bold(picocolors_1.default.green('✔')), test.fullTitle());
|
|
42
|
-
const testId = (0, utils_js_1.getTestomatIdFromTestTitle)(test.title);
|
|
43
|
-
const logs = getTestLogs(test);
|
|
44
|
-
const artifacts = index_js_1.services.artifacts.get(test.fullTitle());
|
|
45
|
-
const keyValues = index_js_1.services.keyValues.get(test.fullTitle());
|
|
46
|
-
client.addTestRun(constants_js_1.STATUS.PASSED, {
|
|
47
|
-
test_id: testId,
|
|
48
|
-
suite_title: getSuiteTitle(test),
|
|
49
|
-
title: getTestName(test),
|
|
50
|
-
code: test.body.toString(),
|
|
51
|
-
file: getFile(test),
|
|
52
|
-
time: test.duration,
|
|
53
|
-
logs,
|
|
54
|
-
manuallyAttachedArtifacts: artifacts,
|
|
55
|
-
meta: keyValues,
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
runner.on(EVENT_TEST_PENDING, test => {
|
|
59
|
-
skipped += 1;
|
|
60
|
-
console.log('skip: %s', test.fullTitle());
|
|
61
|
-
const testId = (0, utils_js_1.getTestomatIdFromTestTitle)(test.title);
|
|
62
|
-
client.addTestRun(constants_js_1.STATUS.SKIPPED, {
|
|
63
|
-
title: getTestName(test),
|
|
64
|
-
suite_title: getSuiteTitle(test),
|
|
65
|
-
code: test.body.toString(),
|
|
66
|
-
file: getFile(test),
|
|
67
|
-
test_id: testId,
|
|
68
|
-
time: test.duration,
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
runner.on(EVENT_TEST_FAIL, async (test, err) => {
|
|
72
|
-
failures += 1;
|
|
73
|
-
console.log(picocolors_1.default.bold(picocolors_1.default.red('✖')), test.fullTitle(), picocolors_1.default.gray(err.message));
|
|
74
|
-
const testId = (0, utils_js_1.getTestomatIdFromTestTitle)(test.title);
|
|
75
|
-
const logs = getTestLogs(test);
|
|
76
|
-
client.addTestRun(constants_js_1.STATUS.FAILED, {
|
|
77
|
-
error: err,
|
|
78
|
-
suite_title: getSuiteTitle(test),
|
|
79
|
-
file: getFile(test),
|
|
80
|
-
test_id: testId,
|
|
81
|
-
title: getTestName(test),
|
|
82
|
-
code: test.body.toString(),
|
|
83
|
-
time: test.duration,
|
|
84
|
-
logs,
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
runner.on(EVENT_RUN_END, () => {
|
|
88
|
-
const status = failures === 0 ? constants_js_1.STATUS.PASSED : constants_js_1.STATUS.FAILED;
|
|
89
|
-
console.log(picocolors_1.default.bold(status), `${passes} passed, ${failures} failed, ${skipped} skipped`);
|
|
90
|
-
// @ts-ignore
|
|
91
|
-
client.updateRunStatus(status);
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
function getTestLogs(test) {
|
|
95
|
-
const suiteLogsArr = index_js_1.services.logger.getLogs(test.parent.fullTitle());
|
|
96
|
-
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
97
|
-
const testLogsArr = index_js_1.services.logger.getLogs(test.fullTitle());
|
|
98
|
-
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
99
|
-
let logs = '';
|
|
100
|
-
if (suiteLogs) {
|
|
101
|
-
logs += `${picocolors_1.default.bold('\t--- BeforeSuite ---')}\n${suiteLogs}`;
|
|
102
|
-
}
|
|
103
|
-
if (testLogs) {
|
|
104
|
-
logs += `\n${picocolors_1.default.bold('\t--- Test ---')}\n${testLogs}`;
|
|
105
|
-
}
|
|
106
|
-
return logs;
|
|
107
|
-
}
|
|
108
|
-
function getSuiteTitle(test, pathArr = []) {
|
|
109
|
-
if (test.parent.parent)
|
|
110
|
-
getSuiteTitle(test.parent, pathArr);
|
|
111
|
-
pathArr.push(test.parent.title);
|
|
112
|
-
return pathArr.filter(t => !!t)[0];
|
|
113
|
-
}
|
|
114
|
-
function getFile(test) {
|
|
115
|
-
return test.parent.file?.replace(process.cwd(), '');
|
|
116
|
-
}
|
|
117
|
-
function getTestName(test) {
|
|
118
|
-
if (process.env.TESTOMATIO_CREATE === 'fulltitle')
|
|
119
|
-
return test.fullTitle();
|
|
120
|
-
return test.title;
|
|
121
|
-
}
|
|
122
|
-
// To have this reporter "extend" a built-in reporter uncomment the following line:
|
|
123
|
-
// @ts-ignore
|
|
124
|
-
mocha_1.default.utils.inherits(MochaReporter, mocha_1.default.reporters.Spec);
|
|
125
|
-
module.exports = MochaReporter;
|
package/lib/fileUploader.js
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.upload = void 0;
|
|
7
|
-
const debug_1 = __importDefault(require("debug"));
|
|
8
|
-
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
9
|
-
const lib_storage_1 = require("@aws-sdk/lib-storage");
|
|
10
|
-
const fs_1 = __importDefault(require("fs"));
|
|
11
|
-
const util_1 = __importDefault(require("util"));
|
|
12
|
-
const path_1 = __importDefault(require("path"));
|
|
13
|
-
const promise_retry_1 = __importDefault(require("promise-retry"));
|
|
14
|
-
const picocolors_1 = __importDefault(require("picocolors"));
|
|
15
|
-
const crypto_1 = require("crypto");
|
|
16
|
-
const constants_js_1 = require("./constants.js");
|
|
17
|
-
const readFile = util_1.default.promisify(fs_1.default.readFile);
|
|
18
|
-
const stat = util_1.default.promisify(fs_1.default.stat);
|
|
19
|
-
const debug = (0, debug_1.default)('@testomatio/reporter:file-uploader');
|
|
20
|
-
const keys = [
|
|
21
|
-
'S3_ENDPOINT',
|
|
22
|
-
'S3_REGION',
|
|
23
|
-
'S3_BUCKET',
|
|
24
|
-
'S3_ACCESS_KEY_ID',
|
|
25
|
-
'S3_SECRET_ACCESS_KEY',
|
|
26
|
-
'S3_SESSION_TOKEN',
|
|
27
|
-
'TESTOMATIO_DISABLE_ARTIFACTS',
|
|
28
|
-
'TESTOMATIO_PRIVATE_ARTIFACTS',
|
|
29
|
-
'S3_FORCE_PATH_STYLE',
|
|
30
|
-
];
|
|
31
|
-
let config;
|
|
32
|
-
function resetConfig() {
|
|
33
|
-
config = undefined;
|
|
34
|
-
isEnabled = undefined;
|
|
35
|
-
}
|
|
36
|
-
function getConfig() {
|
|
37
|
-
if (config)
|
|
38
|
-
return config;
|
|
39
|
-
config = keys.reduce((acc, key) => {
|
|
40
|
-
acc[key] = process.env[key];
|
|
41
|
-
return acc;
|
|
42
|
-
}, {});
|
|
43
|
-
return config;
|
|
44
|
-
}
|
|
45
|
-
function getMaskedConfig() {
|
|
46
|
-
return Object.fromEntries(Object.entries(getConfig()).map(([key, value]) => [
|
|
47
|
-
key,
|
|
48
|
-
key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
|
|
49
|
-
]));
|
|
50
|
-
}
|
|
51
|
-
let isEnabled;
|
|
52
|
-
const isArtifactsEnabled = () => {
|
|
53
|
-
if (isEnabled !== undefined)
|
|
54
|
-
return isEnabled;
|
|
55
|
-
const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = getConfig();
|
|
56
|
-
isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
|
|
57
|
-
debug(`Upload is ${isEnabled ? 'enabled' : 'disabled'}`);
|
|
58
|
-
return isEnabled;
|
|
59
|
-
};
|
|
60
|
-
const _getFileExtBase64 = str => {
|
|
61
|
-
const type = str.charAt(0);
|
|
62
|
-
return ({
|
|
63
|
-
'/': '.jpg',
|
|
64
|
-
i: '.png',
|
|
65
|
-
R: '.gif',
|
|
66
|
-
U: '.webp',
|
|
67
|
-
}[type] || '');
|
|
68
|
-
};
|
|
69
|
-
const _getS3Config = () => {
|
|
70
|
-
const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } = getConfig();
|
|
71
|
-
const cfg = {
|
|
72
|
-
region: S3_REGION,
|
|
73
|
-
credentials: {
|
|
74
|
-
accessKeyId: S3_ACCESS_KEY_ID,
|
|
75
|
-
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
76
|
-
s3ForcePathStyle: S3_FORCE_PATH_STYLE,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
if (S3_SESSION_TOKEN) {
|
|
80
|
-
cfg.credentials.sessionToken = S3_SESSION_TOKEN;
|
|
81
|
-
}
|
|
82
|
-
if (S3_ENDPOINT) {
|
|
83
|
-
cfg.endpoint = S3_ENDPOINT;
|
|
84
|
-
}
|
|
85
|
-
return cfg;
|
|
86
|
-
};
|
|
87
|
-
const uploadUsingS3 = async (filePath, runId) => {
|
|
88
|
-
let ContentType;
|
|
89
|
-
let Key;
|
|
90
|
-
if (typeof filePath === 'object') {
|
|
91
|
-
ContentType = filePath?.type;
|
|
92
|
-
filePath = filePath?.path;
|
|
93
|
-
Key = filePath?.name;
|
|
94
|
-
}
|
|
95
|
-
const { TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
|
|
96
|
-
try {
|
|
97
|
-
debug('S3 config', getMaskedConfig());
|
|
98
|
-
debug('Started upload', filePath, 'to ', S3_BUCKET);
|
|
99
|
-
// Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
|
|
100
|
-
const isFileExist = await checkFileExists(filePath, 20, 500);
|
|
101
|
-
if (!isFileExist) {
|
|
102
|
-
console.error(picocolors_1.default.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
debug('File: ', filePath, ' exists');
|
|
106
|
-
const fileData = await readFile(filePath);
|
|
107
|
-
Key = `${runId}/${(0, crypto_1.randomUUID)()}-${Key || path_1.default.basename(filePath)}`;
|
|
108
|
-
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
109
|
-
if (!S3_BUCKET || !fileData) {
|
|
110
|
-
console.log(constants_js_1.APP_PREFIX, picocolors_1.default.bold(picocolors_1.default.red(`Failed uploading '${Key}'. Please check S3 credentials`)), getMaskedConfig());
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
const s3 = new client_s3_1.S3(_getS3Config());
|
|
114
|
-
/**
|
|
115
|
-
* @type {import('@aws-sdk/client-s3').PutObjectCommandInput}
|
|
116
|
-
*/
|
|
117
|
-
const params = {
|
|
118
|
-
Bucket: S3_BUCKET,
|
|
119
|
-
Key,
|
|
120
|
-
Body: fileData,
|
|
121
|
-
ContentType,
|
|
122
|
-
ACL,
|
|
123
|
-
};
|
|
124
|
-
const out = new lib_storage_1.Upload({
|
|
125
|
-
client: s3,
|
|
126
|
-
params,
|
|
127
|
-
});
|
|
128
|
-
const link = await getS3LocationLink(out);
|
|
129
|
-
debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
|
|
130
|
-
return link;
|
|
131
|
-
}
|
|
132
|
-
catch (e) {
|
|
133
|
-
debug('S3 file uploading error: ', e);
|
|
134
|
-
console.log(constants_js_1.APP_PREFIX, `To ${picocolors_1.default.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
135
|
-
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
136
|
-
console.log(constants_js_1.APP_PREFIX, `To enable ${picocolors_1.default.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
console.log(constants_js_1.APP_PREFIX, `To enable ${picocolors_1.default.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`);
|
|
140
|
-
}
|
|
141
|
-
console.log(constants_js_1.APP_PREFIX, '---------------');
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
145
|
-
const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
|
|
146
|
-
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
147
|
-
const fileExtension = _getFileExtBase64(buffer.toString('base64'));
|
|
148
|
-
const Key = `${runId}/${fileName}${fileExtension}`;
|
|
149
|
-
if (!S3_BUCKET || !buffer) {
|
|
150
|
-
console.log(constants_js_1.APP_PREFIX, picocolors_1.default.bold(picocolors_1.default.red(`Failed uploading '${Key}'. Please check S3 credentials`)), {
|
|
151
|
-
accessKeyId: S3_ACCESS_KEY_ID,
|
|
152
|
-
secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
|
|
153
|
-
region: S3_REGION,
|
|
154
|
-
bucket: S3_BUCKET,
|
|
155
|
-
acl: ACL,
|
|
156
|
-
endpoint: S3_ENDPOINT,
|
|
157
|
-
});
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const s3 = new client_s3_1.S3(_getS3Config());
|
|
161
|
-
try {
|
|
162
|
-
const out = new lib_storage_1.Upload({
|
|
163
|
-
client: s3,
|
|
164
|
-
params: {
|
|
165
|
-
Bucket: S3_BUCKET,
|
|
166
|
-
Key,
|
|
167
|
-
Body: buffer,
|
|
168
|
-
ACL,
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
return await getS3LocationLink(out);
|
|
172
|
-
}
|
|
173
|
-
catch (e) {
|
|
174
|
-
debug('S3 buffer uploading error: ', e);
|
|
175
|
-
console.log(constants_js_1.APP_PREFIX, `To ${picocolors_1.default.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
176
|
-
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
177
|
-
console.log(constants_js_1.APP_PREFIX, `To enable ${picocolors_1.default.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
console.log(constants_js_1.APP_PREFIX, `To enable ${picocolors_1.default.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`);
|
|
181
|
-
}
|
|
182
|
-
console.log(constants_js_1.APP_PREFIX, '---------------');
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const uploadFileByPath = async (filePath, runId) => {
|
|
186
|
-
try {
|
|
187
|
-
if (isArtifactsEnabled()) {
|
|
188
|
-
return uploadUsingS3(filePath, runId);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
catch (e) {
|
|
192
|
-
debug(e);
|
|
193
|
-
console.error(picocolors_1.default.red('Error occurred while uploading artifacts! '), e);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
const uploadFileAsBuffer = async (buffer, fileName, runId) => {
|
|
197
|
-
try {
|
|
198
|
-
if (isArtifactsEnabled()) {
|
|
199
|
-
return uploadUsingS3AsBuffer(buffer, fileName, runId);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
catch (e) {
|
|
203
|
-
debug(e);
|
|
204
|
-
console.error(picocolors_1.default.red('Error occurred while uploading artifacts! '), e);
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
|
|
208
|
-
const checkFile = async () => {
|
|
209
|
-
const fileStats = await stat(filePath);
|
|
210
|
-
if (fileStats.isFile()) {
|
|
211
|
-
return true;
|
|
212
|
-
}
|
|
213
|
-
throw new Error('File not found');
|
|
214
|
-
};
|
|
215
|
-
try {
|
|
216
|
-
await (0, promise_retry_1.default)({
|
|
217
|
-
retries: attempts,
|
|
218
|
-
minTimeout: intervalMs,
|
|
219
|
-
}, checkFile);
|
|
220
|
-
return true;
|
|
221
|
-
}
|
|
222
|
-
catch (err) {
|
|
223
|
-
console.error(picocolors_1.default.yellow(`File ${filePath} was not found or did not have time to be generated...`));
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
};
|
|
227
|
-
const getS3LocationLink = async (out) => {
|
|
228
|
-
const response = await out.done();
|
|
229
|
-
let s3Location = response?.Location;
|
|
230
|
-
if (!s3Location) {
|
|
231
|
-
// TODO: out: a fallback case - remove after deeper testing
|
|
232
|
-
s3Location = out?.singleUploadResult?.Location;
|
|
233
|
-
debug('Uploaded singleUploadResult.Location', s3Location);
|
|
234
|
-
if (!s3Location) {
|
|
235
|
-
throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return s3Location;
|
|
239
|
-
};
|
|
240
|
-
exports.upload = {
|
|
241
|
-
uploadFileByPath,
|
|
242
|
-
uploadFileAsBuffer,
|
|
243
|
-
isArtifactsEnabled,
|
|
244
|
-
resetConfig,
|
|
245
|
-
};
|