@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.
Files changed (123) hide show
  1. package/lib/adapter/codecept.d.ts +2 -0
  2. package/lib/adapter/codecept.js +31 -26
  3. package/lib/adapter/cucumber/current.d.ts +14 -0
  4. package/lib/adapter/cucumber/legacy.d.ts +0 -0
  5. package/lib/adapter/cucumber.d.ts +2 -0
  6. package/lib/adapter/cypress-plugin/index.d.ts +2 -0
  7. package/lib/adapter/cypress-plugin/index.js +10 -10
  8. package/lib/adapter/jasmine.d.ts +11 -0
  9. package/lib/adapter/jest.d.ts +13 -0
  10. package/lib/adapter/mocha.d.ts +2 -0
  11. package/lib/adapter/mocha.js +4 -4
  12. package/lib/adapter/nightwatch.d.ts +4 -0
  13. package/lib/adapter/nightwatch.js +80 -0
  14. package/lib/adapter/playwright.d.ts +14 -0
  15. package/lib/adapter/playwright.js +58 -33
  16. package/lib/adapter/vitest.d.ts +35 -0
  17. package/lib/adapter/vitest.js +6 -6
  18. package/lib/adapter/webdriver.d.ts +24 -0
  19. package/lib/adapter/webdriver.js +51 -14
  20. package/lib/bin/cli.d.ts +2 -0
  21. package/lib/bin/cli.js +250 -0
  22. package/lib/bin/reportXml.d.ts +2 -0
  23. package/lib/bin/reportXml.js +15 -11
  24. package/lib/bin/startTest.d.ts +2 -0
  25. package/lib/bin/startTest.js +12 -7
  26. package/lib/bin/uploadArtifacts.d.ts +2 -0
  27. package/lib/bin/uploadArtifacts.js +82 -0
  28. package/lib/client.d.ts +76 -0
  29. package/lib/client.js +128 -53
  30. package/lib/config.d.ts +1 -0
  31. package/lib/config.js +2 -2
  32. package/lib/constants.d.ts +25 -0
  33. package/lib/constants.js +5 -1
  34. package/lib/data-storage.d.ts +34 -0
  35. package/lib/data-storage.js +19 -9
  36. package/lib/junit-adapter/adapter.d.ts +9 -0
  37. package/lib/junit-adapter/csharp.d.ts +5 -0
  38. package/lib/junit-adapter/csharp.js +11 -1
  39. package/lib/junit-adapter/index.d.ts +3 -0
  40. package/lib/junit-adapter/java.d.ts +5 -0
  41. package/lib/junit-adapter/javascript.d.ts +4 -0
  42. package/lib/junit-adapter/python.d.ts +5 -0
  43. package/lib/junit-adapter/ruby.d.ts +4 -0
  44. package/lib/output.d.ts +11 -0
  45. package/lib/package.json +3 -1
  46. package/lib/pipe/bitbucket.d.ts +23 -0
  47. package/lib/pipe/bitbucket.js +19 -9
  48. package/lib/pipe/csv.d.ts +47 -0
  49. package/lib/pipe/csv.js +2 -2
  50. package/lib/pipe/debug.d.ts +29 -0
  51. package/lib/pipe/debug.js +108 -0
  52. package/lib/pipe/github.d.ts +30 -0
  53. package/lib/pipe/github.js +37 -5
  54. package/lib/pipe/gitlab.d.ts +23 -0
  55. package/lib/pipe/gitlab.js +2 -3
  56. package/lib/pipe/html.d.ts +35 -0
  57. package/lib/pipe/html.js +9 -4
  58. package/lib/pipe/index.d.ts +1 -0
  59. package/lib/pipe/index.js +20 -10
  60. package/lib/pipe/testomatio.d.ts +70 -0
  61. package/lib/pipe/testomatio.js +54 -39
  62. package/lib/reporter-functions.d.ts +34 -0
  63. package/lib/reporter-functions.js +17 -7
  64. package/lib/reporter.d.ts +232 -0
  65. package/lib/reporter.js +19 -33
  66. package/lib/services/artifacts.d.ts +33 -0
  67. package/lib/services/index.d.ts +9 -0
  68. package/lib/services/key-values.d.ts +27 -0
  69. package/lib/services/key-values.js +1 -1
  70. package/lib/services/logger.d.ts +64 -0
  71. package/lib/services/logger.js +1 -2
  72. package/lib/template/testomatio.hbs +651 -1366
  73. package/lib/uploader.d.ts +60 -0
  74. package/lib/uploader.js +312 -0
  75. package/lib/utils/pipe_utils.d.ts +41 -0
  76. package/lib/utils/pipe_utils.js +3 -5
  77. package/lib/utils/utils.d.ts +47 -0
  78. package/lib/utils/utils.js +99 -12
  79. package/lib/xmlReader.d.ts +92 -0
  80. package/lib/xmlReader.js +64 -25
  81. package/package.json +19 -13
  82. package/src/adapter/codecept.js +30 -26
  83. package/src/adapter/cypress-plugin/index.js +5 -5
  84. package/src/adapter/mocha.cjs +1 -1
  85. package/src/adapter/mocha.js +4 -4
  86. package/src/adapter/nightwatch.js +88 -0
  87. package/src/adapter/playwright.js +59 -31
  88. package/src/adapter/vitest.js +6 -6
  89. package/src/adapter/webdriver.js +42 -12
  90. package/src/bin/cli.js +303 -0
  91. package/src/bin/reportXml.js +19 -9
  92. package/src/bin/startTest.js +9 -4
  93. package/src/bin/uploadArtifacts.js +91 -0
  94. package/src/client.js +137 -57
  95. package/src/config.js +2 -2
  96. package/src/constants.js +5 -1
  97. package/src/data-storage.js +2 -2
  98. package/src/junit-adapter/csharp.js +13 -1
  99. package/src/pipe/bitbucket.js +2 -2
  100. package/src/pipe/csv.js +3 -3
  101. package/src/pipe/debug.js +104 -0
  102. package/src/pipe/github.js +3 -5
  103. package/src/pipe/gitlab.js +6 -7
  104. package/src/pipe/html.js +14 -7
  105. package/src/pipe/index.js +5 -7
  106. package/src/pipe/testomatio.js +75 -76
  107. package/src/reporter-functions.js +18 -7
  108. package/src/reporter.cjs_decprecated +21 -0
  109. package/src/reporter.js +20 -11
  110. package/src/services/key-values.js +1 -1
  111. package/src/services/logger.js +5 -4
  112. package/src/template/testomatio.hbs +651 -1366
  113. package/src/uploader.js +371 -0
  114. package/src/utils/pipe_utils.js +4 -12
  115. package/src/utils/utils.js +64 -15
  116. package/src/xmlReader.js +76 -26
  117. package/lib/adapter/jasmine/jasmine.js +0 -63
  118. package/lib/adapter/mocha/mocha.js +0 -125
  119. package/lib/fileUploader.js +0 -245
  120. package/lib/utils/chalk.js +0 -10
  121. package/src/fileUploader.js +0 -307
  122. package/src/reporter.cjs +0 -22
  123. 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 { upload } from './fileUploader.js';
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, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
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.filesToUpload = {};
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, 5000)}${p3}`);
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: true,
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) files = test.files.map(f => path.join(process.cwd(), f));
387
- files = [...files, ...fetchFilesFromStackTrace(test.stack)];
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 => upload.uploadFileByPath(f, runId)));
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
- return Promise.all(this.pipes.map(p => p.createRun(runParams)));
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
- if (item['test-suite'] && item['test-suite']['test-case']) testCases.push(...item['test-suite']['test-case']);
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
- const testId = fetchIdFromOutput(stack);
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
- const prop = [item.properties?.property].flat().find(p => p.name === 'Description');
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
- [item.properties?.property]
547
- .flat()
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;
@@ -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
- };