@testomatio/reporter 1.3.1-beta → 1.3.1-beta.1-exclude-skipped-tests

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