@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
@@ -0,0 +1,371 @@
1
+ import createDebugMessages from 'debug';
2
+ import { S3 } from '@aws-sdk/client-s3';
3
+ import { Upload } from '@aws-sdk/lib-storage';
4
+ import fs from 'fs';
5
+ import os from 'os';
6
+ import path from 'path';
7
+ import promiseRetry from 'promise-retry';
8
+ import pc from 'picocolors';
9
+ import { APP_PREFIX } from './constants.js';
10
+ import { filesize as prettyBytes } from 'filesize';
11
+
12
+ const debug = createDebugMessages('@testomatio/reporter:file-uploader');
13
+
14
+ export class S3Uploader {
15
+ constructor() {
16
+ this.isEnabled = undefined;
17
+ this.storeEnabled = true;
18
+ this.config = undefined;
19
+
20
+ /**
21
+ * @type {{path: string, size: number}[]}
22
+ */
23
+ this.skippedUploads = [];
24
+ this.failedUploads = [];
25
+ /**
26
+ * @type {{path: string, size: number, link: string}[]}
27
+ */
28
+ this.successfulUploads = [];
29
+
30
+ this.configKeys = [
31
+ 'S3_ENDPOINT',
32
+ 'S3_REGION',
33
+ 'S3_BUCKET',
34
+ 'S3_ACCESS_KEY_ID',
35
+ 'S3_SECRET_ACCESS_KEY',
36
+ 'S3_SESSION_TOKEN',
37
+ 'S3_FORCE_PATH_STYLE',
38
+ 'TESTOMATIO_DISABLE_ARTIFACTS',
39
+ 'TESTOMATIO_PRIVATE_ARTIFACTS',
40
+ 'TESTOMATIO_ARTIFACT_MAX_SIZE_MB',
41
+ ];
42
+ }
43
+
44
+ resetConfig() {
45
+ this.config = undefined;
46
+ this.isEnabled = undefined;
47
+ }
48
+
49
+ /**
50
+ *
51
+ * @returns {Record<string, string>}
52
+ */
53
+ getConfig() {
54
+ if (this.config) return this.config;
55
+ this.config = this.configKeys.reduce((acc, key) => {
56
+ acc[key] = process.env[key];
57
+ return acc;
58
+ }, {});
59
+ return this.config;
60
+ }
61
+
62
+ getMaskedConfig() {
63
+ return Object.fromEntries(
64
+ Object.entries(this.getConfig()).map(([key, value]) => [
65
+ key,
66
+ key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
67
+ ]),
68
+ );
69
+ }
70
+
71
+ checkEnabled() {
72
+ if (this.isEnabled !== undefined) return this.isEnabled;
73
+
74
+ const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
75
+ if (!S3_BUCKET) debug(`Artifacts uploading is disabled because S3_BUCKET is not set`);
76
+ this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
77
+
78
+ if (this.isEnabled) debug('S3 uploader is enabled');
79
+ debug(this.getMaskedConfig());
80
+
81
+ return this.isEnabled;
82
+ }
83
+
84
+ enableLogStorage() {
85
+ this.storeEnabled = true;
86
+ }
87
+
88
+ disableLogStorage() {
89
+ this.storeEnabled = false;
90
+ }
91
+
92
+ /**
93
+ *
94
+ * @param {*} Body
95
+ * @param {*} Key
96
+ * @param {{path: string, size?: number}} file
97
+ * @returns
98
+ */
99
+ async #uploadToS3(Body, Key, file) {
100
+ const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
101
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
102
+
103
+ if (!S3_BUCKET || !Body) {
104
+ console.log(
105
+ APP_PREFIX,
106
+ pc.bold(pc.red(`Failed uploading '${Key}'. Please check S3 credentials`)),
107
+ this.getMaskedConfig(),
108
+ );
109
+ return;
110
+ }
111
+
112
+ debug('Uploading to S3:', Key);
113
+
114
+ const s3Config = this.#getS3Config();
115
+ const s3 = new S3(s3Config);
116
+ const params = {
117
+ Bucket: S3_BUCKET,
118
+ Key,
119
+ Body,
120
+ };
121
+ // disable ACL for I AM roles
122
+ if (!s3Config.credentials.sessionToken) {
123
+ params.ACL = ACL;
124
+ }
125
+
126
+ try {
127
+ const upload = new Upload({ client: s3, params });
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());
137
+ }
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 [];
153
+ }
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 [];
165
+ }
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, '');
178
+ }
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;
191
+ }
192
+
193
+ if (typeof filePath === 'string' && !path.isAbsolute(filePath)) {
194
+ filePath = path.join(process.cwd(), filePath);
195
+ }
196
+
197
+ const data = { rid, file: filePath, uploaded };
198
+ const jsonLine = `${JSON.stringify(data)}\n`;
199
+ fs.appendFileSync(tempFilePath, jsonLine);
200
+ }
201
+
202
+ /**
203
+ * @param {*} filePath
204
+ * @param {*} pathInS3 contains runId, rid and filename
205
+ * @returns
206
+ */
207
+ async uploadFileByPath(filePath, pathInS3) {
208
+ // sometimes artifacts uploading started before createRun function completion
209
+ this.isEnabled = this.isEnabled ?? this.checkEnabled();
210
+
211
+ const [runId, rid] = pathInS3;
212
+
213
+ if (!filePath) return;
214
+
215
+ let fileSize = null;
216
+ let fileSizeInMb = null;
217
+
218
+ try {
219
+ // file may not exist
220
+ fileSize = fs.statSync(filePath).size;
221
+ fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
222
+ } catch (e) {
223
+ debug(`File ${filePath} does not exist`);
224
+ }
225
+
226
+ if (!this.isEnabled) {
227
+ this.storeUploadedFile(filePath, runId, rid, false);
228
+ this.skippedUploads.push({ path: filePath, size: fileSize });
229
+ return;
230
+ }
231
+
232
+ const { S3_BUCKET, TESTOMATIO_ARTIFACT_MAX_SIZE_MB } = this.getConfig();
233
+
234
+ debug('Started upload', filePath, 'to', S3_BUCKET);
235
+
236
+ const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
237
+
238
+ if (!isFileExist) {
239
+ console.error(pc.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
240
+ return;
241
+ }
242
+
243
+ // skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
244
+ if (
245
+ this.storeEnabled &&
246
+ TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
247
+ fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)
248
+ ) {
249
+ const skippedArtifact = { path: filePath, size: fileSize };
250
+ this.storeUploadedFile(filePath, runId, rid, false);
251
+ this.skippedUploads.push(skippedArtifact);
252
+ debug(pc.yellow(`Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size. Skipping.`));
253
+ return;
254
+ }
255
+ debug('File:', filePath, 'exists, size:', prettyBytes(fileSize));
256
+
257
+ const fileStream = fs.createReadStream(filePath);
258
+ const Key = pathInS3.join('/');
259
+
260
+ const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
261
+
262
+ this.storeUploadedFile(filePath, runId, rid, !!link);
263
+
264
+ return link;
265
+ }
266
+
267
+ /**
268
+ * @param {Buffer} buffer
269
+ * @param {string[]} pathInS3
270
+ * @returns
271
+ */
272
+ async uploadFileAsBuffer(buffer, pathInS3) {
273
+ if (!this.isEnabled) return;
274
+
275
+ let Key = pathInS3.join('/');
276
+ const ext = this.#getFileExtBase64(buffer);
277
+
278
+ if (ext) {
279
+ Key = `${Key}.${ext}`;
280
+ }
281
+
282
+ return this.#uploadToS3(buffer, Key, { path: Key });
283
+ }
284
+
285
+ async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
286
+ return promiseRetry(
287
+ async (retry, number) => {
288
+ try {
289
+ fs.accessSync(filePath);
290
+ return true;
291
+ } catch (err) {
292
+ if (number === attempts) {
293
+ return false;
294
+ }
295
+ debug(`File not found, retrying (attempt ${number}/${attempts})`);
296
+ await new Promise(resolve => {
297
+ setTimeout(resolve, intervalMs);
298
+ });
299
+ retry(err);
300
+ }
301
+ },
302
+ {
303
+ retries: attempts,
304
+ minTimeout: intervalMs,
305
+ maxTimeout: intervalMs,
306
+ },
307
+ );
308
+ }
309
+
310
+ async getS3LocationLink(out) {
311
+ const response = await out.done();
312
+
313
+ let s3Location = response?.Location?.trim();
314
+
315
+ if (!s3Location) {
316
+ s3Location = out?.singleUploadResult?.Location;
317
+ debug('Uploaded singleUploadResult.Location', s3Location);
318
+
319
+ if (!s3Location) {
320
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
321
+ }
322
+ }
323
+
324
+ // Normalize the URL
325
+ if (!s3Location.startsWith('http')) {
326
+ s3Location = `https://${s3Location}`;
327
+ }
328
+
329
+ return s3Location;
330
+ }
331
+
332
+ #getFileExtBase64(str) {
333
+ const type = str.charAt(0);
334
+
335
+ return (
336
+ {
337
+ '/': 'jpg',
338
+ i: 'png',
339
+ R: 'gif',
340
+ U: 'webp',
341
+ }[type] || ''
342
+ );
343
+ }
344
+
345
+ #getS3Config() {
346
+ const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
347
+ this.getConfig();
348
+
349
+ const cfg = {
350
+ region: S3_REGION,
351
+ credentials: {
352
+ accessKeyId: S3_ACCESS_KEY_ID,
353
+ secretAccessKey: S3_SECRET_ACCESS_KEY,
354
+ },
355
+ };
356
+
357
+ if (S3_FORCE_PATH_STYLE) {
358
+ cfg.forcePathStyle = !['false', '0'].includes(String(S3_FORCE_PATH_STYLE || '').toLowerCase());
359
+ }
360
+
361
+ if (S3_SESSION_TOKEN) {
362
+ cfg.credentials.sessionToken = S3_SESSION_TOKEN;
363
+ }
364
+
365
+ if (S3_ENDPOINT) {
366
+ cfg.endpoint = S3_ENDPOINT;
367
+ }
368
+
369
+ return cfg;
370
+ }
371
+ }
@@ -0,0 +1,119 @@
1
+ import { APP_PREFIX } from '../constants.js';
2
+
3
+ /**
4
+ * Set S3 credentials from the provided artifacts object.
5
+ * @param {Object} artifacts - The artifacts object containing S3 credentials.
6
+ */
7
+ function setS3Credentials(artifacts) {
8
+ if (!Object.keys(artifacts).length) return;
9
+
10
+ console.log(APP_PREFIX, 'S3 credentials obtained from Testomat.io...');
11
+
12
+ if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
13
+ if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
14
+ if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
15
+ if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
16
+ if (artifacts.SESSION_TOKEN) process.env.S3_SESSION_TOKEN = artifacts.SESSION_TOKEN;
17
+ if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
18
+ // endpoint is not received from the server; and shuld be empty if IAM used (credentails obtained from the testomat)
19
+ process.env.S3_ENDPOINT = artifacts.ENDPOINT || '';
20
+ }
21
+
22
+ /**
23
+ * Generates mode request parameters based on the input params.
24
+ * @param {{type: string, id?: string, apiKey: string}} params - The input parameters for the request.
25
+ * @returns {Object|null} - An object containing the generated request parameters, or null if the type is invalid.
26
+ */
27
+ function generateFilterRequestParams(params) {
28
+ const { type, id, apiKey } = params;
29
+
30
+ if (!type) {
31
+ return;
32
+ }
33
+
34
+ if (!id) {
35
+ console.error(APP_PREFIX, `Please make sure your settings "${type.toUpperCase()}"= "${id}" is correct!`);
36
+ return;
37
+ }
38
+
39
+ return {
40
+ params: {
41
+ type,
42
+ id: encodeURIComponent(id),
43
+ api_key: apiKey,
44
+ },
45
+ responseType: 'json',
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Parse filter parameters from a string in the format "type=id".
51
+ * @param {string} opts - The input string containing the filter parameters.
52
+ * @returns {Object} An object containing the parsed filter parameters.
53
+ * The object has properties "type" and "id".
54
+ */
55
+ function parseFilterParams(opts) {
56
+ const [type, id] = opts.split('=');
57
+ const validType = updateFilterType(type);
58
+
59
+ return {
60
+ type: validType,
61
+ id,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Update and validate the filter type.
67
+ * @param {string} type - The original filter type.
68
+ * @returns {string|undefined} The updated and validated filter type.
69
+ * Returns undefined if the type is not valid.
70
+ */
71
+ function updateFilterType(type) {
72
+ const typeLowerCase = type.toLowerCase();
73
+
74
+ const filterTypes = ['tag-name', 'plan-id', 'label', 'jira-ticket'];
75
+
76
+ const filterApi = [
77
+ 'tag',
78
+ 'plan',
79
+ 'label',
80
+ 'jira',
81
+ // "ims-issue", //TODO: WIP
82
+ ];
83
+
84
+ if (!filterTypes.includes(typeLowerCase)) {
85
+ console.log(APP_PREFIX, `❗❗❗ Invalid "filter=${type}" start settings! Available option list: ${filterTypes}`);
86
+ return;
87
+ }
88
+
89
+ const index = filterTypes.indexOf(typeLowerCase);
90
+
91
+ return index !== -1 ? filterApi[index] : undefined;
92
+ }
93
+
94
+ /**
95
+ * Return an emoji based on the provided status.
96
+ * @param {string} status - The status value ('passed', 'failed', or 'skipped').
97
+ * @returns {string} - An emoji corresponding to the provided status.
98
+ */
99
+ function statusEmoji(status) {
100
+ if (status === 'passed') return '🟢';
101
+ if (status === 'failed') return '🔴';
102
+ if (status === 'skipped') return '🟡';
103
+ return '';
104
+ }
105
+
106
+ /**
107
+ * Generate a full name string based on the provided test object.
108
+ * @param {object} t - The test object.
109
+ * @returns {string} - A formatted full name string for the test object.
110
+ */
111
+ function fullName(t) {
112
+ let line = '';
113
+ if (t.suite_title) line = `${t.suite_title}: `;
114
+ line += `**${t.title}**`;
115
+ if (t.example) line += ` \`[${Object.values(t.example)}]\``;
116
+ return line;
117
+ }
118
+
119
+ export { updateFilterType, parseFilterParams, generateFilterRequestParams, setS3Credentials, statusEmoji, fullName };