@testomatio/reporter 2.0.1-beta.5-timestamp → 2.0.1-beta.7

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 (152) hide show
  1. package/README.md +1 -0
  2. package/lib/adapter/codecept.d.ts +2 -0
  3. package/lib/adapter/codecept.js +297 -335
  4. package/lib/adapter/cucumber/current.d.ts +14 -0
  5. package/lib/adapter/cucumber/current.js +195 -203
  6. package/lib/adapter/cucumber/legacy.d.ts +0 -0
  7. package/lib/adapter/cucumber/legacy.js +130 -155
  8. package/lib/adapter/cucumber.d.ts +2 -0
  9. package/lib/adapter/cucumber.js +5 -16
  10. package/lib/adapter/cypress-plugin/index.d.ts +2 -0
  11. package/lib/adapter/cypress-plugin/index.js +91 -105
  12. package/lib/adapter/jasmine.d.ts +11 -0
  13. package/lib/adapter/jasmine.js +54 -53
  14. package/lib/adapter/jest.d.ts +13 -0
  15. package/lib/adapter/jest.js +97 -99
  16. package/lib/adapter/mocha.d.ts +2 -0
  17. package/lib/adapter/mocha.js +112 -141
  18. package/lib/adapter/nightwatch.d.ts +4 -0
  19. package/lib/adapter/nightwatch.js +80 -0
  20. package/lib/adapter/playwright.d.ts +14 -0
  21. package/lib/adapter/playwright.js +208 -231
  22. package/lib/adapter/vitest.d.ts +35 -0
  23. package/lib/adapter/vitest.js +150 -149
  24. package/lib/adapter/webdriver.d.ts +24 -0
  25. package/lib/adapter/webdriver.js +144 -121
  26. package/lib/bin/cli.d.ts +2 -0
  27. package/lib/bin/cli.js +229 -211
  28. package/lib/bin/reportXml.d.ts +2 -0
  29. package/lib/bin/reportXml.js +51 -52
  30. package/lib/bin/startTest.d.ts +2 -0
  31. package/lib/bin/startTest.js +83 -95
  32. package/lib/bin/uploadArtifacts.d.ts +2 -0
  33. package/lib/bin/uploadArtifacts.js +56 -61
  34. package/lib/client.d.ts +76 -0
  35. package/lib/client.js +431 -465
  36. package/lib/config.d.ts +1 -0
  37. package/lib/config.js +18 -23
  38. package/lib/constants.d.ts +25 -0
  39. package/lib/constants.js +50 -44
  40. package/lib/data-storage.d.ts +34 -0
  41. package/lib/data-storage.js +216 -188
  42. package/lib/junit-adapter/adapter.d.ts +9 -0
  43. package/lib/junit-adapter/adapter.js +17 -20
  44. package/lib/junit-adapter/csharp.d.ts +5 -0
  45. package/lib/junit-adapter/csharp.js +28 -14
  46. package/lib/junit-adapter/index.d.ts +3 -0
  47. package/lib/junit-adapter/index.js +27 -25
  48. package/lib/junit-adapter/java.d.ts +5 -0
  49. package/lib/junit-adapter/java.js +41 -53
  50. package/lib/junit-adapter/javascript.d.ts +4 -0
  51. package/lib/junit-adapter/javascript.js +30 -27
  52. package/lib/junit-adapter/python.d.ts +5 -0
  53. package/lib/junit-adapter/python.js +38 -37
  54. package/lib/junit-adapter/ruby.d.ts +4 -0
  55. package/lib/junit-adapter/ruby.js +11 -8
  56. package/lib/output.d.ts +11 -0
  57. package/lib/output.js +44 -52
  58. package/lib/package.json +3 -0
  59. package/lib/pipe/bitbucket.d.ts +25 -0
  60. package/lib/pipe/bitbucket.js +223 -230
  61. package/lib/pipe/csv.d.ts +47 -0
  62. package/lib/pipe/csv.js +113 -126
  63. package/lib/pipe/debug.d.ts +29 -0
  64. package/lib/pipe/debug.js +125 -99
  65. package/lib/pipe/github.d.ts +30 -0
  66. package/lib/pipe/github.js +218 -213
  67. package/lib/pipe/gitlab.d.ts +25 -0
  68. package/lib/pipe/gitlab.js +183 -206
  69. package/lib/pipe/html.d.ts +35 -0
  70. package/lib/pipe/html.js +258 -321
  71. package/lib/pipe/index.d.ts +1 -0
  72. package/lib/pipe/index.js +94 -66
  73. package/lib/pipe/testomatio.d.ts +71 -0
  74. package/lib/pipe/testomatio.js +429 -474
  75. package/lib/replay.d.ts +31 -0
  76. package/lib/replay.js +255 -0
  77. package/lib/reporter-functions.d.ts +41 -0
  78. package/lib/reporter-functions.js +64 -26
  79. package/lib/reporter.d.ts +235 -0
  80. package/lib/reporter.js +37 -29
  81. package/lib/services/artifacts.d.ts +33 -0
  82. package/lib/services/artifacts.js +55 -51
  83. package/lib/services/index.d.ts +11 -0
  84. package/lib/services/index.js +16 -12
  85. package/lib/services/key-values.d.ts +27 -0
  86. package/lib/services/key-values.js +56 -53
  87. package/lib/services/labels.d.ts +22 -0
  88. package/lib/services/labels.js +62 -0
  89. package/lib/services/logger.d.ts +64 -0
  90. package/lib/services/logger.js +226 -245
  91. package/lib/template/testomatio.hbs +1026 -1366
  92. package/lib/uploader.d.ts +60 -0
  93. package/lib/uploader.js +295 -364
  94. package/lib/utils/pipe_utils.d.ts +41 -0
  95. package/lib/utils/pipe_utils.js +89 -85
  96. package/lib/utils/utils.d.ts +54 -0
  97. package/lib/utils/utils.js +398 -307
  98. package/lib/xmlReader.d.ts +92 -0
  99. package/lib/xmlReader.js +525 -532
  100. package/package.json +64 -21
  101. package/src/adapter/codecept.js +377 -0
  102. package/src/adapter/cucumber/current.js +228 -0
  103. package/src/adapter/cucumber/legacy.js +158 -0
  104. package/src/adapter/cucumber.js +4 -0
  105. package/src/adapter/cypress-plugin/index.js +110 -0
  106. package/src/adapter/jasmine.js +60 -0
  107. package/src/adapter/jest.js +107 -0
  108. package/src/adapter/mocha.cjs +2 -0
  109. package/src/adapter/mocha.js +156 -0
  110. package/src/adapter/nightwatch.js +88 -0
  111. package/src/adapter/playwright.js +258 -0
  112. package/src/adapter/vitest.js +183 -0
  113. package/src/adapter/webdriver.js +142 -0
  114. package/src/bin/cli.js +348 -0
  115. package/src/bin/reportXml.js +77 -0
  116. package/src/bin/startTest.js +124 -0
  117. package/src/bin/uploadArtifacts.js +91 -0
  118. package/src/client.js +519 -0
  119. package/src/config.js +30 -0
  120. package/src/constants.js +53 -0
  121. package/src/data-storage.js +204 -0
  122. package/src/junit-adapter/adapter.js +23 -0
  123. package/src/junit-adapter/csharp.js +28 -0
  124. package/src/junit-adapter/index.js +28 -0
  125. package/src/junit-adapter/java.js +58 -0
  126. package/src/junit-adapter/javascript.js +31 -0
  127. package/src/junit-adapter/python.js +42 -0
  128. package/src/junit-adapter/ruby.js +10 -0
  129. package/src/output.js +57 -0
  130. package/src/pipe/bitbucket.js +252 -0
  131. package/src/pipe/csv.js +140 -0
  132. package/src/pipe/debug.js +125 -0
  133. package/src/pipe/github.js +232 -0
  134. package/src/pipe/gitlab.js +247 -0
  135. package/src/pipe/html.js +373 -0
  136. package/src/pipe/index.js +71 -0
  137. package/src/pipe/testomatio.js +504 -0
  138. package/src/replay.js +262 -0
  139. package/src/reporter-functions.js +96 -0
  140. package/src/reporter.cjs_decprecated +21 -0
  141. package/src/reporter.js +36 -0
  142. package/src/services/artifacts.js +59 -0
  143. package/src/services/index.js +15 -0
  144. package/src/services/key-values.js +59 -0
  145. package/src/services/labels.js +59 -0
  146. package/src/services/logger.js +315 -0
  147. package/src/template/emptyData.svg +23 -0
  148. package/src/template/testomatio.hbs +1081 -0
  149. package/src/uploader.js +376 -0
  150. package/src/utils/pipe_utils.js +119 -0
  151. package/src/utils/utils.js +416 -0
  152. package/src/xmlReader.js +614 -0
package/src/replay.js ADDED
@@ -0,0 +1,262 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import TestomatClient from './client.js';
5
+ import { STATUS } from './constants.js';
6
+ import { config } from './config.js';
7
+
8
+ export class Replay {
9
+ constructor(options = {}) {
10
+ this.apiKey = options.apiKey || config.TESTOMATIO || undefined;
11
+ this.dryRun = options.dryRun || false;
12
+ this.onProgress = options.onProgress || (() => {});
13
+ this.onLog = options.onLog || console.log;
14
+ this.onError = options.onError || console.error;
15
+ }
16
+
17
+ /**
18
+ * Get the default debug file path
19
+ * @returns {string} Path to the latest debug file
20
+ */
21
+ getDefaultDebugFile() {
22
+ return path.join(os.tmpdir(), 'testomatio.debug.latest.json');
23
+ }
24
+
25
+ /**
26
+ * Parse a debug file and extract test data
27
+ * @param {string} debugFile - Path to the debug file
28
+ * @returns {Object} Parsed debug data
29
+ */
30
+ parseDebugFile(debugFile) {
31
+ if (!fs.existsSync(debugFile)) {
32
+ throw new Error(`Debug file not found: ${debugFile}`);
33
+ }
34
+
35
+ const fileContent = fs.readFileSync(debugFile, 'utf-8');
36
+ const lines = fileContent.trim().split('\n').filter(line => line.trim() !== '');
37
+
38
+ if (lines.length === 0) {
39
+ throw new Error('Debug file is empty');
40
+ }
41
+
42
+ let runParams = {};
43
+ let finishParams = {};
44
+ let parseErrors = 0;
45
+ const testsMap = new Map(); // Use Map to deduplicate by rid
46
+ const testsWithoutRid = []; // For tests without rid (backward compatibility)
47
+ const envVars = {};
48
+ let runId = null;
49
+
50
+ // Parse debug file line by line
51
+ for (const [lineIndex, line] of lines.entries()) {
52
+ try {
53
+ const logEntry = JSON.parse(line);
54
+
55
+ if (logEntry.data === 'variables' && logEntry.testomatioEnvVars) {
56
+ Object.assign(envVars, logEntry.testomatioEnvVars);
57
+ } else if (logEntry.action === 'createRun') {
58
+ runParams = logEntry.params || {};
59
+ } else if (logEntry.action === 'addTestsBatch' && logEntry.tests) {
60
+ // Extract runId if available
61
+ if (logEntry.runId && !runId) {
62
+ runId = logEntry.runId;
63
+ }
64
+ // Process each test in the batch
65
+ for (const test of logEntry.tests) {
66
+ if (test.rid) {
67
+ // Handle tests with rid (deduplicate)
68
+ const existingTest = testsMap.get(test.rid);
69
+ if (existingTest) {
70
+ // Merge test data - prioritize non-null/non-empty values
71
+ const mergedTest = { ...existingTest };
72
+ Object.keys(test).forEach(key => {
73
+ if (test[key] !== null && test[key] !== undefined) {
74
+ if (key === 'files' && Array.isArray(test[key]) && test[key].length > 0) {
75
+ // Merge files arrays
76
+ mergedTest.files = [...(existingTest.files || []), ...test[key]];
77
+ } else if (key === 'artifacts' && Array.isArray(test[key]) && test[key].length > 0) {
78
+ // Merge artifacts arrays
79
+ mergedTest.artifacts = [...(existingTest.artifacts || []), ...test[key]];
80
+ } else if (existingTest[key] === null || existingTest[key] === undefined ||
81
+ (Array.isArray(existingTest[key]) && existingTest[key].length === 0)) {
82
+ // Use new value if existing is null/undefined/empty array
83
+ mergedTest[key] = test[key];
84
+ }
85
+ }
86
+ });
87
+ testsMap.set(test.rid, mergedTest);
88
+ } else {
89
+ testsMap.set(test.rid, { ...test });
90
+ }
91
+ } else {
92
+ // Handle tests without rid (no deduplication)
93
+ testsWithoutRid.push({ ...test });
94
+ }
95
+ }
96
+ } else if (logEntry.action === 'addTest' && logEntry.testId) {
97
+ // Extract runId if available
98
+ if (logEntry.runId && !runId) {
99
+ runId = logEntry.runId;
100
+ }
101
+ const test = logEntry.testId;
102
+ if (test.rid) {
103
+ // Handle tests with rid (deduplicate)
104
+ const existingTest = testsMap.get(test.rid);
105
+ if (existingTest) {
106
+ // Merge with existing test
107
+ const mergedTest = { ...existingTest, ...test };
108
+ testsMap.set(test.rid, mergedTest);
109
+ } else {
110
+ testsMap.set(test.rid, { ...test });
111
+ }
112
+ } else {
113
+ // Handle tests without rid (no deduplication)
114
+ testsWithoutRid.push({ ...test });
115
+ }
116
+ } else if (logEntry.actions === 'finishRun') {
117
+ finishParams = logEntry.params || {};
118
+ }
119
+ } catch (err) {
120
+ parseErrors++;
121
+ if (parseErrors <= 3) {
122
+ // Only show first 3 parse errors
123
+ this.onError(`Failed to parse line ${lineIndex + 1}: ${line.substring(0, 100)}...`);
124
+ }
125
+ }
126
+ }
127
+
128
+ if (parseErrors > 3) {
129
+ this.onError(`${parseErrors - 3} more parse errors occurred`);
130
+ }
131
+
132
+ // Combine tests with rid and tests without rid
133
+ const allTests = [...Array.from(testsMap.values()), ...testsWithoutRid];
134
+
135
+ return {
136
+ runParams,
137
+ finishParams,
138
+ tests: allTests,
139
+ envVars,
140
+ parseErrors,
141
+ totalLines: lines.length,
142
+ runId
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Restore environment variables from debug data
148
+ * @param {Object} envVars - Environment variables to restore
149
+ */
150
+ restoreEnvironmentVariables(envVars) {
151
+ // Only restore env vars that aren't already set (don't override current values)
152
+ Object.keys(envVars).forEach(key => {
153
+ if (process.env[key] === undefined || process.env[key] === '') {
154
+ process.env[key] = envVars[key];
155
+ }
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Replay test data to Testomat.io
161
+ * @param {string} debugFile - Path to debug file (optional, uses default if not provided)
162
+ * @returns {Promise<Object>} Replay results
163
+ */
164
+ async replay(debugFile) {
165
+ if (!debugFile) {
166
+ debugFile = this.getDefaultDebugFile();
167
+ }
168
+
169
+ if (!this.apiKey) {
170
+ throw new Error('TESTOMATIO API key not found. Set TESTOMATIO environment variable.');
171
+ }
172
+
173
+ this.onLog(`Replaying data from debug file: ${debugFile}`);
174
+
175
+ // Parse the debug file
176
+ const debugData = this.parseDebugFile(debugFile);
177
+ const { runParams, finishParams, tests, envVars, runId } = debugData;
178
+
179
+ this.onLog(`Found ${tests.length} tests to replay`);
180
+
181
+ if (tests.length === 0) {
182
+ throw new Error('No test data found in debug file');
183
+ }
184
+
185
+ // Restore environment variables
186
+ this.restoreEnvironmentVariables(envVars);
187
+
188
+ if (this.dryRun) {
189
+ return {
190
+ success: true,
191
+ testsCount: tests.length,
192
+ runParams,
193
+ finishParams,
194
+ envVars,
195
+ runId,
196
+ dryRun: true
197
+ };
198
+ }
199
+
200
+ // Create client and restore the run
201
+ const client = new TestomatClient({
202
+ apiKey: this.apiKey,
203
+ isBatchEnabled: true,
204
+ ...runParams,
205
+ });
206
+
207
+ // Use the stored runId if available, otherwise create a new run
208
+ if (runId) {
209
+ this.onLog(`Using existing run ID: ${runId}`);
210
+ client.runId = runId;
211
+ } else {
212
+ this.onLog('Publishing to run...');
213
+ await client.createRun(runParams);
214
+ }
215
+
216
+ // Send each test result
217
+ let successCount = 0;
218
+ let failureCount = 0;
219
+
220
+ for (const [index, test] of tests.entries()) {
221
+ try {
222
+ await client.addTestRun(test.status, test);
223
+ successCount++;
224
+ this.onProgress({
225
+ current: index + 1,
226
+ total: tests.length,
227
+ test,
228
+ success: true
229
+ });
230
+ } catch (err) {
231
+ failureCount++;
232
+ this.onError(`Failed to send test ${index + 1}: ${err.message}`);
233
+ this.onProgress({
234
+ current: index + 1,
235
+ total: tests.length,
236
+ test,
237
+ success: false,
238
+ error: err.message
239
+ });
240
+ }
241
+ }
242
+
243
+ await client.updateRunStatus(finishParams.status || STATUS.FINISHED, finishParams.parallel || false);
244
+
245
+ const result = {
246
+ success: true,
247
+ testsCount: tests.length,
248
+ successCount,
249
+ failureCount,
250
+ runParams,
251
+ finishParams,
252
+ envVars,
253
+ runId: runId || client.runId
254
+ };
255
+
256
+ this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
257
+
258
+ return result;
259
+ }
260
+ }
261
+
262
+ export default Replay;
@@ -0,0 +1,96 @@
1
+ import { services } from './services/index.js';
2
+
3
+ /**
4
+ * Stores path to file as artifact and uploads it to the S3 storage
5
+ * @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
6
+ */
7
+ function saveArtifact(data, context = null) {
8
+ if (process.env.IS_PLAYWRIGHT)
9
+ throw new Error(`This function is not available in Playwright framework.
10
+ /Playwright supports artifacts out of the box`);
11
+ if (!data) return;
12
+ services.artifacts.put(data, context);
13
+ }
14
+
15
+ /**
16
+ * Attach log message(s) to the test report
17
+ * @param string
18
+ */
19
+ function logMessage(...args) {
20
+ if (process.env.IS_PLAYWRIGHT) throw new Error('This function is not available in Playwright framework');
21
+ services.logger._templateLiteralLog(...args);
22
+ }
23
+
24
+ /**
25
+ * Similar to "log" function but marks message in report as a step
26
+ * @param {string} message
27
+ */
28
+ function addStep(message) {
29
+ if (process.env.IS_PLAYWRIGHT)
30
+ throw new Error('This function is not available in Playwright framework. Use playwright steps');
31
+
32
+ services.logger.step(message);
33
+ }
34
+
35
+ /**
36
+ * Add key-value pair(s) to the test report
37
+ * @param {{[key: string]: string} | string} keyValue object { key: value } (multiple props allowed) or key (string)
38
+ * @param {string?} value
39
+ */
40
+ function setKeyValue(keyValue, value = null) {
41
+ if (process.env.IS_PLAYWRIGHT)
42
+ throw new Error('This function is not available in Playwright framework. Use test tag instead.');
43
+
44
+ if (typeof keyValue === 'string') {
45
+ keyValue = { [keyValue]: value };
46
+ }
47
+ services.keyValues.put(keyValue);
48
+ }
49
+
50
+ /**
51
+ * Add a single label to the test report
52
+ * @param {string} key - label key (e.g. 'severity', 'feature', or just 'smoke' for labels without values)
53
+ * @param {string} [value] - optional label value (e.g. 'high', 'login')
54
+ */
55
+ function setLabel(key, value = null) {
56
+ if (!key || typeof key !== 'string') {
57
+ console.warn('Label key must be a non-empty string');
58
+ return;
59
+ }
60
+
61
+ // Limit key length to 255 characters
62
+ if (key.length > 255) {
63
+ console.warn('Label key is too long, trimmed to 255 characters:', key);
64
+ key = key.substring(0, 255);
65
+ }
66
+
67
+ let labelString = key;
68
+ if (value !== null && value !== undefined && value !== '') {
69
+ if (typeof value !== 'string') {
70
+ console.warn('Label value must be a string, converting:', value);
71
+ value = String(value);
72
+ }
73
+ // Limit value length to 255 characters
74
+ if (value.length > 255) {
75
+ console.warn('Label value is too long, trimmed to 255 characters:', value);
76
+ value = value.substring(0, 255);
77
+ }
78
+ labelString = `${key}:${value}`;
79
+ }
80
+
81
+ // Limit total label length to 255 characters
82
+ if (labelString.length > 255) {
83
+ console.warn('Label is too long, trimmed to 255 characters:', labelString);
84
+ labelString = labelString.substring(0, 255);
85
+ }
86
+
87
+ services.labels.put([labelString]);
88
+ }
89
+
90
+ export default {
91
+ artifact: saveArtifact,
92
+ log: logMessage,
93
+ step: addStep,
94
+ keyValue: setKeyValue,
95
+ label: setLabel,
96
+ };
@@ -0,0 +1,21 @@
1
+ // const _reporter = require('../lib/reporter.js');
2
+
3
+
4
+ // /**
5
+ // * @typedef {import('../lib/reporter-functions.js').saveArtifact} artifact
6
+ // * @typedef {import('../lib/reporter-functions.js').log} log
7
+ // * @typedef {import('../lib/reporter-functions.js').logger} logger
8
+ // * @typedef {import('../lib/reporter-functions.js')} meta
9
+ // * @typedef {import('../lib/reporter-functions.js')} step
10
+ // *
11
+ // * "Reporter" type which is object containing all types from the above
12
+ // * @typedef {{artifact: artifact, log: log, logger: logger, meta: meta, step: step, }} Reporter
13
+ // */
14
+
15
+ // // const reporter = _reporter;
16
+
17
+ // /**
18
+ // * @type {Reporter}
19
+ // */
20
+ // const reporter = _reporter;
21
+ // module.exports = reporter;
@@ -0,0 +1,36 @@
1
+ // import TestomatClient from './client.js';
2
+ // import * as TRConstants from './constants.js';
3
+ import { services } from './services/index.js';
4
+ import reporterFunctions from './reporter-functions.js';
5
+
6
+ export const artifact = reporterFunctions.artifact;
7
+ export const log = reporterFunctions.log;
8
+ export const logger = services.logger;
9
+ export const meta = reporterFunctions.keyValue;
10
+ export const step = reporterFunctions.step;
11
+ export const label = reporterFunctions.label;
12
+
13
+ /**
14
+ * @typedef {import('./reporter-functions.js')} artifact
15
+ * @typedef {import('./reporter-functions.js')} log
16
+ * @typedef {import('./services/index.js')} logger
17
+ * @typedef {import('./reporter-functions.js')} meta
18
+ * @typedef {import('./reporter-functions.js')} step
19
+ * @typedef {import('./reporter-functions.js')} label
20
+ */
21
+ export default {
22
+ /**
23
+ * @deprecated Use `log` or `testomat.log`
24
+ */
25
+ testomatioLogger: services.logger,
26
+
27
+ artifact: reporterFunctions.artifact,
28
+ log: reporterFunctions.log,
29
+ logger: services.logger,
30
+ meta: reporterFunctions.keyValue,
31
+ step: reporterFunctions.step,
32
+ label: reporterFunctions.label,
33
+
34
+ // TestomatClient,
35
+ // TRConstants,
36
+ };
@@ -0,0 +1,59 @@
1
+ import createDebugMessages from 'debug';
2
+ import { dataStorage } from '../data-storage.js';
3
+
4
+ const debug = createDebugMessages('@testomatio/reporter:services-artifacts');
5
+
6
+ /**
7
+ * Artifact storage is supposed to store file paths
8
+ */
9
+ class ArtifactStorage {
10
+ static #instance;
11
+
12
+ /**
13
+ * Singleton
14
+ * @returns {ArtifactStorage}
15
+ */
16
+ static getInstance() {
17
+ if (!this.#instance) {
18
+ this.#instance = new ArtifactStorage();
19
+ }
20
+ return this.#instance;
21
+ }
22
+
23
+ /**
24
+ * Stores path to file as artifact and uploads it to the S3 storage
25
+ * @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
26
+ * @param {*} context testId or test title
27
+ */
28
+ put(data, context = null) {
29
+ if (!data) return;
30
+ debug('Save artifact:', data);
31
+ dataStorage.putData('artifact', data, context);
32
+ }
33
+
34
+ /**
35
+ * Returns list of artifacts to upload
36
+ * @param {*} context testId or test context from test runner
37
+ * @returns {(string | {path: string, type: string, name: string})[]}
38
+ */
39
+ get(context) {
40
+ let artifacts = dataStorage.getData('artifact', context);
41
+ if (!artifacts || !artifacts.length) return [];
42
+
43
+ artifacts = artifacts.map(artifactData => {
44
+ // artifact could be an object ({type, path, name} props) or string (just path)
45
+ let artifact;
46
+ try {
47
+ artifact = JSON.parse(artifactData);
48
+ } catch (e) {
49
+ artifact = artifactData;
50
+ }
51
+ return artifact;
52
+ });
53
+ artifacts = artifacts.filter(artifact => !!artifact);
54
+ debug(`Artifacts for test ${context}:`, artifacts);
55
+ return artifacts.length ? artifacts : [];
56
+ }
57
+ }
58
+
59
+ export const artifactStorage = ArtifactStorage.getInstance();
@@ -0,0 +1,15 @@
1
+ import { logger } from './logger.js';
2
+ import { artifactStorage } from './artifacts.js';
3
+ import { keyValueStorage } from './key-values.js';
4
+ import { labelStorage } from './labels.js';
5
+ import { dataStorage } from '../data-storage.js';
6
+
7
+ export const services = {
8
+ logger,
9
+ artifacts: artifactStorage,
10
+ keyValues: keyValueStorage,
11
+ labels: labelStorage,
12
+ setContext: context => {
13
+ dataStorage.setContext(context);
14
+ },
15
+ };
@@ -0,0 +1,59 @@
1
+ import createDebugMessages from 'debug';
2
+ import { dataStorage } from '../data-storage.js';
3
+
4
+ const debug = createDebugMessages('@testomatio/reporter:services-key-value');
5
+ class KeyValueStorage {
6
+ static #instance;
7
+
8
+ /**
9
+ *
10
+ * @returns {KeyValueStorage}
11
+ */
12
+ static getInstance() {
13
+ if (!this.#instance) {
14
+ this.#instance = new KeyValueStorage();
15
+ }
16
+ return this.#instance;
17
+ }
18
+
19
+ /**
20
+ * Stores key-value pair and passes it to reporter
21
+ * @param {{[key: string]: string}} keyValue - key-value pair(s) as object
22
+ * @param {*} context - full test title
23
+ */
24
+ put(keyValue, context = null) {
25
+ if (!keyValue) return;
26
+ dataStorage.putData('keyvalue', keyValue, context);
27
+ }
28
+
29
+ #isKeyValueObject(smth) {
30
+ return smth && typeof smth === 'object' && !Array.isArray(smth) && smth !== null;
31
+ }
32
+
33
+ /**
34
+ * Returns key-values pairs for the test as object
35
+ * @param {*} context testId or test context from test runner
36
+ * @returns {{[key: string]: string} | {}} key-values pairs as object, e.g. {priority: 'high', browser: 'chrome'}
37
+ */
38
+ get(context = null) {
39
+ const keyValuesList = dataStorage.getData('keyvalue', context);
40
+ if (!keyValuesList || !keyValuesList?.length) return {};
41
+
42
+ const keyValues = {};
43
+ for (const keyValue of keyValuesList) {
44
+ if (this.#isKeyValueObject(keyValue)) {
45
+ Object.assign(keyValues, keyValue);
46
+ } else if (typeof keyValue === 'string') {
47
+ try {
48
+ Object.assign(keyValues, JSON.parse(keyValue));
49
+ } catch (e) {
50
+ debug(`Error parsing key-values for test ${context}`, keyValue);
51
+ }
52
+ }
53
+ }
54
+
55
+ return keyValues;
56
+ }
57
+ }
58
+
59
+ export const keyValueStorage = KeyValueStorage.getInstance();
@@ -0,0 +1,59 @@
1
+ import createDebugMessages from 'debug';
2
+ import { dataStorage } from '../data-storage.js';
3
+
4
+ const debug = createDebugMessages('@testomatio/reporter:services-labels');
5
+ class LabelStorage {
6
+ static #instance;
7
+
8
+ /**
9
+ *
10
+ * @returns {LabelStorage}
11
+ */
12
+ static getInstance() {
13
+ if (!this.#instance) {
14
+ this.#instance = new LabelStorage();
15
+ }
16
+ return this.#instance;
17
+ }
18
+
19
+ /**
20
+ * Stores labels array and passes it to reporter
21
+ * @param {string[]} labels - array of label strings
22
+ * @param {*} context - full test title
23
+ */
24
+ put(labels, context = null) {
25
+ if (!labels || !Array.isArray(labels)) return;
26
+ dataStorage.putData('labels', labels, context);
27
+ }
28
+
29
+ /**
30
+ * Returns labels array for the test
31
+ * @param {*} context testId or test context from test runner
32
+ * @returns {string[]} labels array, e.g. ['smoke', 'severity:high', 'feature:user_account']
33
+ */
34
+ get(context = null) {
35
+ const labelsList = dataStorage.getData('labels', context);
36
+ if (!labelsList || !labelsList?.length) return [];
37
+
38
+ const allLabels = [];
39
+ for (const labels of labelsList) {
40
+ if (Array.isArray(labels)) {
41
+ allLabels.push(...labels);
42
+ } else if (typeof labels === 'string') {
43
+ try {
44
+ const parsedLabels = JSON.parse(labels);
45
+ if (Array.isArray(parsedLabels)) {
46
+ allLabels.push(...parsedLabels);
47
+ }
48
+ } catch (e) {
49
+ debug(`Error parsing labels for test ${context}`, labels);
50
+ }
51
+ }
52
+ }
53
+
54
+ // Remove duplicates
55
+ return [...new Set(allLabels)];
56
+ }
57
+ }
58
+
59
+ export const labelStorage = LabelStorage.getInstance();