@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,503 @@
1
+ import createDebugMessages from 'debug';
2
+ import pc from 'picocolors';
3
+
4
+ // Retry interceptor function
5
+ import axiosRetry from 'axios-retry';
6
+
7
+ // Default axios instance
8
+ import axios from 'axios';
9
+
10
+ import JsonCycle from 'json-cycle';
11
+ import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
12
+ import { isValidUrl, foundedTestLog } from '../utils/utils.js';
13
+ import { parseFilterParams, generateFilterRequestParams, setS3Credentials } from '../utils/pipe_utils.js';
14
+ import { config } from '../config.js';
15
+
16
+ const debug = createDebugMessages('@testomatio/reporter:pipe:testomatio');
17
+
18
+ if (process.env.TESTOMATIO_RUN) {
19
+ // process.env.runId = process.env.TESTOMATIO_RUN;
20
+ }
21
+
22
+ /**
23
+ * @typedef {import('../../types/types.js').Pipe} Pipe
24
+ * @typedef {import('../../types/types.js').TestData} TestData
25
+ * @class TestomatioPipe
26
+ * @implements {Pipe}
27
+ */
28
+ class TestomatioPipe {
29
+ constructor(params, store) {
30
+ this.batch = {
31
+ isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
32
+ intervalFunction: null, // will be created in createRun by setInterval function
33
+ intervalTime: 5000, // how often tests are sent
34
+ tests: [], // array of tests in batch
35
+ batchIndex: 0, // represents the current batch index (starts from 1 and increments by 1 for each batch)
36
+ numberOfTimesCalledWithoutTests: 0, // how many times batch was called without tests
37
+ };
38
+ this.retriesTimestamps = [];
39
+ this.reportingCanceledDueToReqFailures = false;
40
+ this.notReportedTestsCount = 0;
41
+
42
+ this.isEnabled = false;
43
+ this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
44
+ this.apiKey = params.apiKey || config.TESTOMATIO;
45
+ debug('Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
46
+ if (!this.apiKey) {
47
+ return;
48
+ }
49
+ debug('Testomatio Pipe: Enabled');
50
+
51
+ const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
52
+ const proxy = proxyUrl ? new URL(proxyUrl) : null;
53
+
54
+ this.parallel = params.parallel;
55
+ this.store = store || {};
56
+ this.title = params.title || process.env.TESTOMATIO_TITLE;
57
+ this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
58
+ this.sharedRunTimeout = !!process.env.TESTOMATIO_SHARED_RUN_TIMEOUT;
59
+ this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
60
+ this.env = process.env.TESTOMATIO_ENV;
61
+ this.label = process.env.TESTOMATIO_LABEL;
62
+ // Create a new instance of axios with a custom config
63
+ this.axios = axios.create({
64
+ baseURL: `${this.url.trim()}`,
65
+ timeout: AXIOS_TIMEOUT,
66
+ proxy: proxy
67
+ ? {
68
+ host: proxy.hostname,
69
+ port: parseInt(proxy.port, 10),
70
+ protocol: proxy.protocol,
71
+ }
72
+ : false,
73
+ });
74
+
75
+ // Pass the axios instance to the retry function
76
+ axiosRetry(this.axios, {
77
+ // do not use retries for unit tests
78
+ retries: REPORTER_REQUEST_RETRIES.retriesPerRequest, // Number of retries
79
+ shouldResetTimeout: true,
80
+ retryCondition: error => {
81
+ if (!error.response) return false;
82
+ switch (error.response?.status) {
83
+ case 400: // Bad request (probably wrong API key)
84
+ case 404: // Test not matched
85
+ case 429: // Rate limit exceeded
86
+ case 500: // Internal server error
87
+ return false;
88
+ default:
89
+ break;
90
+ }
91
+ return error.response?.status >= 401; // Retry on 401+ and 5xx
92
+ },
93
+ retryDelay: () => REPORTER_REQUEST_RETRIES.retryTimeout, // sum = 15sec
94
+ onRetry: async (retryCount, error) => {
95
+ this.retriesTimestamps.push(Date.now());
96
+
97
+ debug(`${error.message || `Request failed ${error.status}`}. Retry #${retryCount} ...`);
98
+ },
99
+ });
100
+
101
+ this.isEnabled = true;
102
+ // do not finish this run (for parallel testing)
103
+ this.proceed = process.env.TESTOMATIO_PROCEED;
104
+ this.jiraId = process.env.TESTOMATIO_JIRA_ID;
105
+ this.runId = params.runId || process.env.runId;
106
+ this.createNewTests = params.createNewTests ?? !!process.env.TESTOMATIO_CREATE;
107
+ this.hasUnmatchedTests = false;
108
+ this.requestFailures = 0;
109
+
110
+ if (!isValidUrl(this.url.trim())) {
111
+ this.isEnabled = false;
112
+ console.error(APP_PREFIX, pc.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`));
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
118
+ * @param {Object} opts - The options for preparing the test grepList.
119
+ * @returns {Promise<string[]>} - An array containing the retrieved
120
+ * test grepList, or an empty array if no tests are found or the request is disabled.
121
+ * @throws {Error} - Throws an error if there was a problem while making the request.
122
+ */
123
+ async prepareRun(opts) {
124
+ if (!this.isEnabled) return [];
125
+
126
+ const { type, id } = parseFilterParams(opts);
127
+
128
+ try {
129
+ const q = generateFilterRequestParams({
130
+ type,
131
+ id,
132
+ apiKey: this.apiKey.trim(),
133
+ });
134
+
135
+ if (!q) {
136
+ return;
137
+ }
138
+
139
+ const resp = await this.axios.get('/api/test_grep', q);
140
+ const { data } = resp;
141
+
142
+ if (Array.isArray(data?.tests) && data?.tests?.length > 0) {
143
+ foundedTestLog(APP_PREFIX, data.tests);
144
+ return data.tests;
145
+ }
146
+
147
+ console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
148
+ } catch (err) {
149
+ console.error(APP_PREFIX, `🚩 Error getting Testomat.io test grepList: ${err}`);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Creates a new run on Testomat.io
155
+ * @param {{isBatchEnabled?: boolean}} params
156
+ * @returns Promise<void>
157
+ */
158
+ async createRun(params = {}) {
159
+ this.batch.isEnabled = params.isBatchEnabled ?? this.batch.isEnabled;
160
+ if (!this.isEnabled) return;
161
+ if (this.batch.isEnabled && this.isEnabled)
162
+ this.batch.intervalFunction = setInterval(this.#batchUpload, this.batch.intervalTime);
163
+
164
+ let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
165
+
166
+ // GitHub Actions Url
167
+ if (!buildUrl && process.env.GITHUB_RUN_ID) {
168
+ // eslint-disable-next-line max-len
169
+ buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
170
+ }
171
+
172
+ // Azure DevOps Url
173
+ if (!buildUrl && process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) {
174
+ const collectionUri = process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI;
175
+ const project = process.env.SYSTEM_TEAMPROJECT;
176
+ const buildId = process.env.BUILD_BUILDID;
177
+ buildUrl = `${collectionUri}/${project}/_build/results?buildId=${buildId}`;
178
+ }
179
+
180
+ if (buildUrl && !buildUrl.startsWith('http')) buildUrl = undefined;
181
+
182
+ const accessEvent = process.env.TESTOMATIO_PUBLISH ? 'publish' : null;
183
+
184
+ const runParams = Object.fromEntries(
185
+ Object.entries({
186
+ ci_build_url: buildUrl,
187
+ parallel: this.parallel,
188
+ api_key: this.apiKey.trim(),
189
+ group_title: this.groupTitle,
190
+ access_event: accessEvent,
191
+ jira_id: this.jiraId,
192
+ env: this.env,
193
+ title: this.title,
194
+ label: this.label,
195
+ shared_run: this.sharedRun,
196
+ shared_run_timeout: this.sharedRunTimeout,
197
+ }).filter(([, value]) => !!value),
198
+ );
199
+ debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
200
+
201
+ if (this.runId) {
202
+ this.store.runId = this.runId;
203
+ debug(`Run with id ${this.runId} already created, updating...`);
204
+ const resp = await this.axios.put(`/api/reporter/${this.runId}`, runParams);
205
+ if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
206
+ return;
207
+ }
208
+
209
+ debug('Creating run...');
210
+ try {
211
+ const resp = await this.axios.post(`/api/reporter`, runParams, {
212
+ maxContentLength: Infinity,
213
+ maxBodyLength: Infinity,
214
+ });
215
+
216
+ this.runId = resp.data.uid;
217
+ this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
218
+ this.runPublicUrl = resp.data.public_url;
219
+
220
+ if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
221
+
222
+ this.store.runUrl = this.runUrl;
223
+ this.store.runPublicUrl = this.runPublicUrl;
224
+ this.store.runId = this.runId;
225
+ console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId);
226
+ process.env.runId = this.runId;
227
+ debug('Run created', this.runId);
228
+ } catch (err) {
229
+ const errorText = err.response?.data?.message || err.message;
230
+ console.log(errorText || err);
231
+ if (!this.apiKey) console.error('Testomat.io API key is not set');
232
+ if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
233
+
234
+ console.error(
235
+ APP_PREFIX,
236
+ 'Error creating Testomat.io report (see details above), please check if your API key is valid. Skipping report',
237
+ );
238
+ printCreateIssue(err);
239
+ }
240
+ debug('"createRun" function finished');
241
+ }
242
+
243
+ /**
244
+ * Decides whether to skip test reporting in case of too many request failures
245
+ * @returns {boolean}
246
+ */
247
+ #cancelTestReportingInCaseOfTooManyReqFailures() {
248
+ if (!process.env.TESTOMATIO_MAX_REQUEST_FAILURES) return;
249
+
250
+ const cancelReporting = this.requestFailures >= parseInt(process.env.TESTOMATIO_MAX_REQUEST_FAILURES, 10);
251
+ if (cancelReporting) {
252
+ this.reportingCanceledDueToReqFailures = true;
253
+ let errorMessage = `⚠️ ${process.env.TESTOMATIO_MAX_REQUEST_FAILURES}`;
254
+ errorMessage += ' requests were failed, reporting to Testomat aborted.';
255
+ console.warn(`${APP_PREFIX} ${pc.yellow(errorMessage)}`);
256
+ }
257
+ return cancelReporting;
258
+ }
259
+
260
+ #uploadSingleTest = async data => {
261
+ if (!this.isEnabled) return;
262
+ if (!this.runId) return;
263
+ if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
264
+
265
+ data.api_key = this.apiKey;
266
+ data.create = this.createNewTests;
267
+
268
+ if (!process.env.TESTOMATIO_STACK_PASSED && data.status === STATUS.PASSED) {
269
+ data.stack = null;
270
+ }
271
+
272
+ const json = JsonCycle.stringify(data);
273
+
274
+ debug('Adding test', json);
275
+
276
+ return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, axiosAddTestrunRequestConfig).catch(err => {
277
+ this.requestFailures++;
278
+ this.notReportedTestsCount++;
279
+ if (err.response) {
280
+ if (err.response.status >= 400) {
281
+ const responseData = err.response.data || { message: '' };
282
+ console.log(
283
+ APP_PREFIX,
284
+ pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
285
+ pc.gray(data?.title || ''),
286
+ );
287
+ if (err.response?.data?.message?.includes('could not be matched')) {
288
+ this.hasUnmatchedTests = true;
289
+ }
290
+ return;
291
+ }
292
+ console.log(
293
+ APP_PREFIX,
294
+ pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
295
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
296
+ );
297
+ printCreateIssue(err);
298
+ } else {
299
+ console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
300
+ }
301
+ });
302
+ };
303
+
304
+ /**
305
+ * Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
306
+ */
307
+ #batchUpload = async () => {
308
+ if (!this.batch.isEnabled) return;
309
+ if (!this.batch.tests.length) return;
310
+ if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
311
+ // prevent infinite loop
312
+ if (this.batch.numberOfTimesCalledWithoutTests > 10) {
313
+ debug('📨 Batch upload: no tests to send for 10 times, stopping batch');
314
+ clearInterval(this.batch.intervalFunction);
315
+ this.batch.isEnabled = false;
316
+ }
317
+ if (!this.batch.tests.length) {
318
+ debug('📨 Batch upload: no tests to send');
319
+ this.batch.numberOfTimesCalledWithoutTests++;
320
+ return;
321
+ }
322
+
323
+ this.batch.batchIndex++;
324
+ // get tests from batch and clear batch
325
+ const testsToSend = this.batch.tests.splice(0);
326
+ debug('📨 Batch upload', testsToSend.length, 'tests');
327
+
328
+ return this.axios
329
+ .post(
330
+ `/api/reporter/${this.runId}/testrun`,
331
+ { api_key: this.apiKey, tests: testsToSend, batch_index: this.batch.batchIndex },
332
+ axiosAddTestrunRequestConfig,
333
+ )
334
+ .catch(err => {
335
+ this.requestFailures++;
336
+ this.notReportedTestsCount += testsToSend.length;
337
+ if (err.response) {
338
+ if (err.response.status >= 400) {
339
+ const responseData = err.response.data || { message: '' };
340
+ console.log(
341
+ APP_PREFIX,
342
+ pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
343
+ // pc.grey(data?.title || ''),
344
+ );
345
+ if (err.response?.data?.message?.includes('could not be matched')) {
346
+ this.hasUnmatchedTests = true;
347
+ }
348
+ return;
349
+ }
350
+ console.log(
351
+ APP_PREFIX,
352
+ pc.yellow(`Warning: (${err.response?.status})`),
353
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
354
+ );
355
+ printCreateIssue(err);
356
+ } else {
357
+ console.log(APP_PREFIX, "Report couldn't be processed", err);
358
+ }
359
+ });
360
+ };
361
+
362
+ /**
363
+ * Adds a test to the batch uploader (or reports a single test if batch uploading is disabled)
364
+ */
365
+ addTest(data) {
366
+ if (!this.isEnabled) return;
367
+ if (!this.runId) return;
368
+
369
+ // add test ID + run ID
370
+ if (data.rid) data.rid = `${this.runId}-${data.rid}`;
371
+ data.api_key = this.apiKey;
372
+ data.create = this.createNewTests;
373
+
374
+ if (!this.batch.isEnabled) this.#uploadSingleTest(data);
375
+ else this.batch.tests.push(data);
376
+
377
+ // if test is added after run which is already finished
378
+ if (!this.batch.intervalFunction) this.#batchUpload();
379
+ }
380
+
381
+ /**
382
+ * @param {import('../../types/types.js').RunData} params
383
+ * @returns
384
+ */
385
+ async finishRun(params) {
386
+ if (!this.isEnabled) return;
387
+
388
+ await this.#batchUpload();
389
+ if (this.batch.intervalFunction) {
390
+ clearInterval(this.batch.intervalFunction);
391
+ // this code is required in case test is added after run is finished
392
+ // (e.g. if test has artifacts, add test function will be invoked only after artifacts are uploaded)
393
+ // batch stops working after run is finished; thus, disable it to use single test uploading
394
+ this.batch.intervalFunction = null;
395
+ this.batch.isEnabled = false;
396
+ }
397
+
398
+ debug('Finishing run...');
399
+
400
+ if (this.reportingCanceledDueToReqFailures) {
401
+ const errorMessage = pc.red(
402
+ `⚠️ Due to request failures, ${this.notReportedTestsCount} test(s) were not reported to Testomat.io`,
403
+ );
404
+ console.warn(`${APP_PREFIX} ${errorMessage}`);
405
+ }
406
+
407
+ const { status, parallel } = params;
408
+
409
+ let status_event;
410
+
411
+ if (status === STATUS.FINISHED) status_event = 'finish';
412
+ if (status === STATUS.PASSED) status_event = 'pass';
413
+ if (status === STATUS.FAILED) status_event = 'fail';
414
+ if (parallel) status_event += '_parallel';
415
+
416
+ try {
417
+ if (this.runId && !this.proceed) {
418
+ await this.axios.put(`/api/reporter/${this.runId}`, {
419
+ api_key: this.apiKey,
420
+ duration: params.duration,
421
+ status_event,
422
+ detach: params.detach,
423
+ tests: params.tests,
424
+ });
425
+ if (this.runUrl) {
426
+ console.log(APP_PREFIX, '📊 Report Saved. Report URL:', pc.magenta(this.runUrl));
427
+ }
428
+ if (this.runPublicUrl) {
429
+ console.log(APP_PREFIX, '🌟 Public URL:', pc.magenta(this.runPublicUrl));
430
+ }
431
+ }
432
+ if (this.runUrl && this.proceed) {
433
+ const notFinishedMessage = pc.yellow(pc.bold('Run was not finished because of $TESTOMATIO_PROCEED'));
434
+ console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${pc.magenta(this.runUrl)}`);
435
+ console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
436
+ }
437
+
438
+ if (this.hasUnmatchedTests) {
439
+ console.log('');
440
+ // eslint-disable-next-line max-len
441
+ console.log(APP_PREFIX, pc.yellow(pc.bold('⚠️ Some reported tests were not found in Testomat.io project')));
442
+ // eslint-disable-next-line max-len
443
+ console.log(
444
+ APP_PREFIX,
445
+ `If you use Testomat.io as a reporter only, please re-run tests using ${pc.bold('TESTOMATIO_CREATE=1')}`,
446
+ );
447
+ // eslint-disable-next-line max-len
448
+ console.log(
449
+ APP_PREFIX,
450
+ `But to keep your tests consistent it is recommended to ${pc.bold('import tests first')}`,
451
+ );
452
+ console.log(APP_PREFIX, 'If tests were imported but still not matched, assign test IDs to your tests.');
453
+ console.log(APP_PREFIX, 'You can do that automatically via command line tools:');
454
+ console.log(APP_PREFIX, pc.bold('npx check-tests ... --update-ids'), 'See: https://bit.ly/js-update-ids');
455
+ console.log(APP_PREFIX, 'or for Cucumber:');
456
+ // eslint-disable-next-line max-len
457
+ console.log(APP_PREFIX, pc.bold('npx check-cucumber ... --update-ids'), 'See: https://bit.ly/bdd-update-ids');
458
+ }
459
+ } catch (err) {
460
+ console.log(APP_PREFIX, 'Error updating status, skipping...', err);
461
+ printCreateIssue(err);
462
+ }
463
+ debug('Run finished');
464
+ }
465
+
466
+ toString() {
467
+ return 'Testomatio Reporter';
468
+ }
469
+ }
470
+
471
+ let registeredErrorHints = false;
472
+ function printCreateIssue(err) {
473
+ if (registeredErrorHints) return;
474
+ registeredErrorHints = true;
475
+ process.on('exit', () => {
476
+ console.log();
477
+ console.log(APP_PREFIX, 'There was an error reporting to Testomat.io:');
478
+ console.log(
479
+ APP_PREFIX,
480
+ 'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new',
481
+ ); // eslint-disable-line max-len
482
+ console.log(APP_PREFIX, 'Provide this information:');
483
+ console.log('Error:', err.message || err.code);
484
+ if (!err.config) return;
485
+
486
+ const time = new Date().toUTCString();
487
+ const { data, url, baseURL, method } = err?.config || {};
488
+ console.log('```js');
489
+ console.log({ data: data?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
490
+ console.log('```');
491
+ });
492
+ }
493
+
494
+ const axiosAddTestrunRequestConfig = {
495
+ maxContentLength: Infinity,
496
+ maxBodyLength: Infinity,
497
+ headers: {
498
+ // Overwrite Axios's automatically set Content-Type
499
+ 'Content-Type': 'application/json',
500
+ },
501
+ };
502
+
503
+ export default TestomatioPipe;
@@ -0,0 +1,55 @@
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
+ export default {
51
+ artifact: saveArtifact,
52
+ log: logMessage,
53
+ step: addStep,
54
+ keyValue: setKeyValue,
55
+ };
@@ -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,33 @@
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
+
12
+ /**
13
+ * @typedef {import('./reporter-functions.js')} artifact
14
+ * @typedef {import('./reporter-functions.js')} log
15
+ * @typedef {import('./services/index.js')} logger
16
+ * @typedef {import('./reporter-functions.js')} meta
17
+ * @typedef {import('./reporter-functions.js')} step
18
+ */
19
+ export default {
20
+ /**
21
+ * @deprecated Use `log` or `testomat.log`
22
+ */
23
+ testomatioLogger: services.logger,
24
+
25
+ artifact: reporterFunctions.artifact,
26
+ log: reporterFunctions.log,
27
+ logger: services.logger,
28
+ meta: reporterFunctions.keyValue,
29
+ step: reporterFunctions.step,
30
+
31
+ // TestomatClient,
32
+ // TRConstants,
33
+ };
@@ -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,13 @@
1
+ import { logger } from './logger.js';
2
+ import { artifactStorage } from './artifacts.js';
3
+ import { keyValueStorage } from './key-values.js';
4
+ import { dataStorage } from '../data-storage.js';
5
+
6
+ export const services = {
7
+ logger,
8
+ artifacts: artifactStorage,
9
+ keyValues: keyValueStorage,
10
+ setContext: context => {
11
+ dataStorage.setContext(context);
12
+ },
13
+ };