@testomatio/reporter 1.6.0-beta-2-artifacts → 2.0.0-beta-esm

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/lib/adapter/codecept.js +288 -330
  2. package/lib/adapter/cucumber/current.js +195 -203
  3. package/lib/adapter/cucumber/legacy.js +130 -155
  4. package/lib/adapter/cucumber.js +5 -16
  5. package/lib/adapter/cypress-plugin/index.js +91 -105
  6. package/lib/adapter/jasmine/jasmine.js +63 -0
  7. package/lib/adapter/jasmine.js +54 -53
  8. package/lib/adapter/jest.js +97 -99
  9. package/lib/adapter/mocha/mocha.js +125 -0
  10. package/lib/adapter/mocha.js +111 -140
  11. package/lib/adapter/playwright.js +168 -200
  12. package/lib/adapter/vitest.js +144 -143
  13. package/lib/adapter/webdriver.js +113 -97
  14. package/lib/bin/reportXml.js +49 -49
  15. package/lib/bin/startTest.js +80 -97
  16. package/lib/client.js +344 -385
  17. package/lib/config.js +16 -21
  18. package/lib/constants.js +49 -43
  19. package/lib/data-storage.js +206 -188
  20. package/lib/fileUploader.js +245 -0
  21. package/lib/junit-adapter/adapter.js +17 -20
  22. package/lib/junit-adapter/csharp.js +18 -14
  23. package/lib/junit-adapter/index.js +27 -25
  24. package/lib/junit-adapter/java.js +41 -53
  25. package/lib/junit-adapter/javascript.js +30 -27
  26. package/lib/junit-adapter/python.js +38 -37
  27. package/lib/junit-adapter/ruby.js +11 -8
  28. package/lib/output.js +44 -52
  29. package/lib/package.json +1 -0
  30. package/lib/pipe/bitbucket.js +208 -227
  31. package/lib/pipe/csv.js +111 -124
  32. package/lib/pipe/github.js +184 -211
  33. package/lib/pipe/gitlab.js +164 -205
  34. package/lib/pipe/html.js +253 -312
  35. package/lib/pipe/index.js +83 -63
  36. package/lib/pipe/testomatio.js +391 -454
  37. package/lib/reporter-functions.js +16 -20
  38. package/lib/reporter.js +47 -17
  39. package/lib/services/artifacts.js +55 -51
  40. package/lib/services/index.js +14 -12
  41. package/lib/services/key-values.js +56 -53
  42. package/lib/services/logger.js +227 -245
  43. package/lib/utils/chalk.js +10 -0
  44. package/lib/utils/pipe_utils.js +91 -84
  45. package/lib/utils/utils.js +289 -273
  46. package/lib/xmlReader.js +480 -519
  47. package/package.json +57 -19
  48. package/src/adapter/codecept.js +369 -0
  49. package/src/adapter/cucumber/current.js +228 -0
  50. package/src/adapter/cucumber/legacy.js +158 -0
  51. package/src/adapter/cucumber.js +4 -0
  52. package/src/adapter/cypress-plugin/index.js +110 -0
  53. package/src/adapter/jasmine.js +60 -0
  54. package/src/adapter/jest.js +107 -0
  55. package/src/adapter/mocha.cjs +2 -0
  56. package/src/adapter/mocha.js +156 -0
  57. package/src/adapter/playwright.js +222 -0
  58. package/src/adapter/vitest.js +183 -0
  59. package/src/adapter/webdriver.js +111 -0
  60. package/src/bin/reportXml.js +67 -0
  61. package/src/bin/startTest.js +119 -0
  62. package/src/client.js +423 -0
  63. package/src/config.js +30 -0
  64. package/src/constants.js +49 -0
  65. package/src/data-storage.js +204 -0
  66. package/src/fileUploader.js +307 -0
  67. package/src/junit-adapter/adapter.js +23 -0
  68. package/src/junit-adapter/csharp.js +16 -0
  69. package/src/junit-adapter/index.js +28 -0
  70. package/src/junit-adapter/java.js +58 -0
  71. package/src/junit-adapter/javascript.js +31 -0
  72. package/src/junit-adapter/python.js +42 -0
  73. package/src/junit-adapter/ruby.js +10 -0
  74. package/src/output.js +57 -0
  75. package/src/pipe/bitbucket.js +254 -0
  76. package/src/pipe/csv.js +140 -0
  77. package/src/pipe/github.js +234 -0
  78. package/src/pipe/gitlab.js +229 -0
  79. package/src/pipe/html.js +366 -0
  80. package/src/pipe/index.js +73 -0
  81. package/src/pipe/testomatio.js +498 -0
  82. package/src/reporter-functions.js +44 -0
  83. package/src/reporter.cjs +22 -0
  84. package/src/reporter.js +24 -0
  85. package/src/services/artifacts.js +59 -0
  86. package/src/services/index.js +13 -0
  87. package/src/services/key-values.js +59 -0
  88. package/src/services/logger.js +314 -0
  89. package/src/template/emptyData.svg +23 -0
  90. package/src/template/testomatio.hbs +1421 -0
  91. package/src/utils/chalk.js +13 -0
  92. package/src/utils/pipe_utils.js +127 -0
  93. package/src/utils/utils.js +341 -0
  94. package/src/xmlReader.js +551 -0
  95. package/lib/bin/cli.js +0 -216
  96. package/lib/bin/uploadArtifacts.js +0 -86
  97. package/lib/uploader.js +0 -312
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ import program from 'commander';
3
+ import pc from 'picocolors';
4
+ import glob from 'glob';
5
+ import createDebugMessages from 'debug';
6
+ import { APP_PREFIX } from '../constants.js';
7
+ import XmlReader from '../xmlReader.js';
8
+ import { version } from '../../package.json';
9
+
10
+ const debug = createDebugMessages('@testomatio/reporter:xml-cli');
11
+ console.log(pc.cyan(pc.bold(` 🤩 Testomat.io XML Reporter v${version}`)));
12
+
13
+ program
14
+ .arguments('<pattern>')
15
+ .option('-d, --dir <dir>', 'Project directory')
16
+ .option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
17
+ .option('--lang <lang>', 'Language used (python, ruby, java)')
18
+ .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
19
+ .option('--env-file <envfile>', 'Load environment variables from env file')
20
+ .action(async (pattern, opts) => {
21
+ if (!pattern.endsWith('.xml')) {
22
+ pattern += '.xml';
23
+ }
24
+ let { javaTests, lang } = opts;
25
+ if (opts.envFile) {
26
+ console.log(APP_PREFIX, 'Loading env file:', opts.envFile);
27
+ debug('Loading env file: %s', opts.envFile);
28
+ require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
29
+ }
30
+ if (javaTests === true) javaTests = 'src/test/java';
31
+ lang = lang?.toLowerCase();
32
+ const runReader = new XmlReader({ javaTests, lang });
33
+ const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
34
+ if (!files.length) {
35
+ console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+
40
+ for (const file of files) {
41
+ console.log(APP_PREFIX, `Parsed ${file}`);
42
+ runReader.parse(file);
43
+ }
44
+
45
+ let timeoutTimer;
46
+ if (opts.timelimit) {
47
+ timeoutTimer = setTimeout(() => {
48
+ console.log(`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
49
+ process.exit(0);
50
+ }, parseInt(opts.timelimit, 10) * 1000);
51
+ }
52
+
53
+ try {
54
+ await runReader.createRun();
55
+ await runReader.uploadData();
56
+ } catch (err) {
57
+ console.log(APP_PREFIX, 'Error updating status, skipping...', err);
58
+ }
59
+
60
+ if (timeoutTimer) clearTimeout(timeoutTimer);
61
+ });
62
+
63
+ if (process.argv.length < 3) {
64
+ program.outputHelp();
65
+ }
66
+
67
+ program.parse(process.argv);
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'cross-spawn';
3
+ import program from 'commander';
4
+ import pc from 'picocolors';
5
+ import TestomatClient from '../client.js';
6
+ import { APP_PREFIX, STATUS } from '../constants.js';
7
+ import { version } from '../../package.json';
8
+ import {config} from '../config.js';
9
+
10
+ console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
11
+
12
+ program
13
+ .option('-c, --command <cmd>', 'Test runner command')
14
+ .option('--launch', 'Start a new run and return its ID')
15
+ .option('--finish', 'Finish Run by its ID')
16
+ .option('--env-file <envfile>', 'Load environment variables from env file')
17
+ .option('--filter <filter>', 'Additional execution filter')
18
+ .action(async opts => {
19
+ const { launch, finish, filter } = opts;
20
+ let { command } = opts;
21
+
22
+ if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
23
+
24
+ const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
25
+ const title = process.env.TESTOMATIO_TITLE;
26
+
27
+ if (launch) {
28
+ console.log('Starting a new Run on Testomat.io...');
29
+ const client = new TestomatClient({ apiKey });
30
+
31
+ client.createRun().then(() => {
32
+ console.log(process.env.runId);
33
+ process.exit(0);
34
+ });
35
+ return;
36
+ }
37
+
38
+ if (finish) {
39
+ if (!process.env.TESTOMATIO_RUN) {
40
+ console.log('TESTOMATIO_RUN environment variable must be set.');
41
+ return process.exit(1);
42
+ }
43
+
44
+ console.log('Finishing Run on Testomat.io...');
45
+
46
+ const client = new TestomatClient({ apiKey });
47
+
48
+ // @ts-ignore
49
+ client.updateRunStatus(STATUS.FINISHED).then(() => {
50
+ console.log(pc.yellow(`Run ${process.env.TESTOMATIO_RUN} was finished`));
51
+ process.exit(0);
52
+ });
53
+ return;
54
+ }
55
+
56
+ let exitCode = 0;
57
+
58
+ if (!command.split) {
59
+ process.exitCode = 255;
60
+ console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
61
+ return;
62
+ }
63
+
64
+ const client = new TestomatClient({ apiKey, title, parallel: true });
65
+
66
+ if (filter) {
67
+ const [pipe, ...optsArray] = filter.split(':');
68
+ const pipeOptions = optsArray.join(':');
69
+
70
+ try {
71
+ const tests = await client.prepareRun({ pipe, pipeOptions });
72
+
73
+ if (!tests || tests.length === 0) {
74
+ return;
75
+ }
76
+
77
+ const grep = ` --grep (${tests.join('|')})`;
78
+ command += grep;
79
+ } catch (err) {
80
+ console.log(APP_PREFIX, err);
81
+ }
82
+ }
83
+
84
+ const testCmds = command.split(' ');
85
+ console.log(APP_PREFIX, `🚀 Running`, pc.green(command));
86
+
87
+ if (!apiKey) {
88
+ const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
89
+
90
+ cmd.on('close', code => {
91
+ console.log(APP_PREFIX, '⚠️ ', `Runner exited with ${pc.bold(code)}, report is ignored`);
92
+
93
+ if (code > exitCode) exitCode = code;
94
+ process.exitCode = exitCode;
95
+ });
96
+
97
+ return;
98
+ }
99
+
100
+ client.createRun().then(() => {
101
+ const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
102
+
103
+ cmd.on('close', code => {
104
+ const emoji = code === 0 ? '🟢' : '🔴';
105
+ console.log(APP_PREFIX, emoji, `Runner exited with ${pc.bold(code)}`);
106
+ const status = code === 0 ? 'passed' : 'failed';
107
+ client.updateRunStatus(status, true);
108
+
109
+ if (code > exitCode) exitCode = code;
110
+ process.exitCode = exitCode;
111
+ });
112
+ });
113
+ });
114
+
115
+ if (process.argv.length <= 2) {
116
+ program.outputHelp();
117
+ }
118
+
119
+ program.parse(process.argv);
package/src/client.js ADDED
@@ -0,0 +1,423 @@
1
+ import createDebugMessages from 'debug';
2
+ import createCallsiteRecord from 'callsite-record';
3
+ import { minimatch } from 'minimatch';
4
+ import fs from 'fs';
5
+ import pc from 'picocolors';
6
+ import { randomUUID } from 'crypto';
7
+ import {upload} from './fileUploader.js';
8
+ import { APP_PREFIX, STATUS } from './constants.js';
9
+ import { pipesFactory } from './pipe/index.js';
10
+ import { glob } from 'glob';
11
+ import path, { sep} from 'path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const debug = createDebugMessages('@testomatio/reporter:client');
15
+
16
+ // removed __dirname usage, because:
17
+ // 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
18
+ // 2. got error "__dirname already defined" in compiles js code (cjs dir)
19
+
20
+ let listOfTestFilesToExcludeFromReport = null;
21
+
22
+ /**
23
+ * @typedef {import('../types').TestData} TestData
24
+ * @typedef {import('../types').PipeResult} PipeResult
25
+ */
26
+
27
+ class Client {
28
+ /**
29
+ * Create a Testomat client instance
30
+ * @returns
31
+ */
32
+ // eslint-disable-next-line
33
+ constructor(params = {}) {
34
+ this.uuid = randomUUID();
35
+ this.queue = Promise.resolve();
36
+ this.totalUploaded = 0;
37
+ this.failedToUpload = 0;
38
+
39
+ // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
40
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
41
+ const pathToPackageJSON = path.join(__dirname, '../package.json');
42
+ try {
43
+ this.version = JSON.parse(fs.readFileSync(pathToPackageJSON).toString()).version;
44
+ console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
45
+ } catch (e) {
46
+ // do nothing
47
+ }
48
+ this.executionList = Promise.resolve();
49
+
50
+ }
51
+
52
+ /**
53
+ * Asynchronously prepares the execution list for running tests through various pipes.
54
+ * Each pipe in the client is checked for enablement,
55
+ * and if all pipes are disabled, the function returns a resolved Promise.
56
+ * Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
57
+ * The results are then filtered to remove any undefined values.
58
+ * If no valid results are found, the function returns undefined.
59
+ * Otherwise, it returns the first non-empty array from the filtered results.
60
+ *
61
+ * @param {Object} params - The options for preparing the test execution list.
62
+ * @param {string} params.pipe - Name of the executed pipe.
63
+ * @param {string} params.pipeOptions - Filter option.
64
+ * @returns {Promise<any>} - A Promise that resolves to an
65
+ * array containing the prepared execution list,
66
+ * or resolves to undefined if no valid results are found or if all pipes are disabled.
67
+ */
68
+ async prepareRun(params) {
69
+ const store = {};
70
+ this.pipes = await pipesFactory(params, store);
71
+ const { pipe, pipeOptions } = params;
72
+ // all pipes disabled, skipping
73
+ if (!this.pipes.some(p => p.isEnabled)) {
74
+ return Promise.resolve();
75
+ }
76
+
77
+ try {
78
+ const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
79
+
80
+ if (!filterPipe.isEnabled) {
81
+ // TODO:for the future for the another pipes
82
+ console.warn(
83
+ APP_PREFIX,
84
+ `At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`,
85
+ );
86
+ return;
87
+ }
88
+
89
+ const results = await Promise.all(
90
+ this.pipes.map(async p => ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })),
91
+ );
92
+
93
+ const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
94
+
95
+ if (!result || result.length === 0) {
96
+ return;
97
+ }
98
+
99
+ debug('Execution tests list', result);
100
+
101
+ return result;
102
+ } catch (err) {
103
+ console.error(APP_PREFIX, err);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Used to create a new Test run
109
+ *
110
+ * @returns {Promise<any>} - resolves to Run id which should be used to update / add test
111
+ */
112
+ async createRun(params) {
113
+ if (!this.pipes || !this.pipes.length) this.pipes = await pipesFactory(params || {}, {});
114
+ debug('Creating run...');
115
+ // all pipes disabled, skipping
116
+ if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
117
+
118
+ this.queue = this.queue
119
+ .then(() => Promise.all(this.pipes.map(p => p.createRun())))
120
+ .catch(err => console.log(APP_PREFIX, err))
121
+ .then(() => undefined); // fixes return type
122
+ // debug('Run', this.queue);
123
+ return this.queue;
124
+ }
125
+
126
+ /**
127
+ * Updates test status and its data
128
+ *
129
+ * @param {string|undefined} status
130
+ * @param {TestData} [testData]
131
+ * @returns {Promise<PipeResult[]>}
132
+ */
133
+ async addTestRun(status, testData) {
134
+ // all pipes disabled, skipping
135
+ if (!this.pipes?.filter(p => p.isEnabled).length) return [];
136
+
137
+ if (isTestShouldBeExculedFromReport(testData)) return [];
138
+
139
+ if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
140
+ debug('Skipping test from report', testData?.title);
141
+ return []; // do not log skipped tests
142
+ }
143
+
144
+ if (!testData)
145
+ testData = {
146
+ title: 'Unknown test',
147
+ suite_title: 'Unknown suite',
148
+ };
149
+
150
+ /**
151
+ * @type {TestData}
152
+ */
153
+ const {
154
+ rid,
155
+ error = null,
156
+ time = 0,
157
+ example = null,
158
+ files = [],
159
+ filesBuffers = [],
160
+ steps,
161
+ code = null,
162
+ title,
163
+ file,
164
+ suite_title,
165
+ suite_id,
166
+ test_id,
167
+ manuallyAttachedArtifacts,
168
+ meta,
169
+ } = testData;
170
+ let { message = '' } = testData;
171
+
172
+ let errorFormatted = '';
173
+ if (error) {
174
+ errorFormatted += this.formatError(error) || '';
175
+ message = error?.message;
176
+ }
177
+
178
+ // Attach logs
179
+ const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
180
+
181
+ // add artifacts
182
+ if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
183
+
184
+ const uploadedFiles = [];
185
+
186
+ for (const f of files) {
187
+ uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
188
+ }
189
+
190
+ for (const [idx, buffer] of filesBuffers.entries()) {
191
+ const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
192
+ uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
193
+ }
194
+
195
+ const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
196
+
197
+ if (artifacts.length < uploadedFiles.length) {
198
+ const failedUploading = uploadedFiles.length - artifacts.length;
199
+ this.failedToUpload += failedUploading;
200
+ }
201
+
202
+ this.totalUploaded += artifacts.length;
203
+
204
+ const data = {
205
+ rid,
206
+ files,
207
+ steps,
208
+ status,
209
+ stack: fullLogs,
210
+ example,
211
+ file,
212
+ code,
213
+ title,
214
+ suite_title,
215
+ suite_id,
216
+ test_id,
217
+ message,
218
+ run_time: typeof time === 'number' ? time : parseFloat(time),
219
+ artifacts,
220
+ meta,
221
+ };
222
+
223
+ // debug('Adding test run...', data);
224
+
225
+ // @ts-ignore
226
+ this.queue = this.queue.then(() =>
227
+ Promise.all(
228
+ this.pipes.map(async pipe => {
229
+ try {
230
+ const result = await pipe.addTest(data);
231
+ return { pipe: pipe.toString(), result };
232
+ } catch (err) {
233
+ console.log(APP_PREFIX, pipe.toString(), err);
234
+ }
235
+ }),
236
+ ),
237
+ );
238
+
239
+ // @ts-ignore
240
+ return this.queue;
241
+ }
242
+
243
+ /**
244
+ *
245
+ * Updates the status of the current test run and finishes the run.
246
+ * @param {'passed' | 'failed' | 'skipped' | 'finished'} status - The status of the current test run.
247
+ * Must be one of "passed", "failed", or "finished"
248
+ * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
249
+ * @returns {Promise<any>} - A Promise that resolves when finishes the run.
250
+ */
251
+ updateRunStatus(status, isParallel = false) {
252
+ debug('Updating run status...');
253
+ // all pipes disabled, skipping
254
+ if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
255
+
256
+ const runParams = { status, parallel: isParallel };
257
+
258
+ this.queue = this.queue
259
+ .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
260
+ .then(() => {
261
+ debug('TOTAL artifacts', this.totalUploaded);
262
+ if (this.totalUploaded && !upload.isArtifactsEnabled())
263
+ debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
264
+
265
+ if (this.totalUploaded && upload.isArtifactsEnabled()) {
266
+ console.log(
267
+ APP_PREFIX,
268
+ `🗄️ ${this.totalUploaded} artifacts ${
269
+ process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : pc.bold('publicly')
270
+ } uploaded to S3 bucket`,
271
+ );
272
+
273
+ if (this.failedToUpload > 0) {
274
+ console.log(
275
+ APP_PREFIX,
276
+ pc.yellow(
277
+ `Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded.
278
+ Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
279
+ ),
280
+ );
281
+ }
282
+ }
283
+ })
284
+ .catch(err => console.log(APP_PREFIX, err));
285
+
286
+ return this.queue;
287
+ }
288
+
289
+ /**
290
+ * Returns the formatted stack including the stack trace, steps, and logs.
291
+ * @returns {string}
292
+ */
293
+ formatLogs({ error, steps, logs }) {
294
+ error = error?.trim();
295
+ logs = logs?.trim();
296
+
297
+ if (Array.isArray(steps)) {
298
+ steps = steps
299
+ .map(step => formatStep(step))
300
+ .flat()
301
+ .join('\n');
302
+ }
303
+
304
+ let testLogs = '';
305
+ if (steps) testLogs += `${pc.bold(pc.blue('################[ Steps ]################'))}\n${steps}\n\n`;
306
+ if (logs) testLogs += `${pc.bold(pc.gray('################[ Logs ]################'))}\n${logs}\n\n`;
307
+ if (error) testLogs += `${pc.bold(pc.red('################[ Failure ]################'))}\n${error}`;
308
+ return testLogs;
309
+ }
310
+
311
+ formatError(error, message) {
312
+ if (!message) message = error.message;
313
+ if (error.inspect) message = error.inspect() || '';
314
+
315
+ let stack = '';
316
+ if (error.name) stack += `${pc.red(error.name)}`;
317
+ if (error.operator) stack += ` (${pc.red(error.operator)})`;
318
+ // add new line if something was added to stack
319
+ if (stack) stack += ': ';
320
+
321
+ stack += `${message}\n`;
322
+
323
+ if (error.diff) {
324
+ // diff for vitest
325
+ stack += error.diff;
326
+ stack += '\n\n';
327
+ } else if (error.actual && error.expected && error.actual !== error.expected) {
328
+ // diffs for mocha, cypress, codeceptjs style
329
+ stack += `\n\n${pc.bold(pc.green('+ expected'))} ${pc.bold(pc.red('- actual'))}`;
330
+ stack += `\n${pc.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
331
+ stack += `\n${pc.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
332
+ stack += '\n\n';
333
+ }
334
+
335
+ const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
336
+
337
+ try {
338
+ let hasFrame = false;
339
+ const record = createCallsiteRecord({
340
+ forError: error,
341
+ isCallsiteFrame: frame => {
342
+ if (customFilter && minimatch(frame.fileName, customFilter)) return false;
343
+ if (hasFrame) return false;
344
+ if (isNotInternalFrame(frame)) hasFrame = true;
345
+ return hasFrame;
346
+ },
347
+ });
348
+ // @ts-ignore
349
+ if (record && !record.filename.startsWith('http')) {
350
+ stack += record.renderSync({ stackFilter: isNotInternalFrame });
351
+ }
352
+ return stack;
353
+ } catch (e) {
354
+ console.log(e);
355
+ }
356
+ }
357
+ }
358
+
359
+ function isNotInternalFrame(frame) {
360
+ return (
361
+ frame.getFileName() &&
362
+ frame.getFileName().includes(sep) &&
363
+ !frame.getFileName().includes('node_modules') &&
364
+ !frame.getFileName().includes('internal')
365
+ );
366
+ }
367
+
368
+ function formatStep(step, shift = 0) {
369
+ const prefix = ' '.repeat(shift);
370
+
371
+ const lines = [];
372
+
373
+ if (step.error) {
374
+ lines.push(`${prefix}${pc.red(step.title)} ${pc.gray(`${step.duration}ms`)}`);
375
+ } else {
376
+ lines.push(`${prefix}${step.title} ${pc.gray(`${step.duration}ms`)}`);
377
+ }
378
+
379
+ for (const child of step.steps || []) {
380
+ lines.push(...formatStep(child, shift + 2));
381
+ }
382
+
383
+ return lines;
384
+ }
385
+
386
+ /**
387
+ *
388
+ * @param {TestData} testData
389
+ * @returns boolean
390
+ */
391
+ function isTestShouldBeExculedFromReport(testData) {
392
+ // const fileName = path.basename(test.location?.file || '');
393
+ const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
394
+ if (!globExcludeFilesPattern) return false;
395
+
396
+ if (!testData.file) {
397
+ debug('No "file" property found for test ', testData.title);
398
+ return false;
399
+ }
400
+
401
+ const excludeParretnsList = globExcludeFilesPattern.split(';');
402
+
403
+ // as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
404
+ if (!listOfTestFilesToExcludeFromReport) {
405
+ // list of files with relative paths
406
+ listOfTestFilesToExcludeFromReport = glob.sync(excludeParretnsList, { ignore: '**/node_modules/**' });
407
+ debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
408
+ }
409
+
410
+ const testFileRelativePath = path.relative(process.cwd(), testData.file);
411
+
412
+ // no files found matching the exclusion pattern
413
+ if (!listOfTestFilesToExcludeFromReport.length) return false;
414
+
415
+ if (listOfTestFilesToExcludeFromReport.includes(testFileRelativePath)) {
416
+ debug(`Excluding test '${testData.title}' <${testFileRelativePath}> from reporting`);
417
+ return true;
418
+ }
419
+ return false;
420
+ }
421
+
422
+ export { Client };
423
+ export default Client;
package/src/config.js ADDED
@@ -0,0 +1,30 @@
1
+ // This file is used to read environment variables from .env file
2
+ import createDebugMessages from 'debug';
3
+
4
+ const debug = createDebugMessages('@testomatio/reporter:config');
5
+
6
+ /* for possibility to use multiple env files (reading different paths)
7
+ const envFileVars = dotenv.config({ path: '.env' }).parsed; */
8
+
9
+ if (process.env.TESTOMATIO_API_KEY) {
10
+ process.env.TESTOMATIO = process.env.TESTOMATIO_API_KEY;
11
+ }
12
+ if (process.env.TESTOMATIO_TOKEN) {
13
+ process.env.TESTOMATIO = process.env.TESTOMATIO_TOKEN;
14
+ }
15
+
16
+ if (process.env.TESTOMATIO === 'undefined')
17
+ console.error('TESTOMATIO is "undefined". Something went wrong. Contact dev team.');
18
+
19
+ // select only TESTOMATIO related variables (only to print them in debug)
20
+ const testomatioEnvVars =
21
+ Object.keys(process.env)
22
+ .filter(key => key.startsWith('TESTOMATIO') || key.startsWith('S3_'))
23
+ .reduce((obj, key) => {
24
+ obj[key] = process.env[key];
25
+ return obj;
26
+ }, {}) || {};
27
+ debug('TESTOMATIO variables:', testomatioEnvVars);
28
+
29
+ // includes variables from .env file and process.env
30
+ export const config = process.env;
@@ -0,0 +1,49 @@
1
+ import pc from 'picocolors';
2
+ import os from 'os';
3
+ import path from 'path';
4
+
5
+ const APP_PREFIX = pc.gray('[TESTOMATIO]');
6
+ const AXIOS_TIMEOUT = 20 * 1000; // sum = 20sec
7
+
8
+ const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
9
+
10
+ const CSV_HEADERS = [
11
+ { id: 'suite_title', title: 'Suite_title' },
12
+ { id: 'title', title: 'Title' },
13
+ { id: 'status', title: 'Status' },
14
+ { id: 'message', title: 'Message' },
15
+ { id: 'stack', title: 'Stack' },
16
+ ];
17
+
18
+ const STATUS = {
19
+ PASSED: 'passed',
20
+ FAILED: 'failed',
21
+ SKIPPED: 'skipped',
22
+ FINISHED: 'finished',
23
+ };
24
+ // html pipe var
25
+ const HTML_REPORT = {
26
+ FOLDER: 'html-report',
27
+ REPORT_DEFAULT_NAME: 'testomatio-report.html',
28
+ TEMPLATE_NAME: 'testomatio.hbs',
29
+ };
30
+
31
+ const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
32
+
33
+ const REPORTER_REQUEST_RETRIES = {
34
+ retryTimeout: 5 * 1000, // sum = 5sec
35
+ retriesPerRequest: 2,
36
+ maxTotalRetries: Number(process.env.TESTOMATIO_MAX_REQUEST_FAILURES_COUNT) || 10,
37
+ withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
38
+ };
39
+
40
+ export {
41
+ APP_PREFIX,
42
+ TESTOMAT_TMP_STORAGE_DIR,
43
+ CSV_HEADERS,
44
+ STATUS,
45
+ HTML_REPORT,
46
+ AXIOS_TIMEOUT,
47
+ testomatLogoURL,
48
+ REPORTER_REQUEST_RETRIES,
49
+ };