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