@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/lib/uploader.js CHANGED
@@ -1,381 +1,312 @@
1
- const debug = require('debug')('@testomatio/reporter:uploader');
2
- const { S3 } = require('@aws-sdk/client-s3');
3
- const { Upload } = require('@aws-sdk/lib-storage');
4
- const fs = require('fs');
5
- const os = require('os');
6
- const path = require('path');
7
- const promiseRetry = require('promise-retry');
8
- const chalk = require('chalk');
9
- const { APP_PREFIX } = require('./constants');
10
- const { filesize: prettyBytes } = require('filesize');
11
-
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.S3Uploader = 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 os_1 = __importDefault(require("os"));
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 constants_js_1 = require("./constants.js");
16
+ const filesize_1 = require("filesize");
17
+ const debug = (0, debug_1.default)('@testomatio/reporter:file-uploader');
12
18
  class S3Uploader {
13
- constructor() {
14
- this.isEnabled = undefined;
15
- this.storeEnabled = true;
16
- this.config = undefined;
17
-
18
- // counters
19
- /**
20
- * @type {{path: string, size: number}[]}
21
- */
22
- this.skippedUploads = [];
23
- this.failedUploads = [];
24
- /**
25
- * @type {{path: string, size: number, link: string}[]}
26
- */
27
- this.successfulUploads = [];
28
-
29
- this.configKeys = [
30
- 'S3_ENDPOINT',
31
- 'S3_REGION',
32
- 'S3_BUCKET',
33
- 'S3_ACCESS_KEY_ID',
34
- 'S3_SECRET_ACCESS_KEY',
35
- 'S3_SESSION_TOKEN',
36
- 'S3_FORCE_PATH_STYLE',
37
- 'TESTOMATIO_DISABLE_ARTIFACTS',
38
- 'TESTOMATIO_PRIVATE_ARTIFACTS',
39
- 'TESTOMATIO_ARTIFACT_MAX_SIZE_MB',
40
- ];
41
- }
42
-
43
- resetConfig() {
44
- this.config = undefined;
45
- this.isEnabled = undefined;
46
- }
47
-
48
- getConfig() {
49
- if (this.config) return this.config;
50
- this.config = this.configKeys.reduce((acc, key) => {
51
- acc[key] = process.env[key];
52
- return acc;
53
- }, {});
54
- return this.config;
55
- }
56
-
57
- getMaskedConfig() {
58
- return Object.fromEntries(
59
- Object.entries(this.getConfig()).map(([key, value]) => [
60
- key,
61
- key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
62
- ]),
63
- );
64
- }
65
-
66
- checkEnabled() {
67
- if (this.isEnabled !== undefined) return this.isEnabled;
68
-
69
- const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
70
- if (!S3_BUCKET) debug(`Artifacts uploading is disabled because S3_BUCKET is not set`);
71
- this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
72
-
73
- if (this.isEnabled) debug('S3 uploader is enabled');
74
- debug(this.getMaskedConfig());
75
-
76
- return this.isEnabled;
77
- }
78
-
79
- enableLogStorage() {
80
- this.storeEnabled = true;
81
- }
82
-
83
- disableLogStorage() {
84
- this.storeEnabled = false;
85
- }
86
-
87
- /**
88
- *
89
- * @param {*} Body
90
- * @param {*} Key
91
- * @param {{path: string, size?: number}} file
92
- * @returns
93
- */
94
- async #uploadToS3(Body, Key, file) {
95
- const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
96
- const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
97
-
98
- if (!S3_BUCKET || !Body) {
99
- console.log(
100
- APP_PREFIX,
101
- chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`),
102
- this.getMaskedConfig(),
103
- );
104
- return;
105
- }
106
-
107
- debug('Uploading to S3:', Key);
108
-
109
- const s3Config = this.#getS3Config();
110
- const s3 = new S3(s3Config);
111
-
112
- const params = {
113
- Bucket: S3_BUCKET,
114
- Key,
115
- Body,
116
- };
117
-
118
- // disable ACL for I AM roles
119
- if (!s3Config.credentials.sessionToken) {
120
- params.ACL = ACL;
121
- }
122
-
123
- try {
124
- const upload = new Upload({
125
- client: s3,
126
- params,
127
- });
128
-
129
- const link = await this.getS3LocationLink(upload);
130
- this.successfulUploads.push({ path: file.path, size: file.size, link });
131
- debug(`📤 Uploaded artifact. File: ${file.path}, size: ${prettyBytes(file.size)}, link: ${link}`);
132
- return link;
133
- } catch (e) {
134
- this.failedUploads.push({ path: file.path, size: file.size });
135
- debug('S3 uploading error:', e);
136
- console.log(APP_PREFIX, 'Upload failed:', e.message, '\nConfig:\n', this.getMaskedConfig());
19
+ constructor() {
20
+ this.isEnabled = undefined;
21
+ this.storeEnabled = true;
22
+ this.config = undefined;
23
+ /**
24
+ * @type {{path: string, size: number}[]}
25
+ */
26
+ this.skippedUploads = [];
27
+ this.failedUploads = [];
28
+ /**
29
+ * @type {{path: string, size: number, link: string}[]}
30
+ */
31
+ this.successfulUploads = [];
32
+ this.configKeys = [
33
+ 'S3_ENDPOINT',
34
+ 'S3_REGION',
35
+ 'S3_BUCKET',
36
+ 'S3_ACCESS_KEY_ID',
37
+ 'S3_SECRET_ACCESS_KEY',
38
+ 'S3_SESSION_TOKEN',
39
+ 'S3_FORCE_PATH_STYLE',
40
+ 'TESTOMATIO_DISABLE_ARTIFACTS',
41
+ 'TESTOMATIO_PRIVATE_ARTIFACTS',
42
+ 'TESTOMATIO_ARTIFACT_MAX_SIZE_MB',
43
+ ];
137
44
  }
138
- }
139
-
140
- /**
141
- * Returns an array of uploaded files
142
- *
143
- * @returns {{rid: string, file: string, uploaded: boolean}[]}
144
- */
145
- readUploadedFiles(runId) {
146
- const tempFilePath = this.#getFilePathWithUploadsList(runId);
147
-
148
- debug('Reading file', tempFilePath);
149
-
150
- if (!fs.existsSync(tempFilePath)) {
151
- debug('File not found:', tempFilePath);
152
- return [];
45
+ resetConfig() {
46
+ this.config = undefined;
47
+ this.isEnabled = undefined;
153
48
  }
154
-
155
- const stats = fs.statSync(tempFilePath);
156
- debug('Artifacts file stats:', +stats.mtime);
157
- debug('Current time:', +new Date());
158
- const diff = +new Date() - +stats.mtime;
159
- debug('Diff:', diff);
160
- const diffHours = diff / 1000 / 60 / 60;
161
- debug('Diff hours:', diffHours);
162
- if (diffHours > 3) {
163
- console.log(APP_PREFIX, "Artifacts file is too old, can't process artifacts. Please re-run the tests.");
164
- return [];
49
+ /**
50
+ *
51
+ * @returns {Record<string, string>}
52
+ */
53
+ getConfig() {
54
+ if (this.config)
55
+ return this.config;
56
+ this.config = this.configKeys.reduce((acc, key) => {
57
+ acc[key] = process.env[key];
58
+ return acc;
59
+ }, {});
60
+ return this.config;
165
61
  }
166
-
167
- const data = fs.readFileSync(tempFilePath, 'utf8');
168
- debug('Artifacts file contents:', data);
169
- const lines = data.split('\n').filter(Boolean);
170
- return lines.map(line => JSON.parse(line));
171
- }
172
-
173
- #getFilePathWithUploadsList(runId) {
174
- const tempFilePath = path.join(os.tmpdir(), `testomatio.run.${runId}.json`);
175
- if (!fs.existsSync(tempFilePath)) {
176
- debug('Creating artifacts file:', tempFilePath);
177
- fs.writeFileSync(tempFilePath, '');
62
+ getMaskedConfig() {
63
+ return Object.fromEntries(Object.entries(this.getConfig()).map(([key, value]) => [
64
+ key,
65
+ key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
66
+ ]));
178
67
  }
179
- return tempFilePath;
180
- }
181
-
182
- storeUploadedFile(filePath, runId, rid, uploaded = false) {
183
- if (!this.storeEnabled) return;
184
-
185
- if (!filePath || !runId || !rid) return;
186
-
187
- const tempFilePath = this.#getFilePathWithUploadsList(runId);
188
-
189
- if (typeof filePath === 'object') {
190
- filePath = filePath.path;
68
+ checkEnabled() {
69
+ if (this.isEnabled !== undefined)
70
+ return this.isEnabled;
71
+ const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
72
+ if (!S3_BUCKET)
73
+ debug(`Artifacts uploading is disabled because S3_BUCKET is not set`);
74
+ this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
75
+ if (this.isEnabled)
76
+ debug('S3 uploader is enabled');
77
+ debug(this.getMaskedConfig());
78
+ return this.isEnabled;
191
79
  }
192
-
193
- if (typeof filePath === 'string' && !path.isAbsolute(filePath)) {
194
- filePath = path.join(process.cwd(), filePath);
80
+ enableLogStorage() {
81
+ this.storeEnabled = true;
195
82
  }
196
-
197
- const data = { rid, file: filePath, uploaded };
198
- const jsonLine = `${JSON.stringify(data)}\n`;
199
- fs.appendFileSync(tempFilePath, jsonLine);
200
- }
201
-
202
- /**
203
- *
204
- * @param {*} filePath
205
- * @param {*} pathInS3 contains runId, rid and filename
206
- * @returns
207
- */
208
- async uploadFileByPath(filePath, pathInS3) {
209
- // sometimes artifacts uploading started before createRun function completion
210
- this.isEnabled = this.isEnabled ?? this.checkEnabled();
211
-
212
- const [runId, rid] = pathInS3;
213
-
214
- if (!filePath) return;
215
-
216
- let fileSize = null;
217
- let fileSizeInMb = null;
218
-
219
- try {
220
- // file may not exist
221
- fileSize = fs.statSync(filePath).size;
222
- fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
223
- } catch (e) {
224
- debug(`File ${filePath} does not exist`);
83
+ disableLogStorage() {
84
+ this.storeEnabled = false;
225
85
  }
226
-
227
- if (!this.isEnabled) {
228
- this.storeUploadedFile(filePath, runId, rid, false);
229
- this.skippedUploads.push({ path: filePath, size: fileSize });
230
- return;
86
+ /**
87
+ *
88
+ * @param {*} Body
89
+ * @param {*} Key
90
+ * @param {{path: string, size?: number}} file
91
+ * @returns
92
+ */
93
+ async #uploadToS3(Body, Key, file) {
94
+ const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
95
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
96
+ if (!S3_BUCKET || !Body) {
97
+ console.log(constants_js_1.APP_PREFIX, picocolors_1.default.bold(picocolors_1.default.red(`Failed uploading '${Key}'. Please check S3 credentials`)), this.getMaskedConfig());
98
+ return;
99
+ }
100
+ debug('Uploading to S3:', Key);
101
+ const s3Config = this.#getS3Config();
102
+ const s3 = new client_s3_1.S3(s3Config);
103
+ const params = {
104
+ Bucket: S3_BUCKET,
105
+ Key,
106
+ Body,
107
+ };
108
+ // disable ACL for I AM roles
109
+ if (!s3Config.credentials.sessionToken) {
110
+ params.ACL = ACL;
111
+ }
112
+ try {
113
+ const upload = new lib_storage_1.Upload({ client: s3, params });
114
+ const link = await this.getS3LocationLink(upload);
115
+ this.successfulUploads.push({ path: file.path, size: file.size, link });
116
+ debug(`📤 Uploaded artifact. File: ${file.path}, size: ${(0, filesize_1.filesize)(file.size)}, link: ${link}`);
117
+ return link;
118
+ }
119
+ catch (e) {
120
+ this.failedUploads.push({ path: file.path, size: file.size });
121
+ debug('S3 uploading error:', e);
122
+ console.log(constants_js_1.APP_PREFIX, 'Upload failed:', e.message, '\nConfig:\n', this.getMaskedConfig());
123
+ }
231
124
  }
232
-
233
- const { S3_BUCKET, TESTOMATIO_ARTIFACT_MAX_SIZE_MB } = this.getConfig();
234
-
235
- debug('Started upload', filePath, 'to', S3_BUCKET);
236
-
237
- const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
238
-
239
- if (!isFileExist) {
240
- console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
241
- return;
125
+ /**
126
+ * Returns an array of uploaded files
127
+ *
128
+ * @returns {{rid: string, file: string, uploaded: boolean}[]}
129
+ */
130
+ readUploadedFiles(runId) {
131
+ const tempFilePath = this.#getFilePathWithUploadsList(runId);
132
+ debug('Reading file', tempFilePath);
133
+ if (!fs_1.default.existsSync(tempFilePath)) {
134
+ debug('File not found:', tempFilePath);
135
+ return [];
136
+ }
137
+ const stats = fs_1.default.statSync(tempFilePath);
138
+ debug('Artifacts file stats:', +stats.mtime);
139
+ debug('Current time:', +new Date());
140
+ const diff = +new Date() - +stats.mtime;
141
+ debug('Diff:', diff);
142
+ const diffHours = diff / 1000 / 60 / 60;
143
+ debug('Diff hours:', diffHours);
144
+ if (diffHours > 3) {
145
+ console.log(constants_js_1.APP_PREFIX, "Artifacts file is too old, can't process artifacts. Please re-run the tests.");
146
+ return [];
147
+ }
148
+ const data = fs_1.default.readFileSync(tempFilePath, 'utf8');
149
+ debug('Artifacts file contents:', data);
150
+ const lines = data.split('\n').filter(Boolean);
151
+ return lines.map(line => JSON.parse(line));
242
152
  }
243
-
244
- // skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
245
- if (
246
- this.storeEnabled &&
247
- TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
248
- fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)
249
- ) {
250
- const skippedArtifact = { path: filePath, size: fileSize };
251
- this.storeUploadedFile(filePath, runId, rid, false);
252
- this.skippedUploads.push(skippedArtifact);
253
- debug(
254
- chalk.yellow(
255
- `Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size ${
256
- TESTOMATIO_ARTIFACT_MAX_SIZE_MB
257
- }MB. Skipping.`,
258
- ),
259
- );
260
- return;
153
+ #getFilePathWithUploadsList(runId) {
154
+ const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.run.${runId}.json`);
155
+ if (!fs_1.default.existsSync(tempFilePath)) {
156
+ debug('Creating artifacts file:', tempFilePath);
157
+ fs_1.default.writeFileSync(tempFilePath, '');
158
+ }
159
+ return tempFilePath;
261
160
  }
262
- debug('File:', filePath, 'exists, size:', prettyBytes(fileSize));
263
-
264
- const fileStream = fs.createReadStream(filePath);
265
- const Key = pathInS3.join('/');
266
-
267
- const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
268
-
269
- this.storeUploadedFile(filePath, runId, rid, !!link);
270
-
271
- return link;
272
- }
273
-
274
- /**
275
- * @param {Buffer} buffer
276
- * @param {string[]} pathInS3
277
- * @returns
278
- */
279
- async uploadFileAsBuffer(buffer, pathInS3) {
280
- if (!this.isEnabled) return;
281
-
282
- let Key = pathInS3.join('/');
283
- const ext = this.#getFileExtBase64(buffer);
284
-
285
- if (ext) {
286
- Key = `${Key}.${ext}`;
161
+ storeUploadedFile(filePath, runId, rid, uploaded = false) {
162
+ if (!this.storeEnabled)
163
+ return;
164
+ if (!filePath || !runId || !rid)
165
+ return;
166
+ const tempFilePath = this.#getFilePathWithUploadsList(runId);
167
+ if (typeof filePath === 'object') {
168
+ filePath = filePath.path;
169
+ }
170
+ if (typeof filePath === 'string' && !path_1.default.isAbsolute(filePath)) {
171
+ filePath = path_1.default.join(process.cwd(), filePath);
172
+ }
173
+ const data = { rid, file: filePath, uploaded };
174
+ const jsonLine = `${JSON.stringify(data)}\n`;
175
+ fs_1.default.appendFileSync(tempFilePath, jsonLine);
287
176
  }
288
-
289
- return this.#uploadToS3(buffer, Key, { path: Key });
290
- }
291
-
292
- async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
293
- return promiseRetry(
294
- async (retry, number) => {
177
+ /**
178
+ * @param {*} filePath
179
+ * @param {*} pathInS3 contains runId, rid and filename
180
+ * @returns
181
+ */
182
+ async uploadFileByPath(filePath, pathInS3) {
183
+ // sometimes artifacts uploading started before createRun function completion
184
+ this.isEnabled = this.isEnabled ?? this.checkEnabled();
185
+ const [runId, rid] = pathInS3;
186
+ if (!filePath)
187
+ return;
188
+ let fileSize = null;
189
+ let fileSizeInMb = null;
295
190
  try {
296
- fs.accessSync(filePath);
297
- return true;
298
- } catch (err) {
299
- if (number === attempts) {
300
- return false;
301
- }
302
- debug(`File not found, retrying (attempt ${number}/${attempts})`);
303
- await new Promise(resolve => {
304
- setTimeout(resolve, intervalMs);
305
- });
306
- retry(err);
191
+ // file may not exist
192
+ fileSize = fs_1.default.statSync(filePath).size;
193
+ fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
307
194
  }
308
- },
309
- {
310
- retries: attempts,
311
- minTimeout: intervalMs,
312
- maxTimeout: intervalMs,
313
- },
314
- );
315
- }
316
-
317
- async getS3LocationLink(out) {
318
- const response = await out.done();
319
-
320
- let s3Location = response?.Location?.trim();
321
-
322
- if (!s3Location) {
323
- s3Location = out?.singleUploadResult?.Location;
324
- debug('Uploaded singleUploadResult.Location', s3Location);
325
-
326
- if (!s3Location) {
327
- throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
328
- }
195
+ catch (e) {
196
+ debug(`File ${filePath} does not exist`);
197
+ }
198
+ if (!this.isEnabled) {
199
+ this.storeUploadedFile(filePath, runId, rid, false);
200
+ this.skippedUploads.push({ path: filePath, size: fileSize });
201
+ return;
202
+ }
203
+ const { S3_BUCKET, TESTOMATIO_ARTIFACT_MAX_SIZE_MB } = this.getConfig();
204
+ debug('Started upload', filePath, 'to', S3_BUCKET);
205
+ const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
206
+ if (!isFileExist) {
207
+ console.error(picocolors_1.default.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
208
+ return;
209
+ }
210
+ // skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
211
+ if (this.storeEnabled &&
212
+ TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
213
+ fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)) {
214
+ const skippedArtifact = { path: filePath, size: fileSize };
215
+ this.storeUploadedFile(filePath, runId, rid, false);
216
+ this.skippedUploads.push(skippedArtifact);
217
+ debug(picocolors_1.default.yellow(`Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size. Skipping.`));
218
+ return;
219
+ }
220
+ debug('File:', filePath, 'exists, size:', (0, filesize_1.filesize)(fileSize));
221
+ const fileStream = fs_1.default.createReadStream(filePath);
222
+ const Key = pathInS3.join('/');
223
+ const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
224
+ this.storeUploadedFile(filePath, runId, rid, !!link);
225
+ return link;
329
226
  }
330
-
331
- // Normalize the URL
332
- if (!s3Location.startsWith('http')) {
333
- s3Location = `https://${s3Location}`;
227
+ /**
228
+ * @param {Buffer} buffer
229
+ * @param {string[]} pathInS3
230
+ * @returns
231
+ */
232
+ async uploadFileAsBuffer(buffer, pathInS3) {
233
+ if (!this.isEnabled)
234
+ return;
235
+ let Key = pathInS3.join('/');
236
+ const ext = this.#getFileExtBase64(buffer);
237
+ if (ext) {
238
+ Key = `${Key}.${ext}`;
239
+ }
240
+ return this.#uploadToS3(buffer, Key, { path: Key });
334
241
  }
335
-
336
- return s3Location;
337
- }
338
-
339
- #getFileExtBase64(str) {
340
- const type = str.charAt(0);
341
-
342
- return (
343
- {
344
- '/': 'jpg',
345
- i: 'png',
346
- R: 'gif',
347
- U: 'webp',
348
- }[type] || ''
349
- );
350
- }
351
-
352
- #getS3Config() {
353
- const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
354
- this.getConfig();
355
-
356
- const cfg = {
357
- region: S3_REGION,
358
- credentials: {
359
- accessKeyId: S3_ACCESS_KEY_ID,
360
- secretAccessKey: S3_SECRET_ACCESS_KEY,
361
- },
362
- };
363
-
364
- if (S3_FORCE_PATH_STYLE) {
365
- cfg.forcePathStyle = !['false', '0'].includes(String(S3_FORCE_PATH_STYLE || '').toLowerCase());
242
+ async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
243
+ return (0, promise_retry_1.default)(async (retry, number) => {
244
+ try {
245
+ fs_1.default.accessSync(filePath);
246
+ return true;
247
+ }
248
+ catch (err) {
249
+ if (number === attempts) {
250
+ return false;
251
+ }
252
+ debug(`File not found, retrying (attempt ${number}/${attempts})`);
253
+ await new Promise(resolve => {
254
+ setTimeout(resolve, intervalMs);
255
+ });
256
+ retry(err);
257
+ }
258
+ }, {
259
+ retries: attempts,
260
+ minTimeout: intervalMs,
261
+ maxTimeout: intervalMs,
262
+ });
366
263
  }
367
-
368
- if (S3_SESSION_TOKEN) {
369
- cfg.credentials.sessionToken = S3_SESSION_TOKEN;
264
+ async getS3LocationLink(out) {
265
+ const response = await out.done();
266
+ let s3Location = response?.Location?.trim();
267
+ if (!s3Location) {
268
+ s3Location = out?.singleUploadResult?.Location;
269
+ debug('Uploaded singleUploadResult.Location', s3Location);
270
+ if (!s3Location) {
271
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
272
+ }
273
+ }
274
+ // Normalize the URL
275
+ if (!s3Location.startsWith('http')) {
276
+ s3Location = `https://${s3Location}`;
277
+ }
278
+ return s3Location;
370
279
  }
371
-
372
- if (S3_ENDPOINT) {
373
- cfg.endpoint = S3_ENDPOINT;
280
+ #getFileExtBase64(str) {
281
+ const type = str.charAt(0);
282
+ return ({
283
+ '/': 'jpg',
284
+ i: 'png',
285
+ R: 'gif',
286
+ U: 'webp',
287
+ }[type] || '');
288
+ }
289
+ #getS3Config() {
290
+ const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } = this.getConfig();
291
+ const cfg = {
292
+ region: S3_REGION,
293
+ credentials: {
294
+ accessKeyId: S3_ACCESS_KEY_ID,
295
+ secretAccessKey: S3_SECRET_ACCESS_KEY,
296
+ },
297
+ };
298
+ if (S3_FORCE_PATH_STYLE) {
299
+ cfg.forcePathStyle = !['false', '0'].includes(String(S3_FORCE_PATH_STYLE || '').toLowerCase());
300
+ }
301
+ if (S3_SESSION_TOKEN) {
302
+ cfg.credentials.sessionToken = S3_SESSION_TOKEN;
303
+ }
304
+ if (S3_ENDPOINT) {
305
+ cfg.endpoint = S3_ENDPOINT;
306
+ }
307
+ return cfg;
374
308
  }
375
-
376
- return cfg;
377
- }
378
-
379
309
  }
310
+ exports.S3Uploader = S3Uploader;
380
311
 
381
- module.exports = S3Uploader;
312
+ module.exports.S3Uploader = S3Uploader;