@testomatio/reporter 1.6.13 → 2.0.1-beta-esm

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/lib/adapter/codecept.d.ts +2 -0
  2. package/lib/adapter/codecept.js +295 -335
  3. package/lib/adapter/cucumber/current.d.ts +14 -0
  4. package/lib/adapter/cucumber/current.js +195 -203
  5. package/lib/adapter/cucumber/legacy.d.ts +0 -0
  6. package/lib/adapter/cucumber/legacy.js +130 -155
  7. package/lib/adapter/cucumber.d.ts +2 -0
  8. package/lib/adapter/cucumber.js +5 -16
  9. package/lib/adapter/cypress-plugin/index.d.ts +2 -0
  10. package/lib/adapter/cypress-plugin/index.js +93 -105
  11. package/lib/adapter/jasmine.d.ts +11 -0
  12. package/lib/adapter/jasmine.js +54 -53
  13. package/lib/adapter/jest.d.ts +13 -0
  14. package/lib/adapter/jest.js +97 -99
  15. package/lib/adapter/mocha.d.ts +2 -0
  16. package/lib/adapter/mocha.js +112 -140
  17. package/lib/adapter/playwright.d.ts +14 -0
  18. package/lib/adapter/playwright.js +195 -231
  19. package/lib/adapter/vitest.d.ts +35 -0
  20. package/lib/adapter/vitest.js +150 -149
  21. package/lib/adapter/webdriver.d.ts +24 -0
  22. package/lib/adapter/webdriver.js +134 -119
  23. package/lib/bin/cli.d.ts +2 -0
  24. package/lib/bin/cli.js +164 -211
  25. package/lib/bin/reportXml.d.ts +2 -0
  26. package/lib/bin/reportXml.js +49 -52
  27. package/lib/bin/startTest.d.ts +2 -0
  28. package/lib/bin/startTest.js +82 -95
  29. package/lib/bin/uploadArtifacts.d.ts +2 -0
  30. package/lib/bin/uploadArtifacts.js +55 -61
  31. package/lib/client.d.ts +76 -0
  32. package/lib/client.js +411 -465
  33. package/lib/config.d.ts +1 -0
  34. package/lib/config.js +16 -21
  35. package/lib/constants.d.ts +25 -0
  36. package/lib/constants.js +50 -44
  37. package/lib/data-storage.d.ts +34 -0
  38. package/lib/data-storage.js +206 -188
  39. package/lib/junit-adapter/adapter.d.ts +9 -0
  40. package/lib/junit-adapter/adapter.js +17 -20
  41. package/lib/junit-adapter/csharp.d.ts +4 -0
  42. package/lib/junit-adapter/csharp.js +18 -14
  43. package/lib/junit-adapter/index.d.ts +3 -0
  44. package/lib/junit-adapter/index.js +27 -25
  45. package/lib/junit-adapter/java.d.ts +5 -0
  46. package/lib/junit-adapter/java.js +41 -53
  47. package/lib/junit-adapter/javascript.d.ts +4 -0
  48. package/lib/junit-adapter/javascript.js +30 -27
  49. package/lib/junit-adapter/python.d.ts +5 -0
  50. package/lib/junit-adapter/python.js +38 -37
  51. package/lib/junit-adapter/ruby.d.ts +4 -0
  52. package/lib/junit-adapter/ruby.js +11 -8
  53. package/lib/output.d.ts +11 -0
  54. package/lib/output.js +44 -52
  55. package/lib/package.json +3 -0
  56. package/lib/pipe/bitbucket.d.ts +23 -0
  57. package/lib/pipe/bitbucket.js +210 -229
  58. package/lib/pipe/csv.d.ts +47 -0
  59. package/lib/pipe/csv.js +113 -126
  60. package/lib/pipe/debug.d.ts +29 -0
  61. package/lib/pipe/debug.js +104 -99
  62. package/lib/pipe/github.d.ts +30 -0
  63. package/lib/pipe/github.js +186 -213
  64. package/lib/pipe/gitlab.d.ts +23 -0
  65. package/lib/pipe/gitlab.js +166 -207
  66. package/lib/pipe/html.d.ts +34 -0
  67. package/lib/pipe/html.js +260 -319
  68. package/lib/pipe/index.d.ts +1 -0
  69. package/lib/pipe/index.js +84 -66
  70. package/lib/pipe/testomatio.d.ts +70 -0
  71. package/lib/pipe/testomatio.js +413 -462
  72. package/lib/reporter-functions.d.ts +34 -0
  73. package/lib/reporter-functions.js +28 -26
  74. package/lib/reporter.d.ts +232 -0
  75. package/lib/reporter.js +34 -29
  76. package/lib/services/artifacts.d.ts +33 -0
  77. package/lib/services/artifacts.js +55 -51
  78. package/lib/services/index.d.ts +9 -0
  79. package/lib/services/index.js +14 -12
  80. package/lib/services/key-values.d.ts +27 -0
  81. package/lib/services/key-values.js +56 -53
  82. package/lib/services/logger.d.ts +64 -0
  83. package/lib/services/logger.js +227 -245
  84. package/lib/template/testomatio.hbs +651 -1366
  85. package/lib/uploader.d.ts +60 -0
  86. package/lib/uploader.js +291 -360
  87. package/lib/utils/pipe_utils.d.ts +41 -0
  88. package/lib/utils/pipe_utils.js +89 -85
  89. package/lib/utils/utils.d.ts +45 -0
  90. package/lib/utils/utils.js +347 -307
  91. package/lib/xmlReader.d.ts +92 -0
  92. package/lib/xmlReader.js +490 -529
  93. package/package.json +57 -15
  94. package/src/adapter/codecept.js +375 -0
  95. package/src/adapter/cucumber/current.js +228 -0
  96. package/src/adapter/cucumber/legacy.js +158 -0
  97. package/src/adapter/cucumber.js +4 -0
  98. package/src/adapter/cypress-plugin/index.js +112 -0
  99. package/src/adapter/jasmine.js +60 -0
  100. package/src/adapter/jest.js +107 -0
  101. package/src/adapter/mocha.cjs +2 -0
  102. package/src/adapter/mocha.js +157 -0
  103. package/src/adapter/playwright.js +250 -0
  104. package/src/adapter/vitest.js +183 -0
  105. package/src/adapter/webdriver.js +142 -0
  106. package/src/bin/cli.js +280 -0
  107. package/src/bin/reportXml.js +74 -0
  108. package/src/bin/startTest.js +123 -0
  109. package/src/bin/uploadArtifacts.js +90 -0
  110. package/src/client.js +504 -0
  111. package/src/config.js +30 -0
  112. package/src/constants.js +53 -0
  113. package/src/data-storage.js +204 -0
  114. package/src/junit-adapter/adapter.js +23 -0
  115. package/src/junit-adapter/csharp.js +16 -0
  116. package/src/junit-adapter/index.js +28 -0
  117. package/src/junit-adapter/java.js +58 -0
  118. package/src/junit-adapter/javascript.js +31 -0
  119. package/src/junit-adapter/python.js +42 -0
  120. package/src/junit-adapter/ruby.js +10 -0
  121. package/src/output.js +57 -0
  122. package/src/pipe/bitbucket.js +254 -0
  123. package/src/pipe/csv.js +140 -0
  124. package/src/pipe/debug.js +104 -0
  125. package/src/pipe/github.js +233 -0
  126. package/src/pipe/gitlab.js +229 -0
  127. package/src/pipe/html.js +374 -0
  128. package/src/pipe/index.js +71 -0
  129. package/src/pipe/testomatio.js +503 -0
  130. package/src/reporter-functions.js +55 -0
  131. package/src/reporter.cjs_decprecated +21 -0
  132. package/src/reporter.js +33 -0
  133. package/src/services/artifacts.js +59 -0
  134. package/src/services/index.js +13 -0
  135. package/src/services/key-values.js +59 -0
  136. package/src/services/logger.js +316 -0
  137. package/src/template/emptyData.svg +23 -0
  138. package/src/template/testomatio.hbs +706 -0
  139. package/src/uploader.js +371 -0
  140. package/src/utils/pipe_utils.js +119 -0
  141. package/src/utils/utils.js +383 -0
  142. package/src/xmlReader.js +562 -0
package/src/client.js ADDED
@@ -0,0 +1,504 @@
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 { APP_PREFIX, STATUS } from './constants.js';
8
+ import { pipesFactory } from './pipe/index.js';
9
+ import { glob } from 'glob';
10
+ import path, { sep } from 'path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { S3Uploader } from './uploader.js';
13
+ import { formatStep, storeRunId } from './utils/utils.js';
14
+ import { filesize as prettyBytes } from 'filesize';
15
+
16
+ const debug = createDebugMessages('@testomatio/reporter:client');
17
+
18
+ // removed __dirname usage, because:
19
+ // 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
20
+ // 2. got error "__dirname already defined" in compiles js code (cjs dir)
21
+
22
+ let listOfTestFilesToExcludeFromReport = null;
23
+
24
+ /**
25
+ * @typedef {import('../types/types.js').TestData} TestData
26
+ * @typedef {import('../types/types.js').PipeResult} PipeResult
27
+ */
28
+
29
+ class Client {
30
+ /**
31
+ * Create a Testomat client instance
32
+ * @returns
33
+ */
34
+ // eslint-disable-next-line
35
+ constructor(params = {}) {
36
+ this.paramsForPipesFactory = params;
37
+ this.pipeStore = {};
38
+ this.runId = randomUUID(); // will be replaced by real run id
39
+ this.queue = Promise.resolve();
40
+
41
+ // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
42
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
43
+ const pathToPackageJSON = path.join(__dirname, '../package.json');
44
+ try {
45
+ this.version = JSON.parse(fs.readFileSync(pathToPackageJSON).toString()).version;
46
+ console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
47
+ } catch (e) {
48
+ // do nothing
49
+ }
50
+ this.executionList = Promise.resolve();
51
+
52
+ this.uploader = new S3Uploader();
53
+ }
54
+
55
+ /**
56
+ * Asynchronously prepares the execution list for running tests through various pipes.
57
+ * Each pipe in the client is checked for enablement,
58
+ * and if all pipes are disabled, the function returns a resolved Promise.
59
+ * Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
60
+ * The results are then filtered to remove any undefined values.
61
+ * If no valid results are found, the function returns undefined.
62
+ * Otherwise, it returns the first non-empty array from the filtered results.
63
+ *
64
+ * @param {Object} params - The options for preparing the test execution list.
65
+ * @param {string} params.pipe - Name of the executed pipe.
66
+ * @param {string} params.pipeOptions - Filter option.
67
+ * @returns {Promise<any>} - A Promise that resolves to an
68
+ * array containing the prepared execution list,
69
+ * or resolves to undefined if no valid results are found or if all pipes are disabled.
70
+ */
71
+ async prepareRun(params) {
72
+ this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
73
+ const { pipe, pipeOptions } = params;
74
+ // all pipes disabled, skipping
75
+ if (!this.pipes.some(p => p.isEnabled)) {
76
+ return Promise.resolve();
77
+ }
78
+
79
+ try {
80
+ const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
81
+
82
+ if (!filterPipe.isEnabled) {
83
+ // TODO:for the future for the another pipes
84
+ console.warn(
85
+ APP_PREFIX,
86
+ `At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`,
87
+ );
88
+ return;
89
+ }
90
+
91
+ const results = await Promise.all(
92
+ this.pipes.map(async p => ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })),
93
+ );
94
+
95
+ const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
96
+
97
+ if (!result || result.length === 0) {
98
+ return;
99
+ }
100
+
101
+ debug('Execution tests list', result);
102
+
103
+ return result;
104
+ } catch (err) {
105
+ console.error(APP_PREFIX, err);
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Used to create a new Test run
111
+ *
112
+ * @returns {Promise<any>} - resolves to Run id which should be used to update / add test
113
+ */
114
+ async createRun(params) {
115
+ if (!this.pipes || !this.pipes.length)
116
+ this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
117
+ debug('Creating run...');
118
+ // all pipes disabled, skipping
119
+ if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
120
+
121
+ this.queue = this.queue
122
+ .then(() => Promise.all(this.pipes.map(p => p.createRun())))
123
+ .catch(err => console.log(APP_PREFIX, err))
124
+ .then(() => {
125
+ const runId = this.pipeStore?.runId;
126
+ if (runId) this.runId = runId;
127
+ storeRunId(this.runId);
128
+ })
129
+ .then(() => this.uploader.checkEnabled())
130
+ .then(() => undefined); // fixes return type
131
+ // debug('Run', this.queue);
132
+ return this.queue;
133
+ }
134
+
135
+ /**
136
+ * Updates test status and its data
137
+ *
138
+ * @param {string|undefined} status
139
+ * @param {TestData} [testData]
140
+ * @returns {Promise<PipeResult[]>}
141
+ */
142
+ async addTestRun(status, testData) {
143
+ // all pipes disabled, skipping
144
+ if (!this.pipes?.filter(p => p.isEnabled).length) return [];
145
+
146
+ if (isTestShouldBeExculedFromReport(testData)) return [];
147
+
148
+ if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
149
+ debug('Skipping test from report', testData?.title);
150
+ return []; // do not log skipped tests
151
+ }
152
+
153
+ if (!testData)
154
+ testData = {
155
+ title: 'Unknown test',
156
+ suite_title: 'Unknown suite',
157
+ };
158
+
159
+ /**
160
+ * @type {TestData}
161
+ */
162
+ const {
163
+ rid,
164
+ error = null,
165
+ time = 0,
166
+ example = null,
167
+ files = [],
168
+ filesBuffers = [],
169
+ steps,
170
+ code = null,
171
+ title,
172
+ file,
173
+ suite_title,
174
+ suite_id,
175
+ test_id,
176
+ manuallyAttachedArtifacts,
177
+ } = testData;
178
+ let { message = '', meta = {} } = testData;
179
+
180
+ // stringify meta values and limit keys and values length to 255
181
+ meta = Object.entries(meta)
182
+ .filter(([, value]) => value !== null && value !== undefined)
183
+ .map(([key, value]) => {
184
+ try {
185
+ if (typeof value === 'object') {
186
+ value = JSON.stringify(value);
187
+ } else if (typeof value !== 'string') {
188
+ try {
189
+ value = value.toString();
190
+ } catch (err) {
191
+ console.warn(APP_PREFIX, `Can't convert meta value to string`, err);
192
+ }
193
+ }
194
+
195
+ if (value?.length > 255) {
196
+ value = value.substring(0, 255);
197
+ debug(APP_PREFIX, `Meta info value "${value}" is too long, trimmed to 255 characters`);
198
+ }
199
+
200
+ if (key?.length > 255) {
201
+ const newKey = key.substring(0, 255);
202
+ debug(APP_PREFIX, `Meta info key "${key}" is too long, trimmed to 255 characters`);
203
+ return [newKey, value];
204
+ }
205
+
206
+ return [key, value];
207
+ } catch (err) {
208
+ debug(APP_PREFIX, `Error while processing meta info key ${key}`, err);
209
+ return [null, null];
210
+ }
211
+ })
212
+ .reduce((acc, [key, value]) => {
213
+ if (key) acc[key] = value;
214
+ return acc;
215
+ }, {});
216
+
217
+ let errorFormatted = '';
218
+ if (error) {
219
+ errorFormatted += this.formatError(error) || '';
220
+ message = error?.message;
221
+ }
222
+
223
+ // Attach logs
224
+ const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
225
+
226
+ // add artifacts
227
+ if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
228
+
229
+ const uploadedFiles = [];
230
+
231
+ for (let f of files) {
232
+ if (!f) continue; // f === null
233
+ if (typeof f === 'object') {
234
+ if (!f.path) continue;
235
+
236
+ f = f.path;
237
+ }
238
+
239
+ uploadedFiles.push(this.uploader.uploadFileByPath(f, [this.runId, rid, path.basename(f)]));
240
+ }
241
+
242
+ for (const [idx, buffer] of filesBuffers.entries()) {
243
+ const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
244
+ uploadedFiles.push(this.uploader.uploadFileAsBuffer(buffer, [this.runId, rid, fileName]));
245
+ }
246
+
247
+ const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
248
+
249
+ const data = {
250
+ rid,
251
+ files,
252
+ steps,
253
+ status,
254
+ stack: fullLogs,
255
+ example,
256
+ file,
257
+ code,
258
+ title,
259
+ suite_title,
260
+ suite_id,
261
+ test_id,
262
+ message,
263
+ run_time: typeof time === 'number' ? time : parseFloat(time),
264
+ artifacts,
265
+ meta,
266
+ };
267
+
268
+ // debug('Adding test run...', data);
269
+
270
+ // @ts-ignore
271
+ this.queue = this.queue.then(() =>
272
+ Promise.all(
273
+ this.pipes.map(async pipe => {
274
+ try {
275
+ const result = await pipe.addTest(data);
276
+ return { pipe: pipe.toString(), result };
277
+ } catch (err) {
278
+ console.log(APP_PREFIX, pipe.toString(), err);
279
+ }
280
+ }),
281
+ ),
282
+ );
283
+
284
+ // @ts-ignore
285
+ return this.queue;
286
+ }
287
+
288
+ /**
289
+ *
290
+ * Updates the status of the current test run and finishes the run.
291
+ * @param {'passed' | 'failed' | 'skipped' | 'finished'} status - The status of the current test run.
292
+ * Must be one of "passed", "failed", or "finished"
293
+ * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
294
+ * @returns {Promise<any>} - A Promise that resolves when finishes the run.
295
+ */
296
+ updateRunStatus(status, isParallel = false) {
297
+ debug('Updating run status...');
298
+ // all pipes disabled, skipping
299
+ if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
300
+
301
+ const runParams = { status, parallel: isParallel };
302
+
303
+ this.queue = this.queue
304
+ .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
305
+ .then(() => {
306
+ if (!this.uploader.isEnabled) return;
307
+
308
+ const filesizeStrMaxLength = 7;
309
+
310
+ if (this.uploader.successfulUploads.length) {
311
+ debug('\n', APP_PREFIX, `🗄️ ${this.uploader.successfulUploads.length} artifacts uploaded to S3 bucket`);
312
+ const uploadedArtifacts = this.uploader.successfulUploads.map(file => ({
313
+ relativePath: file.path.replace(process.cwd(), ''),
314
+ link: file.link,
315
+ sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
316
+ }));
317
+
318
+ uploadedArtifacts.forEach(upload => {
319
+ debug(
320
+ `🟢Uploaded artifact`,
321
+ `${upload.relativePath},`,
322
+ 'size:',
323
+ `${upload.sizePretty},`,
324
+ 'link:',
325
+ `${upload.link}`,
326
+ );
327
+ });
328
+ }
329
+
330
+ if (this.uploader.failedUploads.length) {
331
+ console.log(
332
+ APP_PREFIX,
333
+ `🗄️ ${this.uploader.failedUploads.length} artifacts 🔴${pc.bold('failed')} to upload`,
334
+ );
335
+ const failedUploads = this.uploader.failedUploads.map(file => ({
336
+ relativePath: file.path.replace(process.cwd(), ''),
337
+ sizePretty: prettyBytes(file.size, { round: 0 }).toString(),
338
+ }));
339
+
340
+ const pathPadding = Math.max(...failedUploads.map(upload => upload.relativePath.length)) + 1;
341
+
342
+ failedUploads.forEach(upload => {
343
+ console.log(
344
+ ` ${pc.gray('|')} 🔴 ${upload.relativePath.padEnd(pathPadding)} ${pc.gray(
345
+ `| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
346
+ )}`,
347
+ );
348
+ });
349
+ }
350
+
351
+ if (this.uploader.skippedUploads.length) {
352
+ console.log(
353
+ '\n',
354
+ APP_PREFIX,
355
+ `🗄️ ${pc.bold(this.uploader.skippedUploads.length)} artifacts uploading 🟡${pc.bold('skipped')}`,
356
+ );
357
+ const skippedUploads = this.uploader.skippedUploads.map(file => ({
358
+ relativePath: file.path.replace(process.cwd(), ''),
359
+ sizePretty: file.size === null ? 'unknown' : prettyBytes(file.size, { round: 0 }).toString(),
360
+ }));
361
+ const pathPadding = Math.max(...skippedUploads.map(upload => upload.relativePath.length)) + 1;
362
+ skippedUploads.forEach(upload => {
363
+ console.log(
364
+ ` ${pc.gray('|')} 🟡 ${upload.relativePath.padEnd(pathPadding)} ${pc.gray(
365
+ `| ${upload.sizePretty.padStart(filesizeStrMaxLength)} |`,
366
+ )}`,
367
+ );
368
+ });
369
+ }
370
+
371
+ if (this.uploader.skippedUploads.length || this.uploader.failedUploads.length) {
372
+ const command = `TESTOMATIO=<your_api_key> TESTOMATIO_RUN=${
373
+ this.runId
374
+ } npx @testomatio/reporter upload-artifacts`;
375
+ const numberOfNotUploadedArtifacts = this.uploader.skippedUploads.length + this.uploader.failedUploads.length;
376
+ console.log(
377
+ APP_PREFIX,
378
+ `${numberOfNotUploadedArtifacts} artifacts were not uploaded.
379
+ Run "${pc.magenta(command)}" with valid S3 credentials to upload skipped & failed artifacts`,
380
+ );
381
+ }
382
+ })
383
+ .catch(err => console.log(APP_PREFIX, err));
384
+
385
+ return this.queue;
386
+ }
387
+
388
+ /**
389
+ * Returns the formatted stack including the stack trace, steps, and logs.
390
+ * @returns {string}
391
+ */
392
+ formatLogs({ error, steps, logs }) {
393
+ error = error?.trim();
394
+ logs = logs?.trim();
395
+
396
+ if (Array.isArray(steps)) {
397
+ steps = steps
398
+ .map(step => formatStep(step))
399
+ .flat()
400
+ .join('\n');
401
+ }
402
+
403
+ let testLogs = '';
404
+ if (steps) testLogs += `${pc.bold(pc.blue('################[ Steps ]################'))}\n${steps}\n\n`;
405
+ if (logs) testLogs += `${pc.bold(pc.gray('################[ Logs ]################'))}\n${logs}\n\n`;
406
+ if (error) testLogs += `${pc.bold(pc.red('################[ Failure ]################'))}\n${error}`;
407
+ return testLogs;
408
+ }
409
+
410
+ formatError(error, message) {
411
+ if (!message) message = error.message;
412
+ if (error.inspect) message = error.inspect() || '';
413
+
414
+ let stack = '';
415
+ if (error.name) stack += `${pc.red(error.name)}`;
416
+ if (error.operator) stack += ` (${pc.red(error.operator)})`;
417
+ // add new line if something was added to stack
418
+ if (stack) stack += ': ';
419
+
420
+ stack += `${message}\n`;
421
+
422
+ if (error.diff) {
423
+ // diff for vitest
424
+ stack += error.diff;
425
+ stack += '\n\n';
426
+ } else if (error.actual && error.expected && error.actual !== error.expected) {
427
+ // diffs for mocha, cypress, codeceptjs style
428
+ stack += `\n\n${pc.bold(pc.green('+ expected'))} ${pc.bold(pc.red('- actual'))}`;
429
+ stack += `\n${pc.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
430
+ stack += `\n${pc.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
431
+ stack += '\n\n';
432
+ }
433
+
434
+ const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
435
+
436
+ try {
437
+ let hasFrame = false;
438
+ const record = createCallsiteRecord({
439
+ forError: error,
440
+ isCallsiteFrame: frame => {
441
+ if (customFilter && minimatch(frame.fileName, customFilter)) return false;
442
+ if (hasFrame) return false;
443
+ if (isNotInternalFrame(frame)) hasFrame = true;
444
+ return hasFrame;
445
+ },
446
+ });
447
+ // @ts-ignore
448
+ if (record && !record.filename.startsWith('http')) {
449
+ stack += record.renderSync({ stackFilter: isNotInternalFrame });
450
+ }
451
+ return stack;
452
+ } catch (e) {
453
+ console.log(e);
454
+ }
455
+ }
456
+ }
457
+
458
+ function isNotInternalFrame(frame) {
459
+ return (
460
+ frame.getFileName() &&
461
+ frame.getFileName().includes(sep) &&
462
+ !frame.getFileName().includes('node_modules') &&
463
+ !frame.getFileName().includes('internal')
464
+ );
465
+ }
466
+
467
+ /**
468
+ *
469
+ * @param {TestData} testData
470
+ * @returns boolean
471
+ */
472
+ function isTestShouldBeExculedFromReport(testData) {
473
+ // const fileName = path.basename(test.location?.file || '');
474
+ const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
475
+ if (!globExcludeFilesPattern) return false;
476
+
477
+ if (!testData.file) {
478
+ debug('No "file" property found for test ', testData.title);
479
+ return false;
480
+ }
481
+
482
+ const excludeParretnsList = globExcludeFilesPattern.split(';');
483
+
484
+ // as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
485
+ if (!listOfTestFilesToExcludeFromReport) {
486
+ // list of files with relative paths
487
+ listOfTestFilesToExcludeFromReport = glob.sync(excludeParretnsList, { ignore: '**/node_modules/**' });
488
+ debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
489
+ }
490
+
491
+ const testFileRelativePath = path.relative(process.cwd(), testData.file);
492
+
493
+ // no files found matching the exclusion pattern
494
+ if (!listOfTestFilesToExcludeFromReport.length) return false;
495
+
496
+ if (listOfTestFilesToExcludeFromReport.includes(testFileRelativePath)) {
497
+ debug(`Excluding test '${testData.title}' <${testFileRelativePath}> from reporting`);
498
+ return true;
499
+ }
500
+ return false;
501
+ }
502
+
503
+ export { Client };
504
+ 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,53 @@
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 TESTOMATIO_REQUEST_TIMEOUT = parseInt(process.env.TESTOMATIO_REQUEST_TIMEOUT, 10);
7
+ if (TESTOMATIO_REQUEST_TIMEOUT) {
8
+ console.log(`${APP_PREFIX} Request timeout is set to ${TESTOMATIO_REQUEST_TIMEOUT / 1000}s`);
9
+ }
10
+ const AXIOS_TIMEOUT = TESTOMATIO_REQUEST_TIMEOUT || 20 * 1000;
11
+
12
+ const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
13
+
14
+ const CSV_HEADERS = [
15
+ { id: 'suite_title', title: 'Suite_title' },
16
+ { id: 'title', title: 'Title' },
17
+ { id: 'status', title: 'Status' },
18
+ { id: 'message', title: 'Message' },
19
+ { id: 'stack', title: 'Stack' },
20
+ ];
21
+
22
+ const STATUS = {
23
+ PASSED: 'passed',
24
+ FAILED: 'failed',
25
+ SKIPPED: 'skipped',
26
+ FINISHED: 'finished',
27
+ };
28
+ // html pipe var
29
+ const HTML_REPORT = {
30
+ FOLDER: 'html-report',
31
+ REPORT_DEFAULT_NAME: 'testomatio-report.html',
32
+ TEMPLATE_NAME: 'testomatio.hbs',
33
+ };
34
+
35
+ const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
36
+
37
+ const REPORTER_REQUEST_RETRIES = {
38
+ retryTimeout: 5 * 1000, // sum = 5sec
39
+ retriesPerRequest: 2,
40
+ maxTotalRetries: Number(process.env.TESTOMATIO_MAX_REQUEST_FAILURES_COUNT) || 10,
41
+ withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
42
+ };
43
+
44
+ export {
45
+ APP_PREFIX,
46
+ TESTOMAT_TMP_STORAGE_DIR,
47
+ CSV_HEADERS,
48
+ STATUS,
49
+ HTML_REPORT,
50
+ AXIOS_TIMEOUT,
51
+ testomatLogoURL,
52
+ REPORTER_REQUEST_RETRIES,
53
+ };