@testomatio/reporter 1.4.1-beta-1 → 1.4.1

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 (45) hide show
  1. package/README.md +60 -57
  2. package/lib/adapter/codecept.js +86 -27
  3. package/lib/adapter/cucumber/current.js +17 -10
  4. package/lib/adapter/cucumber/legacy.js +5 -4
  5. package/lib/adapter/cucumber.js +2 -2
  6. package/lib/adapter/cypress-plugin/index.js +53 -26
  7. package/lib/adapter/jasmine.js +2 -2
  8. package/lib/adapter/jest.js +48 -10
  9. package/lib/adapter/mocha.js +90 -10
  10. package/lib/adapter/playwright.js +68 -18
  11. package/lib/adapter/webdriver.js +47 -2
  12. package/lib/bin/reportXml.js +21 -16
  13. package/lib/bin/startTest.js +12 -12
  14. package/lib/client.js +112 -52
  15. package/lib/config.js +34 -0
  16. package/lib/constants.js +28 -1
  17. package/lib/data-storage.js +203 -0
  18. package/lib/fileUploader.js +66 -53
  19. package/lib/junit-adapter/adapter.js +0 -2
  20. package/lib/junit-adapter/csharp.js +3 -4
  21. package/lib/junit-adapter/index.js +3 -3
  22. package/lib/junit-adapter/java.js +11 -11
  23. package/lib/junit-adapter/javascript.js +1 -2
  24. package/lib/junit-adapter/python.js +12 -14
  25. package/lib/junit-adapter/ruby.js +1 -2
  26. package/lib/pipe/csv.js +35 -32
  27. package/lib/pipe/github.js +4 -3
  28. package/lib/pipe/gitlab.js +17 -10
  29. package/lib/pipe/html.js +361 -0
  30. package/lib/pipe/index.js +3 -1
  31. package/lib/pipe/testomatio.js +230 -51
  32. package/lib/reporter-functions.js +12 -9
  33. package/lib/reporter.js +8 -12
  34. package/lib/services/artifacts.js +57 -0
  35. package/lib/services/index.js +13 -0
  36. package/lib/{storages/key-value-storage.js → services/key-values.js} +19 -19
  37. package/lib/{storages → services}/logger.js +58 -37
  38. package/lib/template/emptyData.svg +23 -0
  39. package/lib/template/testomatio.hbs +1421 -0
  40. package/lib/utils/pipe_utils.js +73 -79
  41. package/lib/utils/utils.js +53 -40
  42. package/lib/xmlReader.js +113 -105
  43. package/package.json +13 -7
  44. package/lib/storages/artifact-storage.js +0 -70
  45. package/lib/storages/data-storage.js +0 -307
@@ -1,14 +1,18 @@
1
1
  const debug = require('debug')('@testomatio/reporter:pipe:testomatio');
2
2
  const chalk = require('chalk');
3
+ // Retry interceptor function
4
+ const axiosRetry = require('axios-retry');
5
+ // Default axios instance
3
6
  const axios = require('axios');
4
7
  const JsonCycle = require('json-cycle');
5
- const { APP_PREFIX, STATUS } = require('../constants');
8
+
9
+ const { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } = require('../constants');
6
10
  const { isValidUrl, foundedTestLog } = require('../utils/utils');
7
- const { parseFilterParams, generateFilterRequestParams, setS3Credentials, } = require('../utils/pipe_utils');
11
+ const { parseFilterParams, generateFilterRequestParams, setS3Credentials } = require('../utils/pipe_utils');
12
+ const config = require('../config');
8
13
 
9
- const { TESTOMATIO_RUN } = process.env;
10
- if (TESTOMATIO_RUN) {
11
- process.env.runId = TESTOMATIO_RUN;
14
+ if (process.env.TESTOMATIO_RUN) {
15
+ process.env.runId = process.env.TESTOMATIO_RUN;
12
16
  }
13
17
 
14
18
  /**
@@ -19,9 +23,20 @@ if (TESTOMATIO_RUN) {
19
23
  */
20
24
  class TestomatioPipe {
21
25
  constructor(params, store) {
26
+ this.batch = {
27
+ isEnabled: params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
28
+ intervalFunction: null, // will be created in createRun by setInterval function
29
+ intervalTime: 5000, // how often tests are sent
30
+ tests: [], // array of tests in batch
31
+ batchIndex: 0, // represents the current batch index (starts from 1 and increments by 1 for each batch)
32
+ };
33
+ this.retriesTimestamps = [];
34
+ this.reportingCanceledDueToReqFailures = false;
35
+ this.notReportedTestsCount = 0;
36
+
22
37
  this.isEnabled = false;
23
38
  this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
24
- this.apiKey = params.apiKey || process.env.TESTOMATIO;
39
+ this.apiKey = params.apiKey || config.TESTOMATIO;
25
40
  debug('Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
26
41
  if (!this.apiKey) {
27
42
  return;
@@ -33,10 +48,37 @@ class TestomatioPipe {
33
48
  this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
34
49
  this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
35
50
  this.env = process.env.TESTOMATIO_ENV;
36
-
51
+ this.label = process.env.TESTOMATIO_LABEL;
52
+ // Create a new instance of axios with a custom config
37
53
  this.axios = axios.create({
38
54
  baseURL: `${this.url.trim()}`,
39
- timeout: 30000
55
+ timeout: AXIOS_TIMEOUT,
56
+ });
57
+
58
+ // Pass the axios instance to the retry function
59
+ axiosRetry(this.axios, {
60
+ // do not use retries for unit tests
61
+ retries: REPORTER_REQUEST_RETRIES.retriesPerRequest, // Number of retries
62
+ shouldResetTimeout: true,
63
+ retryCondition: error => {
64
+ if (!error.response) return false;
65
+ switch (error.response?.status) {
66
+ case 400: // Bad request (probably wrong API key)
67
+ case 404: // Test not matched
68
+ case 429: // Rate limit exceeded
69
+ case 500: // Internal server error
70
+ return false;
71
+ default:
72
+ break;
73
+ }
74
+ return error.response?.status >= 401; // Retry on 401+ and 5xx
75
+ },
76
+ retryDelay: () => REPORTER_REQUEST_RETRIES.retryTimeout, // sum = 15sec
77
+ onRetry: async (retryCount, error) => {
78
+ this.retriesTimestamps.push(Date.now());
79
+
80
+ debug(`${error.message || `Request failed ${error.status}`}. Retry #${retryCount} ...`);
81
+ },
40
82
  });
41
83
 
42
84
  this.isEnabled = true;
@@ -56,7 +98,7 @@ class TestomatioPipe {
56
98
  /**
57
99
  * Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
58
100
  * @param {Object} opts - The options for preparing the test grepList.
59
- * @returns {Promise<string[]>} - An array containing the retrieved
101
+ * @returns {Promise<string[]>} - An array containing the retrieved
60
102
  * test grepList, or an empty array if no tests are found or the request is disabled.
61
103
  * @throws {Error} - Throws an error if there was a problem while making the request.
62
104
  */
@@ -69,7 +111,7 @@ class TestomatioPipe {
69
111
  const q = generateFilterRequestParams({
70
112
  type,
71
113
  id,
72
- apiKey: this.apiKey.trim()
114
+ apiKey: this.apiKey.trim(),
73
115
  });
74
116
 
75
117
  if (!q) {
@@ -83,23 +125,23 @@ class TestomatioPipe {
83
125
  foundedTestLog(APP_PREFIX, data.tests);
84
126
  return data.tests;
85
127
  }
86
-
128
+
87
129
  console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
88
- }
89
- catch (err) {
90
- console.error(
91
- APP_PREFIX,
92
- `🚩 Error getting Testomat.io test grepList: ${err}`,
93
- );
130
+ } catch (err) {
131
+ console.error(APP_PREFIX, `🚩 Error getting Testomat.io test grepList: ${err}`);
94
132
  }
95
133
  }
96
134
 
97
135
  /**
136
+ * Creates a new run on Testomat.io
137
+ * @param {{isBatchEnabled?: boolean}} params
98
138
  * @returns Promise<void>
99
139
  */
100
- async createRun() {
140
+ async createRun(params = {}) {
141
+ this.batch.isEnabled = params.isBatchEnabled ?? this.batch.isEnabled;
101
142
  debug('Creating run...');
102
143
  if (!this.isEnabled) return;
144
+ if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.#batchUpload, this.batch.intervalTime);
103
145
 
104
146
  let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
105
147
 
@@ -131,6 +173,7 @@ class TestomatioPipe {
131
173
  jira_id: this.jiraId,
132
174
  env: this.env,
133
175
  title: this.title,
176
+ label: this.label,
134
177
  shared_run: this.sharedRun,
135
178
  }).filter(([, value]) => !!value),
136
179
  );
@@ -148,10 +191,13 @@ class TestomatioPipe {
148
191
  maxContentLength: Infinity,
149
192
  maxBodyLength: Infinity,
150
193
  });
194
+
151
195
  this.runId = resp.data.uid;
152
196
  this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
153
197
  this.runPublicUrl = resp.data.public_url;
198
+
154
199
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
200
+
155
201
  this.store.runUrl = this.runUrl;
156
202
  this.store.runPublicUrl = this.runPublicUrl;
157
203
  this.store.runId = this.runId;
@@ -161,57 +207,146 @@ class TestomatioPipe {
161
207
  } catch (err) {
162
208
  console.error(
163
209
  APP_PREFIX,
164
- 'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
165
- err,
210
+ 'Error creating Testomat.io report, please check if your API key is valid. Skipping report | ',
211
+ err?.response?.statusText || err?.status || err.message,
166
212
  );
213
+ printCreateIssue(err);
167
214
  }
168
215
  debug('"createRun" function finished');
169
216
  }
170
217
 
171
218
  /**
172
- *
173
- * @param testData data
174
- * @returns
219
+ * Decides whether to skip test reporting in case of too many request failures
220
+ * @param {TestData} testData
221
+ * @returns {boolean}
175
222
  */
176
- addTest(data) {
177
- debug('Adding test...');
223
+ #cancelTestReportingInCaseOfTooManyReqFailures(testData) {
224
+ if (this.reportingCanceledDueToReqFailures) return true;
225
+
226
+ const retriesCountWithinTime = this.retriesTimestamps.filter(
227
+ timestamp => Date.now() - timestamp < REPORTER_REQUEST_RETRIES.withinTimeSeconds * 1000,
228
+ ).length;
229
+ debug(`${retriesCountWithinTime} failed requests within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s`);
230
+
231
+ if (retriesCountWithinTime > REPORTER_REQUEST_RETRIES.maxTotalRetries) {
232
+ const errorMessage = chalk.yellow(
233
+ `${retriesCountWithinTime} requests were failed within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s,\
234
+ reporting for test "${testData.title}" to Testomat is skipped`,
235
+ );
236
+ console.warn(`${APP_PREFIX} ${errorMessage}`);
237
+
238
+ this.reportingCanceledDueToReqFailures = true;
239
+ this.notReportedTestsCount++;
240
+
241
+ return true;
242
+ }
243
+
244
+ return false;
245
+ }
246
+
247
+ #uploadSingleTest = async data => {
178
248
  if (!this.isEnabled) return;
179
249
  if (!this.runId) return;
250
+ if (this.#cancelTestReportingInCaseOfTooManyReqFailures(data)) return;
251
+
180
252
  data.api_key = this.apiKey;
181
253
  data.create = this.createNewTests;
254
+
255
+ if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
256
+ data.stack = null;
257
+ }
258
+
182
259
  const json = JsonCycle.stringify(data);
183
260
 
184
- return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, {
185
- maxContentLength: Infinity,
186
- maxBodyLength: Infinity,
187
- headers: {
188
- // Overwrite Axios's automatically set Content-Type
189
- 'Content-Type': 'application/json',
190
- },
191
- })
192
- .catch(err => {
193
- if (err.response) {
194
- if (err.response.status >= 400) {
195
- const responseData = err.response.data || { message: '' };
261
+ debug('Adding test', json);
262
+
263
+ return this.axios
264
+ .post(`/api/reporter/${this.runId}/testrun`, json, axiosAddTestrunRequestConfig)
265
+ .catch(err => {
266
+ if (err.response) {
267
+ if (err.response.status >= 400) {
268
+ const responseData = err.response.data || { message: '' };
269
+ console.log(
270
+ APP_PREFIX,
271
+ chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
272
+ chalk.grey(data?.title || ''),
273
+ );
274
+ if (err.response?.data?.message?.includes('could not be matched')) {
275
+ this.hasUnmatchedTests = true;
276
+ }
277
+ return;
278
+ }
196
279
  console.log(
197
280
  APP_PREFIX,
198
- chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
199
- chalk.grey(data?.title || ''),
281
+ chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
282
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
200
283
  );
201
- if (err.response.data.message.includes('could not be matched')) {
202
- this.hasUnmatchedTests = true;
284
+ printCreateIssue(err);
285
+ } else {
286
+ console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
287
+ }
288
+ });
289
+ };
290
+
291
+
292
+ /**
293
+ * Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
294
+ */
295
+ #batchUpload = async () => {
296
+ this.batch.batchIndex++;
297
+ if (!this.batch.isEnabled) return;
298
+ if (!this.batch.tests.length) return;
299
+
300
+ // get tests from batch and clear batch
301
+ const testsToSend = this.batch.tests.splice(0);
302
+ debug('📨 Batch upload', testsToSend.length, 'tests');
303
+
304
+ return this.axios
305
+ .post(
306
+ `/api/reporter/${this.runId}/testrun`,
307
+ { api_key: this.apiKey, tests: testsToSend, batch_index: this.batch.batchIndex },
308
+ axiosAddTestrunRequestConfig,
309
+ )
310
+ .catch(err => {
311
+ if (err.response) {
312
+ if (err.response.status >= 400) {
313
+ const responseData = err.response.data || { message: '' };
314
+ console.log(
315
+ APP_PREFIX,
316
+ chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
317
+ // chalk.grey(data?.title || ''),
318
+ );
319
+ if (err.response?.data?.message?.includes('could not be matched')) {
320
+ this.hasUnmatchedTests = true;
321
+ }
322
+ return;
203
323
  }
204
- return;
324
+ console.log(
325
+ APP_PREFIX,
326
+ chalk.yellow(`Warning: (${err.response?.status})`),
327
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
328
+ );
329
+ printCreateIssue(err);
330
+ } else {
331
+ console.log(APP_PREFIX, "Report couldn't be processed", err);
205
332
  }
206
- console.log(
207
- APP_PREFIX,
208
- chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
209
- `Report couldn't be processed: ${err?.response?.data?.message}`,
210
- );
211
- } else {
212
- console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
213
- }
214
- });
333
+ });
334
+ };
335
+
336
+ /**
337
+ * Adds a test to the batch uploader (or reports a single test if batch uploading is disabled)
338
+ */
339
+ addTest(data) {
340
+ if (!this.isEnabled) return;
341
+ if (!this.runId) return;
342
+ data.api_key = this.apiKey;
343
+ data.create = this.createNewTests;
344
+
345
+ if (!this.batch.isEnabled) this.#uploadSingleTest(data);
346
+ else this.batch.tests.push(data);
347
+
348
+ // if test is added after run already finished
349
+ if (!this.batch.intervalFunction) this.#batchUpload();
215
350
  }
216
351
 
217
352
  /**
@@ -219,8 +354,19 @@ class TestomatioPipe {
219
354
  * @returns
220
355
  */
221
356
  async finishRun(params) {
357
+ this.#batchUpload();
358
+ if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
359
+
222
360
  debug('Finishing run...');
223
361
  if (!this.isEnabled) return;
362
+ debug('Finishing run...');
363
+
364
+ if (this.reportingCanceledDueToReqFailures) {
365
+ const errorMessage = chalk.red(
366
+ `⚠️ Due to request failures, ${this.notReportedTestsCount} test(s) were not reported to Testomat.io`,
367
+ );
368
+ console.warn(`${APP_PREFIX} ${errorMessage}`);
369
+ }
224
370
 
225
371
  const { status, parallel } = params;
226
372
 
@@ -277,6 +423,7 @@ class TestomatioPipe {
277
423
  }
278
424
  } catch (err) {
279
425
  console.log(APP_PREFIX, 'Error updating status, skipping...', err);
426
+ printCreateIssue(err);
280
427
  }
281
428
  debug('Run finished');
282
429
  }
@@ -286,4 +433,36 @@ class TestomatioPipe {
286
433
  }
287
434
  }
288
435
 
436
+ let registeredErrorHints = false;
437
+ function printCreateIssue(err) {
438
+ if (registeredErrorHints) return;
439
+ registeredErrorHints = true;
440
+ process.on('exit', () => {
441
+ console.log();
442
+ console.log(APP_PREFIX, 'There was an error reporting to Testomat.io:');
443
+ console.log(
444
+ APP_PREFIX,
445
+ 'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new',
446
+ ); // eslint-disable-line max-len
447
+ console.log(APP_PREFIX, 'Provide this information:');
448
+ console.log('Error:', err.message || err.code);
449
+ if (!err.config) return;
450
+
451
+ const time = new Date().toUTCString();
452
+ const { data, url, baseURL, method } = err?.config || {};
453
+ console.log('```js');
454
+ console.log({ data: data?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
455
+ console.log('```');
456
+ });
457
+ }
458
+
459
+ const axiosAddTestrunRequestConfig = {
460
+ maxContentLength: Infinity,
461
+ maxBodyLength: Infinity,
462
+ headers: {
463
+ // Overwrite Axios's automatically set Content-Type
464
+ 'Content-Type': 'application/json',
465
+ },
466
+ };
467
+
289
468
  module.exports = TestomatioPipe;
@@ -1,6 +1,9 @@
1
- const logger = require('./storages/logger');
2
- const artifactStorage = require('./storages/artifact-storage');
3
- const keyValueStorage = require('./storages/key-value-storage');
1
+ const { services } = require('./services');
2
+ const { initPlaywrightForStorage } = require('./adapter/playwright');
3
+
4
+ if (process.env.PLAYWRIGHT_TEST_BASE_URL) {
5
+ initPlaywrightForStorage();
6
+ }
4
7
 
5
8
  /**
6
9
  * Stores path to file as artifact and uploads it to the S3 storage
@@ -8,7 +11,7 @@ const keyValueStorage = require('./storages/key-value-storage');
8
11
  */
9
12
  function saveArtifact(data, context = null) {
10
13
  if (!data) return;
11
- artifactStorage.put(data, context);
14
+ services.artifacts.put(data, context);
12
15
  }
13
16
 
14
17
  /**
@@ -16,23 +19,23 @@ function saveArtifact(data, context = null) {
16
19
  * @param {...any} args
17
20
  */
18
21
  function logMessage(...args) {
19
- logger.log(...args);
22
+ services.logger._templateLiteralLog(...args);
20
23
  }
21
24
 
22
25
  /**
23
26
  * Similar to "log" function but marks message in report as a step
24
- * @param {*} message
27
+ * @param {*} message
25
28
  */
26
29
  function addStep(message) {
27
- logger.step(message);
30
+ services.logger.step(message);
28
31
  }
29
32
 
30
33
  /**
31
34
  * Add key-value pair(s) to the test report
32
- * @param {*} keyValue
35
+ * @param {*} keyValue
33
36
  */
34
37
  function setKeyValue(keyValue) {
35
- keyValueStorage.put(keyValue);
38
+ services.keyValues.put(keyValue);
36
39
  }
37
40
 
38
41
  module.exports = {
package/lib/reporter.js CHANGED
@@ -1,23 +1,19 @@
1
- const logger = require('./storages/logger');
2
1
  const TestomatClient = require('./client');
3
2
  const TRConstants = require('./constants');
3
+ const { services } = require('./services');
4
4
 
5
- const log = logger.templateLiteralLog.bind(logger);
6
- const step = logger.step.bind(logger);
7
5
  const reporterFunctions = require('./reporter-functions');
8
6
 
9
7
  module.exports = {
10
- logger,
11
- testomatioLogger: logger,
12
- log,
13
- step,
14
- TestomatClient,
15
- TRConstants,
16
- };
8
+ // TODO: deprecate in future; use log or testomat.log
9
+ testomatioLogger: services.logger,
17
10
 
18
- module.exports.testomat = {
19
11
  artifact: reporterFunctions.artifact,
20
12
  log: reporterFunctions.log,
21
- step: reporterFunctions.step,
13
+ logger: services.logger,
22
14
  meta: reporterFunctions.keyValue,
15
+ step: reporterFunctions.step,
16
+
17
+ TestomatClient,
18
+ TRConstants,
23
19
  };
@@ -0,0 +1,57 @@
1
+ const debug = require('debug')('@testomatio/reporter:services-artifacts');
2
+ const { dataStorage } = require('../data-storage');
3
+
4
+ /**
5
+ * Artifact storage is supposed to store file paths
6
+ */
7
+ class ArtifactStorage {
8
+ static #instance;
9
+
10
+ /**
11
+ * Singleton
12
+ * @returns {ArtifactStorage}
13
+ */
14
+ static getInstance() {
15
+ if (!this.#instance) {
16
+ this.#instance = new ArtifactStorage();
17
+ }
18
+ return this.#instance;
19
+ }
20
+
21
+ /**
22
+ * Stores path to file as artifact and uploads it to the S3 storage
23
+ * @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
24
+ * @param {*} context testId or test title
25
+ */
26
+ put(data, context = null) {
27
+ if (!data) return;
28
+ debug('Save artifact:', data);
29
+ dataStorage.putData('artifact', data, context);
30
+ }
31
+
32
+ /**
33
+ * Returns list of artifacts to upload
34
+ * @param {*} context testId or test context from test runner
35
+ * @returns {(string | {path: string, type: string, name: string})[]}
36
+ */
37
+ get(context) {
38
+ let artifacts = dataStorage.getData('artifact', context);
39
+ if (!artifacts || !artifacts.length) return [];
40
+
41
+ artifacts = artifacts.map(artifactData => {
42
+ // artifact could be an object ({type, path, name} props) or string (just path)
43
+ let artifact;
44
+ try {
45
+ artifact = JSON.parse(artifactData);
46
+ } catch (e) {
47
+ artifact = artifactData;
48
+ }
49
+ return artifact;
50
+ });
51
+ artifacts = artifacts.filter(artifact => !!artifact);
52
+ debug(`Artifacts for test ${context}:`, artifacts);
53
+ return artifacts.length ? artifacts : [];
54
+ }
55
+ }
56
+
57
+ module.exports.artifactStorage = ArtifactStorage.getInstance();
@@ -0,0 +1,13 @@
1
+ const { logger } = require('./logger');
2
+ const { artifactStorage } = require('./artifacts');
3
+ const { keyValueStorage } = require('./key-values');
4
+ const { dataStorage } = require('../data-storage');
5
+
6
+ module.exports.services = {
7
+ logger,
8
+ artifacts: artifactStorage,
9
+ keyValues: keyValueStorage,
10
+ setContext: context => {
11
+ dataStorage.setContext(context);
12
+ },
13
+ };
@@ -1,24 +1,28 @@
1
- const debug = require('debug')('@testomatio/reporter:key-value-storage');
2
- const DataStorage = require('./data-storage');
1
+ const debug = require('debug')('@testomatio/reporter:services-key-value');
2
+ const { dataStorage } = require('../data-storage');
3
3
 
4
4
  class KeyValueStorage {
5
- constructor() {
6
- this.dataStorage = new DataStorage('keyvalue');
5
+ static #instance;
7
6
 
8
- // singleton
9
- if (!KeyValueStorage.instance) {
10
- KeyValueStorage.instance = this;
7
+ /**
8
+ *
9
+ * @returns {KeyValueStorage}
10
+ */
11
+ static getInstance() {
12
+ if (!this.#instance) {
13
+ this.#instance = new KeyValueStorage();
11
14
  }
15
+ return this.#instance;
12
16
  }
13
17
 
14
18
  /**
15
19
  * Stores key-value pair and passes it to reporter
16
20
  * @param {{key: string}} keyValue - key-value pair(s) as object
17
- * @param {*} context testId or test title
21
+ * @param {*} context - full test title
18
22
  */
19
23
  put(keyValue, context = null) {
20
24
  if (!keyValue) return;
21
- this.dataStorage.putData(keyValue, context);
25
+ dataStorage.putData('keyvalue', keyValue, context);
22
26
  }
23
27
 
24
28
  #isKeyValueObject(smth) {
@@ -28,13 +32,11 @@ class KeyValueStorage {
28
32
  /**
29
33
  * Returns key-values pairs for the test as object
30
34
  * @param {*} context testId or test context from test runner
31
- * @returns {Object} key-values pairs as object, e.g. {priority: 'high', browser: 'chrome'}
35
+ * @returns {{[key: string]: string} | {}} key-values pairs as object, e.g. {priority: 'high', browser: 'chrome'}
32
36
  */
33
- get(context) {
34
- if (!context) return null;
35
-
36
- const keyValuesList = this.dataStorage.getData(context);
37
- if (!keyValuesList || !keyValuesList?.length) return null;
37
+ get(context = null) {
38
+ const keyValuesList = dataStorage.getData('keyvalue', context);
39
+ if (!keyValuesList || !keyValuesList?.length) return {};
38
40
 
39
41
  const keyValues = {};
40
42
  for (const keyValue of keyValuesList) {
@@ -49,10 +51,8 @@ class KeyValueStorage {
49
51
  }
50
52
  }
51
53
 
52
- return Object.keys(keyValues).length ? keyValues : null;
54
+ return keyValues;
53
55
  }
54
56
  }
55
57
 
56
- KeyValueStorage.instance = null;
57
-
58
- module.exports = new KeyValueStorage();
58
+ module.exports.keyValueStorage = KeyValueStorage.getInstance();