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