@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
package/lib/client.js CHANGED
@@ -1,418 +1,377 @@
1
- const debug = require('debug')('@testomatio/reporter:client');
2
- const createCallsiteRecord = require('callsite-record');
3
- const { sep, join } = require('path');
4
- const { minimatch } = require('minimatch');
5
- const fs = require('fs');
6
- const chalk = require('chalk');
7
- const { randomUUID } = require('crypto');
8
- const S3Uploader = require('./uploader');
9
- const { APP_PREFIX, STATUS } = require('./constants');
10
- const pipesFactory = require('./pipe');
11
- const { glob } = require('glob');
12
- const path = require('path');
13
-
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.Client = void 0;
30
+ const debug_1 = __importDefault(require("debug"));
31
+ const callsite_record_1 = __importDefault(require("callsite-record"));
32
+ const minimatch_1 = require("minimatch");
33
+ const fs_1 = __importDefault(require("fs"));
34
+ const picocolors_1 = __importDefault(require("picocolors"));
35
+ const crypto_1 = require("crypto");
36
+ const fileUploader_js_1 = require("./fileUploader.js");
37
+ const constants_js_1 = require("./constants.js");
38
+ const index_js_1 = require("./pipe/index.js");
39
+ const glob_1 = require("glob");
40
+ const path_1 = __importStar(require("path"));
41
+ const node_url_1 = require("node:url");
42
+ const debug = (0, debug_1.default)('@testomatio/reporter:client');
43
+ // removed __dirname usage, because:
44
+ // 1. replaced with ESM syntax (import.meta.url), but it throws an error on tsc compilation;
45
+ // 2. got error "__dirname already defined" in compiles js code (cjs dir)
14
46
  let listOfTestFilesToExcludeFromReport = null;
15
-
16
47
  /**
17
48
  * @typedef {import('../types').TestData} TestData
18
- * @typedef {import('../types').RunStatus} RunStatus
19
49
  * @typedef {import('../types').PipeResult} PipeResult
20
50
  */
21
-
22
51
  class Client {
23
- /**
24
- * Create a Testomat client instance
25
- *
26
- * @param {*} params
27
- */
28
- constructor(params = {}) {
29
- this.pipeStore = {};
30
- this.runId = randomUUID(); // will be replaced by real run id
31
- this.pipes = pipesFactory(params, this.pipeStore);
32
- this.queue = Promise.resolve();
33
- this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
34
- this.executionList = Promise.resolve();
35
- this.uploader = new S3Uploader();
36
- this.uploader.checkEnabled();
37
-
38
- console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
39
- }
40
-
41
- /**
42
- * Asynchronously prepares the execution list for running tests through various pipes.
43
- * Each pipe in the client is checked for enablement,
44
- * and if all pipes are disabled, the function returns a resolved Promise.
45
- * Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
46
- * The results are then filtered to remove any undefined values.
47
- * If no valid results are found, the function returns undefined.
48
- * Otherwise, it returns the first non-empty array from the filtered results.
49
- *
50
- * @param {Object} params - The options for preparing the test execution list.
51
- * @param {string} params.pipe - Name of the executed pipe.
52
- * @param {string} params.pipeOptions - Filter option.
53
- * @returns {Promise<any>} - A Promise that resolves to an
54
- * array containing the prepared execution list,
55
- * or resolves to undefined if no valid results are found or if all pipes are disabled.
56
- */
57
- async prepareRun(params) {
58
- const { pipe, pipeOptions } = params;
59
- // all pipes disabled, skipping
60
- if (!this.pipes.some(p => p.isEnabled)) {
61
- return Promise.resolve();
52
+ /**
53
+ * Create a Testomat client instance
54
+ * @returns
55
+ */
56
+ // eslint-disable-next-line
57
+ constructor(params = {}) {
58
+ this.uuid = (0, crypto_1.randomUUID)();
59
+ this.queue = Promise.resolve();
60
+ this.totalUploaded = 0;
61
+ this.failedToUpload = 0;
62
+ // @ts-ignore this line will be removed in compiled code, because __dirname is defined in commonjs
63
+ const pathToPackageJSON = path_1.default.join(__dirname, '../package.json');
64
+ try {
65
+ this.version = JSON.parse(fs_1.default.readFileSync(pathToPackageJSON).toString()).version;
66
+ console.log(constants_js_1.APP_PREFIX, `Testomatio Reporter v${this.version}`);
67
+ }
68
+ catch (e) {
69
+ // do nothing
70
+ }
71
+ this.executionList = Promise.resolve();
62
72
  }
63
-
64
- try {
65
- const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
66
-
67
- if (!filterPipe.isEnabled) {
68
- // TODO:for the future for the another pipes
69
- console.warn(
70
- APP_PREFIX,
71
- `At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`,
72
- );
73
- return;
74
- }
75
-
76
- const results = await Promise.all(
77
- this.pipes.map(async p => ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })),
78
- );
79
-
80
- const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
81
-
82
- if (!result || result.length === 0) {
83
- return;
84
- }
85
-
86
- debug('Execution tests list', result);
87
-
88
- return result;
89
- } catch (err) {
90
- console.error(APP_PREFIX, err);
73
+ /**
74
+ * Asynchronously prepares the execution list for running tests through various pipes.
75
+ * Each pipe in the client is checked for enablement,
76
+ * and if all pipes are disabled, the function returns a resolved Promise.
77
+ * Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
78
+ * The results are then filtered to remove any undefined values.
79
+ * If no valid results are found, the function returns undefined.
80
+ * Otherwise, it returns the first non-empty array from the filtered results.
81
+ *
82
+ * @param {Object} params - The options for preparing the test execution list.
83
+ * @param {string} params.pipe - Name of the executed pipe.
84
+ * @param {string} params.pipeOptions - Filter option.
85
+ * @returns {Promise<any>} - A Promise that resolves to an
86
+ * array containing the prepared execution list,
87
+ * or resolves to undefined if no valid results are found or if all pipes are disabled.
88
+ */
89
+ async prepareRun(params) {
90
+ const store = {};
91
+ this.pipes = await (0, index_js_1.pipesFactory)(params, store);
92
+ const { pipe, pipeOptions } = params;
93
+ // all pipes disabled, skipping
94
+ if (!this.pipes.some(p => p.isEnabled)) {
95
+ return Promise.resolve();
96
+ }
97
+ try {
98
+ const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
99
+ if (!filterPipe.isEnabled) {
100
+ // TODO:for the future for the another pipes
101
+ console.warn(constants_js_1.APP_PREFIX, `At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`);
102
+ return;
103
+ }
104
+ const results = await Promise.all(this.pipes.map(async (p) => ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })));
105
+ const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
106
+ if (!result || result.length === 0) {
107
+ return;
108
+ }
109
+ debug('Execution tests list', result);
110
+ return result;
111
+ }
112
+ catch (err) {
113
+ console.error(constants_js_1.APP_PREFIX, err);
114
+ }
91
115
  }
92
- }
93
-
94
- /**
95
- * Used to create a new Test run
96
- *
97
- * @returns {Promise<any>} - resolves to Run id which should be used to update / add test
98
- */
99
- createRun() {
100
- debug('Creating run...');
101
- // all pipes disabled, skipping
102
- if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
103
-
104
- this.queue = this.queue
105
- .then(() => Promise.all(this.pipes.map(p => p.createRun())))
106
- .catch(err => console.log(APP_PREFIX, err))
107
- .then(() => {
108
- const runId = this.pipeStore?.runId;
109
- if (runId) this.runId = runId;
110
- this.uploader.checkEnabled();
111
- })
112
- .then(() => undefined); // fixes return type
113
-
114
- // debug('Run', this.queue);
115
- return this.queue;
116
- }
117
-
118
- /**
119
- * Updates test status and its data
120
- *
121
- * @param {string|undefined} status
122
- * @param {TestData} [testData]
123
- * @returns {Promise<PipeResult[]>}
124
- */
125
- async addTestRun(status, testData) {
126
- // all pipes disabled, skipping
127
- if (!this.pipes?.filter(p => p.isEnabled).length) return [];
128
-
129
- if (isTestShouldBeExculedFromReport(testData)) return [];
130
-
131
- if (status === STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
132
- debug('Skipping test from report', testData?.title);
133
- return []; // do not log skipped tests
116
+ /**
117
+ * Used to create a new Test run
118
+ *
119
+ * @returns {Promise<any>} - resolves to Run id which should be used to update / add test
120
+ */
121
+ async createRun(params) {
122
+ if (!this.pipes || !this.pipes.length)
123
+ this.pipes = await (0, index_js_1.pipesFactory)(params || {}, {});
124
+ debug('Creating run...');
125
+ // all pipes disabled, skipping
126
+ if (!this.pipes?.filter(p => p.isEnabled).length)
127
+ return Promise.resolve();
128
+ this.queue = this.queue
129
+ .then(() => Promise.all(this.pipes.map(p => p.createRun())))
130
+ .catch(err => console.log(constants_js_1.APP_PREFIX, err))
131
+ .then(() => undefined); // fixes return type
132
+ // debug('Run', this.queue);
133
+ return this.queue;
134
134
  }
135
-
136
- if (!testData)
137
- testData = {
138
- title: 'Unknown test',
139
- suite_title: 'Unknown suite',
140
- };
141
-
142
135
  /**
143
- * @type {TestData}
136
+ * Updates test status and its data
137
+ *
138
+ * @param {string|undefined} status
139
+ * @param {TestData} [testData]
140
+ * @returns {Promise<PipeResult[]>}
144
141
  */
145
- const {
146
- rid,
147
- error = null,
148
- time = 0,
149
- example = null,
150
- files = [],
151
- filesBuffers = [],
152
- steps,
153
- code = null,
154
- title,
155
- file,
156
- suite_title,
157
- suite_id,
158
- test_id,
159
- manuallyAttachedArtifacts,
160
- meta,
161
- } = testData;
162
- let { message = '' } = testData;
163
-
164
- let errorFormatted = '';
165
- if (error) {
166
- errorFormatted += this.formatError(error) || '';
167
- message = error?.message;
142
+ async addTestRun(status, testData) {
143
+ // all pipes disabled, skipping
144
+ if (!this.pipes?.filter(p => p.isEnabled).length)
145
+ return [];
146
+ if (isTestShouldBeExculedFromReport(testData))
147
+ return [];
148
+ if (status === constants_js_1.STATUS.SKIPPED && process.env.TESTOMATIO_EXCLUDE_SKIPPED) {
149
+ debug('Skipping test from report', testData?.title);
150
+ return []; // do not log skipped tests
151
+ }
152
+ if (!testData)
153
+ testData = {
154
+ title: 'Unknown test',
155
+ suite_title: 'Unknown suite',
156
+ };
157
+ /**
158
+ * @type {TestData}
159
+ */
160
+ const { rid, error = null, time = 0, example = null, files = [], filesBuffers = [], steps, code = null, title, file, suite_title, suite_id, test_id, manuallyAttachedArtifacts, meta, } = testData;
161
+ let { message = '' } = testData;
162
+ let errorFormatted = '';
163
+ if (error) {
164
+ errorFormatted += this.formatError(error) || '';
165
+ message = error?.message;
166
+ }
167
+ // Attach logs
168
+ const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
169
+ // add artifacts
170
+ if (manuallyAttachedArtifacts?.length)
171
+ files.push(...manuallyAttachedArtifacts);
172
+ const uploadedFiles = [];
173
+ for (const f of files) {
174
+ uploadedFiles.push(fileUploader_js_1.upload.uploadFileByPath(f, this.uuid));
175
+ }
176
+ for (const [idx, buffer] of filesBuffers.entries()) {
177
+ const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
178
+ uploadedFiles.push(fileUploader_js_1.upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
179
+ }
180
+ const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
181
+ if (artifacts.length < uploadedFiles.length) {
182
+ const failedUploading = uploadedFiles.length - artifacts.length;
183
+ this.failedToUpload += failedUploading;
184
+ }
185
+ this.totalUploaded += artifacts.length;
186
+ const data = {
187
+ rid,
188
+ files,
189
+ steps,
190
+ status,
191
+ stack: fullLogs,
192
+ example,
193
+ file,
194
+ code,
195
+ title,
196
+ suite_title,
197
+ suite_id,
198
+ test_id,
199
+ message,
200
+ run_time: typeof time === 'number' ? time : parseFloat(time),
201
+ artifacts,
202
+ meta,
203
+ };
204
+ // debug('Adding test run...', data);
205
+ // @ts-ignore
206
+ this.queue = this.queue.then(() => Promise.all(this.pipes.map(async (pipe) => {
207
+ try {
208
+ const result = await pipe.addTest(data);
209
+ return { pipe: pipe.toString(), result };
210
+ }
211
+ catch (err) {
212
+ console.log(constants_js_1.APP_PREFIX, pipe.toString(), err);
213
+ }
214
+ })));
215
+ // @ts-ignore
216
+ return this.queue;
168
217
  }
169
-
170
- // Attach logs
171
- const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
172
-
173
- // add artifacts
174
- if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
175
-
176
- const uploadedFiles = [];
177
-
178
- for (let f of files) {
179
- if (typeof f === 'object') {
180
- if (!f.path) continue;
181
-
182
- f = f.path;
183
- }
184
-
185
- uploadedFiles.push(this.uploader.uploadFileByPath(f, [
186
- this.runId,
187
- rid,
188
- path.basename(f)
189
- ]));
190
-
218
+ /**
219
+ *
220
+ * Updates the status of the current test run and finishes the run.
221
+ * @param {'passed' | 'failed' | 'skipped' | 'finished'} status - The status of the current test run.
222
+ * Must be one of "passed", "failed", or "finished"
223
+ * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
224
+ * @returns {Promise<any>} - A Promise that resolves when finishes the run.
225
+ */
226
+ updateRunStatus(status, isParallel = false) {
227
+ debug('Updating run status...');
228
+ // all pipes disabled, skipping
229
+ if (!this.pipes?.filter(p => p.isEnabled).length)
230
+ return Promise.resolve();
231
+ const runParams = { status, parallel: isParallel };
232
+ this.queue = this.queue
233
+ .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
234
+ .then(() => {
235
+ debug('TOTAL artifacts', this.totalUploaded);
236
+ if (this.totalUploaded && !fileUploader_js_1.upload.isArtifactsEnabled())
237
+ debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
238
+ if (this.totalUploaded && fileUploader_js_1.upload.isArtifactsEnabled()) {
239
+ console.log(constants_js_1.APP_PREFIX, `🗄️ ${this.totalUploaded} artifacts ${process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : picocolors_1.default.bold('publicly')} uploaded to S3 bucket`);
240
+ if (this.failedToUpload > 0) {
241
+ console.log(constants_js_1.APP_PREFIX, picocolors_1.default.yellow(`Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded.
242
+ Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`));
243
+ }
244
+ }
245
+ })
246
+ .catch(err => console.log(constants_js_1.APP_PREFIX, err));
247
+ return this.queue;
191
248
  }
192
-
193
- for (const [idx, buffer] of filesBuffers.entries()) {
194
- const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
195
- uploadedFiles.push(this.uploader.uploadFileAsBuffer(buffer, [this.runId, rid, fileName]));
249
+ /**
250
+ * Returns the formatted stack including the stack trace, steps, and logs.
251
+ * @returns {string}
252
+ */
253
+ formatLogs({ error, steps, logs }) {
254
+ error = error?.trim();
255
+ logs = logs?.trim();
256
+ if (Array.isArray(steps)) {
257
+ steps = steps
258
+ .map(step => formatStep(step))
259
+ .flat()
260
+ .join('\n');
261
+ }
262
+ let testLogs = '';
263
+ if (steps)
264
+ testLogs += `${picocolors_1.default.bold(picocolors_1.default.blue('################[ Steps ]################'))}\n${steps}\n\n`;
265
+ if (logs)
266
+ testLogs += `${picocolors_1.default.bold(picocolors_1.default.gray('################[ Logs ]################'))}\n${logs}\n\n`;
267
+ if (error)
268
+ testLogs += `${picocolors_1.default.bold(picocolors_1.default.red('################[ Failure ]################'))}\n${error}`;
269
+ return testLogs;
196
270
  }
197
-
198
- const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
199
-
200
- const data = {
201
- rid,
202
- files,
203
- steps,
204
- status,
205
- stack: fullLogs,
206
- example,
207
- file,
208
- code,
209
- title,
210
- suite_title,
211
- suite_id,
212
- test_id,
213
- message,
214
- run_time: typeof time === 'number' ? time : parseFloat(time),
215
- artifacts,
216
- meta,
217
- };
218
-
219
- // debug('Adding test run...', data);
220
-
221
- this.queue = this.queue.then(() =>
222
- Promise.all(
223
- this.pipes.map(async pipe => {
224
- try {
225
- const result = await pipe.addTest(data);
226
- return { pipe: pipe.toString(), result };
227
- } catch (err) {
228
- console.log(APP_PREFIX, pipe.toString(), err);
229
- }
230
- }),
231
- ),
232
- );
233
-
234
- return this.queue;
235
- }
236
-
237
- /**
238
- *
239
- * Updates the status of the current test run and finishes the run.
240
- * @param {'passed' | 'failed' | 'skipped' | 'finished'} status - The status of the current test run.
241
- * Must be one of "passed", "failed", or "finished"
242
- * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
243
- * @returns {Promise<any>} - A Promise that resolves when finishes the run.
244
- */
245
- updateRunStatus(status, isParallel = false) {
246
- debug('Updating run status...');
247
- // all pipes disabled, skipping
248
- if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
249
-
250
- const runParams = { status, parallel: isParallel };
251
-
252
- this.queue = this.queue
253
- .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
254
- .then(() => {
255
- debug('TOTAL artifacts', this.uploader.totalUploaded);
256
- debug(`${this.uploader.skippedUpload} artifacts skipped`);
257
-
258
- if (this.uploader.totalUploaded && this.uploader.isEnabled) {
259
- console.log(
260
- APP_PREFIX,
261
- `🗄️ ${this.uploader.totalUploaded} artifacts ${
262
- process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
263
- } uploaded to S3 bucket`,
264
- );
271
+ formatError(error, message) {
272
+ if (!message)
273
+ message = error.message;
274
+ if (error.inspect)
275
+ message = error.inspect() || '';
276
+ let stack = '';
277
+ if (error.name)
278
+ stack += `${picocolors_1.default.red(error.name)}`;
279
+ if (error.operator)
280
+ stack += ` (${picocolors_1.default.red(error.operator)})`;
281
+ // add new line if something was added to stack
282
+ if (stack)
283
+ stack += ': ';
284
+ stack += `${message}\n`;
285
+ if (error.diff) {
286
+ // diff for vitest
287
+ stack += error.diff;
288
+ stack += '\n\n';
265
289
  }
266
-
267
- if (this.uploader.failedUpload) {
268
- console.log(APP_PREFIX, `${this.uploader.failedUpload} artifacts failed to upload`);
290
+ else if (error.actual && error.expected && error.actual !== error.expected) {
291
+ // diffs for mocha, cypress, codeceptjs style
292
+ stack += `\n\n${picocolors_1.default.bold(picocolors_1.default.green('+ expected'))} ${picocolors_1.default.bold(picocolors_1.default.red('- actual'))}`;
293
+ stack += `\n${picocolors_1.default.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
294
+ stack += `\n${picocolors_1.default.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
295
+ stack += '\n\n';
269
296
  }
270
-
271
- if (this.uploader.isEnabled && this.uploader.skippedUpload) {
272
- console.log(APP_PREFIX, `${chalk.bold(this.uploader.skippedUpload)} artifacts skipped to upload`);
297
+ const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
298
+ try {
299
+ let hasFrame = false;
300
+ const record = (0, callsite_record_1.default)({
301
+ forError: error,
302
+ isCallsiteFrame: frame => {
303
+ if (customFilter && (0, minimatch_1.minimatch)(frame.fileName, customFilter))
304
+ return false;
305
+ if (hasFrame)
306
+ return false;
307
+ if (isNotInternalFrame(frame))
308
+ hasFrame = true;
309
+ return hasFrame;
310
+ },
311
+ });
312
+ // @ts-ignore
313
+ if (record && !record.filename.startsWith('http')) {
314
+ stack += record.renderSync({ stackFilter: isNotInternalFrame });
315
+ }
316
+ return stack;
273
317
  }
274
-
275
- if (this.uploader.skippedUpload || this.uploader.failedUpload) {
276
- console.log(APP_PREFIX, `Run "${chalk.magenta(`TESTOMATIO_RUN=${this.runId} npx upload-artifacts`)}" with valid S3 credentials to upload skipped & failed artifacts`);
318
+ catch (e) {
319
+ console.log(e);
277
320
  }
278
-
279
- })
280
- .catch(err => console.log(APP_PREFIX, err));
281
-
282
- return this.queue;
283
- }
284
-
285
- /**
286
- * Returns the formatted stack including the stack trace, steps, and logs.
287
- * @returns {string}
288
- */
289
- formatLogs({ error, steps, logs }) {
290
- error = error?.trim();
291
- logs = logs?.trim();
292
-
293
- if (Array.isArray(steps)) {
294
- steps = steps
295
- .map(step => formatStep(step))
296
- .flat()
297
- .join('\n');
298
- }
299
-
300
- let testLogs = '';
301
- if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}\n\n`;
302
- if (logs) testLogs += `${chalk.bold.gray('################[ Logs ]################')}\n${logs}\n\n`;
303
- if (error) testLogs += `${chalk.bold.red('################[ Failure ]################')}\n${error}`;
304
- return testLogs;
305
- }
306
-
307
- formatError(error, message) {
308
- if (!message) message = error.message;
309
- if (error.inspect) message = error.inspect() || '';
310
-
311
- let stack = '';
312
- if (error.name) stack += `${chalk.red(error.name)}`;
313
- if (error.operator) stack += ` (${chalk.red(error.operator)})`;
314
- // add new line if something was added to stack
315
- if (stack) stack += ': ';
316
-
317
- stack += `${message}\n`;
318
-
319
- if (error.diff) {
320
- // diff for vitest
321
- stack += error.diff;
322
- stack += '\n\n';
323
- } else if (error.actual && error.expected && error.actual !== error.expected) {
324
- // diffs for mocha, cypress, codeceptjs style
325
- stack += `\n\n${chalk.bold.green('+ expected')} ${chalk.bold.red('- actual')}`;
326
- stack += `\n${chalk.green(`+ ${error.expected.toString().split('\n').join('\n+ ')}`)}`;
327
- stack += `\n${chalk.red(`- ${error.actual.toString().split('\n').join('\n- ')}`)}`;
328
- stack += '\n\n';
329
321
  }
330
-
331
- const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
332
-
333
- try {
334
- let hasFrame = false;
335
- const record = createCallsiteRecord({
336
- forError: error,
337
- isCallsiteFrame: frame => {
338
- if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
339
- if (hasFrame) return false;
340
- if (isNotInternalFrame(frame)) hasFrame = true;
341
- return hasFrame;
342
- },
343
- });
344
- if (record && !record.filename.startsWith('http')) {
345
- stack += record.renderSync({ stackFilter: isNotInternalFrame });
346
- }
347
- return stack;
348
- } catch (e) {
349
- console.log(e);
350
- }
351
- }
352
322
  }
353
-
323
+ exports.Client = Client;
354
324
  function isNotInternalFrame(frame) {
355
- return (
356
- frame.getFileName() &&
357
- frame.getFileName().includes(sep) &&
358
- !frame.getFileName().includes('node_modules') &&
359
- !frame.getFileName().includes('internal')
360
- );
325
+ return (frame.getFileName() &&
326
+ frame.getFileName().includes(path_1.sep) &&
327
+ !frame.getFileName().includes('node_modules') &&
328
+ !frame.getFileName().includes('internal'));
361
329
  }
362
-
363
330
  function formatStep(step, shift = 0) {
364
- const prefix = ' '.repeat(shift);
365
-
366
- const lines = [];
367
-
368
- if (step.error) {
369
- lines.push(`${prefix}${chalk.red(step.title)} ${chalk.gray(`${step.duration}ms`)}`);
370
- } else {
371
- lines.push(`${prefix}${step.title} ${chalk.gray(`${step.duration}ms`)}`);
372
- }
373
-
374
- for (const child of step.steps || []) {
375
- lines.push(...formatStep(child, shift + 2));
376
- }
377
-
378
- return lines;
331
+ const prefix = ' '.repeat(shift);
332
+ const lines = [];
333
+ if (step.error) {
334
+ lines.push(`${prefix}${picocolors_1.default.red(step.title)} ${picocolors_1.default.gray(`${step.duration}ms`)}`);
335
+ }
336
+ else {
337
+ lines.push(`${prefix}${step.title} ${picocolors_1.default.gray(`${step.duration}ms`)}`);
338
+ }
339
+ for (const child of step.steps || []) {
340
+ lines.push(...formatStep(child, shift + 2));
341
+ }
342
+ return lines;
379
343
  }
380
-
381
344
  /**
382
345
  *
383
346
  * @param {TestData} testData
384
347
  * @returns boolean
385
348
  */
386
349
  function isTestShouldBeExculedFromReport(testData) {
387
- // const fileName = path.basename(test.location?.file || '');
388
- const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
389
- if (!globExcludeFilesPattern) return false;
390
-
391
- if (!testData.file) {
392
- debug('No "file" property found for test ', testData.title);
350
+ // const fileName = path.basename(test.location?.file || '');
351
+ const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
352
+ if (!globExcludeFilesPattern)
353
+ return false;
354
+ if (!testData.file) {
355
+ debug('No "file" property found for test ', testData.title);
356
+ return false;
357
+ }
358
+ const excludeParretnsList = globExcludeFilesPattern.split(';');
359
+ // as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
360
+ if (!listOfTestFilesToExcludeFromReport) {
361
+ // list of files with relative paths
362
+ listOfTestFilesToExcludeFromReport = glob_1.glob.sync(excludeParretnsList, { ignore: '**/node_modules/**' });
363
+ debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
364
+ }
365
+ const testFileRelativePath = path_1.default.relative(process.cwd(), testData.file);
366
+ // no files found matching the exclusion pattern
367
+ if (!listOfTestFilesToExcludeFromReport.length)
368
+ return false;
369
+ if (listOfTestFilesToExcludeFromReport.includes(testFileRelativePath)) {
370
+ debug(`Excluding test '${testData.title}' <${testFileRelativePath}> from reporting`);
371
+ return true;
372
+ }
393
373
  return false;
394
- }
395
-
396
- const excludeParretnsList = globExcludeFilesPattern.split(';');
397
-
398
- // as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
399
- if (!listOfTestFilesToExcludeFromReport) {
400
- // list of files with relative paths
401
- listOfTestFilesToExcludeFromReport = glob.sync(excludeParretnsList, { ignore: '**/node_modules/**' });
402
- debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
403
- }
404
-
405
- const testFileRelativePath = path.relative(process.cwd(), testData.file);
406
-
407
- // no files found matching the exclusion pattern
408
- if (!listOfTestFilesToExcludeFromReport.length) return false;
409
-
410
- if (listOfTestFilesToExcludeFromReport.includes(testFileRelativePath)) {
411
- debug(`Excluding test '${testData.title}' <${testFileRelativePath}> from reporting`);
412
- return true;
413
- }
414
- return false;
415
374
  }
416
-
417
375
  module.exports = Client;
418
- module.exports.TestomatioClient = Client;
376
+
377
+ module.exports.Client = Client;