@testomatio/reporter 2.6.0-beta.1.allure → 2.6.1

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 (57) hide show
  1. package/README.md +9 -11
  2. package/lib/adapter/playwright.d.ts +2 -0
  3. package/lib/adapter/playwright.js +29 -5
  4. package/lib/adapter/utils/playwright.d.ts +25 -0
  5. package/lib/adapter/utils/playwright.js +123 -0
  6. package/lib/adapter/vitest.js +2 -1
  7. package/lib/bin/cli.js +36 -36
  8. package/lib/data-storage.d.ts +1 -1
  9. package/lib/data-storage.js +1 -0
  10. package/lib/junit-adapter/index.js +0 -4
  11. package/lib/pipe/coverage.js +63 -5
  12. package/lib/pipe/debug.js +1 -2
  13. package/lib/pipe/github.js +15 -0
  14. package/lib/pipe/html.d.ts +2 -3
  15. package/lib/pipe/html.js +745 -37
  16. package/lib/pipe/testomatio.js +83 -36
  17. package/lib/reporter-functions.d.ts +36 -11
  18. package/lib/reporter-functions.js +72 -22
  19. package/lib/reporter.d.ts +90 -38
  20. package/lib/services/artifacts.d.ts +1 -1
  21. package/lib/services/key-values.d.ts +1 -1
  22. package/lib/services/links.d.ts +5 -3
  23. package/lib/services/links.js +1 -1
  24. package/lib/services/logger.d.ts +1 -1
  25. package/lib/template/testomatio-old.hbs +1421 -0
  26. package/lib/template/testomatio.hbs +3200 -1157
  27. package/lib/utils/log-formatter.d.ts +1 -2
  28. package/lib/utils/log-formatter.js +8 -4
  29. package/lib/utils/utils.js +0 -9
  30. package/package.json +2 -2
  31. package/src/adapter/playwright.js +32 -6
  32. package/src/adapter/utils/playwright.js +121 -0
  33. package/src/adapter/vitest.js +2 -1
  34. package/src/bin/cli.js +39 -47
  35. package/src/data-storage.js +1 -0
  36. package/src/junit-adapter/index.js +0 -4
  37. package/src/pipe/coverage.js +90 -32
  38. package/src/pipe/debug.js +1 -2
  39. package/src/pipe/github.js +14 -0
  40. package/src/pipe/html.js +844 -38
  41. package/src/pipe/testomatio.js +98 -53
  42. package/src/reporter-functions.js +73 -25
  43. package/src/services/links.js +1 -1
  44. package/src/template/testomatio-old.hbs +1421 -0
  45. package/src/template/testomatio.hbs +3200 -1157
  46. package/src/utils/log-formatter.js +9 -4
  47. package/src/utils/utils.js +0 -5
  48. package/types/types.d.ts +30 -6
  49. package/lib/allureReader.d.ts +0 -65
  50. package/lib/allureReader.js +0 -448
  51. package/lib/junit-adapter/kotlin.d.ts +0 -5
  52. package/lib/junit-adapter/kotlin.js +0 -46
  53. package/lib/services/labels.d.ts +0 -0
  54. package/lib/services/labels.js +0 -0
  55. package/src/allureReader.js +0 -523
  56. package/src/junit-adapter/kotlin.js +0 -48
  57. package/src/services/labels.js +0 -1
@@ -3,11 +3,12 @@ import pc from 'picocolors';
3
3
  import { Gaxios } from 'gaxios';
4
4
  import JsonCycle from 'json-cycle';
5
5
  import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
6
- import { isValidUrl,
7
- foundedTestLog,
8
- readLatestRunId,
9
- transformEnvVarToBoolean,
10
- getGitCommitSha
6
+ import {
7
+ isValidUrl,
8
+ foundedTestLog,
9
+ readLatestRunId,
10
+ transformEnvVarToBoolean,
11
+ getGitCommitSha,
11
12
  } from '../utils/utils.js';
12
13
  import { parseFilterParams, generateFilterRequestParams, setS3Credentials } from '../utils/pipe_utils.js';
13
14
  import { config } from '../config.js';
@@ -25,7 +26,7 @@ if (process.env.TESTOMATIO_RUN) process.env.runId = process.env.TESTOMATIO_RUN;
25
26
  class TestomatioPipe {
26
27
  constructor(params, store) {
27
28
  this.batch = {
28
- isEnabled: params?.isBatchEnabled ?? !transformEnvVarToBoolean(process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD),
29
+ isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
29
30
  intervalFunction: null, // will be created in createRun by setInterval function
30
31
  intervalTime: 5000, // how often tests are sent
31
32
  tests: [], // array of tests in batch
@@ -198,6 +199,9 @@ class TestomatioPipe {
198
199
  if (!this.isEnabled) return;
199
200
  if (this.batch.isEnabled && this.isEnabled)
200
201
  this.batch.intervalFunction = setInterval(this.#batchUpload, this.batch.intervalTime);
202
+ if (this.store) {
203
+ this.store.runKind = params.kind;
204
+ }
201
205
 
202
206
  let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
203
207
 
@@ -219,6 +223,16 @@ class TestomatioPipe {
219
223
 
220
224
  const accessEvent = process.env.TESTOMATIO_PUBLISH ? 'publish' : null;
221
225
 
226
+ const coverageConfiguration = this.store?.coverageConfiguration;
227
+ let description = null;
228
+ let configuration = null;
229
+ if (coverageConfiguration && (coverageConfiguration.tests?.length || coverageConfiguration.suites?.length)) {
230
+ description = this.store?.coverageDescription || null;
231
+ configuration = {
232
+ tests: coverageConfiguration.tests?.map(id => id.replace(/^T/, '')) || [],
233
+ suites: coverageConfiguration.suites?.map(id => id.replace(/^S/, '')) || [],
234
+ };
235
+ }
222
236
  const runParams = Object.fromEntries(
223
237
  Object.entries({
224
238
  ci_build_url: buildUrl,
@@ -232,6 +246,8 @@ class TestomatioPipe {
232
246
  shared_run: this.sharedRun,
233
247
  shared_run_timeout: this.sharedRunTimeout,
234
248
  kind: params.kind,
249
+ configuration,
250
+ description,
235
251
  }).filter(([, value]) => !!value),
236
252
  );
237
253
  debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
@@ -246,6 +262,15 @@ class TestomatioPipe {
246
262
  responseType: 'json',
247
263
  });
248
264
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
265
+ if (resp.data.url) {
266
+ const respUrl = new URL(resp.data.url);
267
+ this.runUrl = `${this.url}${respUrl.pathname}`;
268
+ this.runPublicUrl = resp.data.public_url;
269
+ this.store.runUrl = this.runUrl;
270
+ this.store.runPublicUrl = this.runPublicUrl;
271
+ console.log(APP_PREFIX, '📊 Using existing run. Report ID:', this.runId);
272
+ console.log(APP_PREFIX, '📊 Report URL:', pc.magenta(this.runUrl));
273
+ }
249
274
  return;
250
275
  }
251
276
 
@@ -278,11 +303,13 @@ class TestomatioPipe {
278
303
  if (!this.apiKey) console.error('Testomat.io API key is not set');
279
304
  if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
280
305
 
306
+ if (process.env.DEBUG || process.env.TESTOMATIO_DEBUG) this.#logFailedResponse(err);
307
+
281
308
  console.error(
282
309
  APP_PREFIX,
283
310
  'Error creating Testomat.io report (see details above), please check if your API key is valid. Skipping report',
284
311
  );
285
- printCreateIssue(err);
312
+ printCreateIssue();
286
313
  }
287
314
  debug('"createRun" function finished');
288
315
  }
@@ -329,24 +356,8 @@ class TestomatioPipe {
329
356
  this.requestFailures++;
330
357
  this.notReportedTestsCount++;
331
358
  if (err.response) {
332
- if (err.response.status >= 400) {
333
- const responseData = err.response.data || { message: '' };
334
- console.log(
335
- APP_PREFIX,
336
- pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
337
- pc.gray(data?.title || ''),
338
- );
339
- if (err.response?.data?.message?.includes('could not be matched')) {
340
- this.hasUnmatchedTests = true;
341
- }
342
- return;
343
- }
344
- console.log(
345
- APP_PREFIX,
346
- pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
347
- `Report couldn't be processed: ${err?.response?.data?.message}`,
348
- );
349
- printCreateIssue(err);
359
+ this.#logFailedResponse(err);
360
+ printCreateIssue();
350
361
  } else {
351
362
  console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
352
363
  }
@@ -395,20 +406,8 @@ class TestomatioPipe {
395
406
  this.requestFailures++;
396
407
  this.notReportedTestsCount += testsToSend.length;
397
408
  if (err.response) {
398
- if (err.response.status >= 400) {
399
- const responseData = err.response.data || { message: '' };
400
- console.log(APP_PREFIX, pc.yellow(`Warning: ${responseData.message} (${err.response.status})`));
401
- if (err.response?.data?.message?.includes('could not be matched')) {
402
- this.hasUnmatchedTests = true;
403
- }
404
- return;
405
- }
406
- console.log(
407
- APP_PREFIX,
408
- pc.yellow(`Warning: (${err.response?.status})`),
409
- `Report couldn't be processed: ${err?.response?.data?.message}`,
410
- );
411
- printCreateIssue(err);
409
+ this.#logFailedResponse(err);
410
+ printCreateIssue();
412
411
  } else {
413
412
  console.log(APP_PREFIX, "Report couldn't be processed", err);
414
413
  }
@@ -521,37 +520,83 @@ class TestomatioPipe {
521
520
  }
522
521
  } catch (err) {
523
522
  console.log(APP_PREFIX, 'Error updating status, skipping...', err);
524
- printCreateIssue(err);
523
+ if (process.env.DEBUG || process.env.TESTOMATIO_DEBUG) this.#logFailedResponse(err);
524
+ printCreateIssue();
525
525
  }
526
526
  debug('Run finished');
527
527
  }
528
528
 
529
+ #logFailedResponse(error) {
530
+ let responseBody = stringify(error.response?.data ?? error.response ?? error, { pretty: true });
531
+ if (!responseBody) responseBody = '<empty>';
532
+ responseBody = hideTestomatioToken(responseBody);
533
+
534
+ const statusCode = error.status || error.code || error.response?.status || '<unknown status code>';
535
+ const method = error.response?.config.method || '<unknown method>';
536
+ const url = error.response?.config.url || '<unknown url>';
537
+
538
+ let message = pc.yellow('\n⚠️ Request to Testomat.io failed:\n');
539
+ message += pc.bold(`${pc.red(statusCode)} ${method} ${url}\n`);
540
+ message += `\t${pc.bold('response: ')}${pc.gray(responseBody)}\n`;
541
+
542
+ const requestBody = hideTestomatioToken(stringify(error.response?.config?.data));
543
+ if (process.env.DEBUG || process.env.TESTOMATIO_DEBUG) {
544
+ message += `\t${pc.bold('request: ')}${pc.gray(requestBody)}\n`;
545
+ } else {
546
+ const requestBodyCut = requestBody.slice(0, 1000);
547
+ message += `\t${pc.bold('request: ')}${pc.gray(`${requestBodyCut}.....`)}\n`;
548
+ message += '\trequest body is cut, run with TESTOMATIO_DEBUG=1 to see full body\n';
549
+ }
550
+
551
+ console.log(message);
552
+
553
+ if (error.response?.data?.message?.includes('could not be matched')) {
554
+ this.hasUnmatchedTests = true;
555
+ }
556
+ }
557
+
529
558
  toString() {
530
559
  return 'Testomatio Reporter';
531
560
  }
532
561
  }
533
562
 
534
563
  let registeredErrorHints = false;
535
- function printCreateIssue(err) {
564
+ function printCreateIssue() {
536
565
  if (registeredErrorHints) return;
537
566
  registeredErrorHints = true;
538
567
  process.on('exit', () => {
539
- console.log();
540
- console.log(APP_PREFIX, 'There was an error reporting to Testomat.io:');
541
568
  console.log(
542
569
  APP_PREFIX,
543
- 'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new',
570
+ 'There was an error reporting to Testomat.io.\n',
571
+ pc.yellow(
572
+ 'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new.',
573
+ ),
574
+ pc.yellow('Provide the logs from above'),
544
575
  );
545
- console.log(APP_PREFIX, 'Provide this information:');
546
- console.log('Error:', err.message || err.code);
547
- if (!err.config) return;
548
-
549
- const time = new Date().toUTCString();
550
- const { body, url, baseURL, method } = err?.config || {};
551
- console.log('```js');
552
- console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
553
- console.log('```');
554
576
  });
555
577
  }
556
578
 
579
+ /**
580
+ * Removes Testomatio token from string data
581
+ *
582
+ * @param {string} data
583
+ * @returns {string}
584
+ */
585
+ function hideTestomatioToken(data) {
586
+ return (typeof data === 'string' ? data : '')
587
+ .replace(/"api_key"\s*:\s*"[^"]+"/g, '"api_key": "<hidden>"')
588
+ .replace(/"(tstmt_[^"]+)"/g, '"tstmt_***"');
589
+ }
590
+
591
+ /**
592
+ * Stringifies provided data
593
+ *
594
+ * @param {any} anything
595
+ * @param {{ pretty: boolean }} opts
596
+ * @returns {string}
597
+ */
598
+ function stringify(anything, opts = { pretty: false }) {
599
+ return typeof anything === 'string' ? anything : JSON.stringify(anything, null, opts.pretty ? 2 : undefined);
600
+ }
601
+
557
602
  export default TestomatioPipe;
@@ -1,5 +1,7 @@
1
+ import { playwrightLogsMarkers } from './adapter/utils/playwright.js';
1
2
  import { isPlaywright } from './helpers.js';
2
3
  import { services } from './services/index.js';
4
+ import pc from 'picocolors';
3
5
 
4
6
  /**
5
7
  * Stores path to file as artifact and uploads it to the S3 storage
@@ -8,7 +10,10 @@ import { services } from './services/index.js';
8
10
  * @returns {void}
9
11
  */
10
12
  function saveArtifact(data, context = null) {
11
- showPlaywrightWarning('artifact', 'Playwright supports artifacts out of the box.');
13
+ if (isPlaywright)
14
+ console.warn(`[TESTOMATIO] 'artifact' function is not supported for Playwright
15
+ Playwright supports artifacts out of the box.`);
16
+
12
17
  if (!data) return;
13
18
  services.artifacts.put(data, context);
14
19
  }
@@ -41,59 +46,102 @@ function addStep(message, logs) {
41
46
 
42
47
  /**
43
48
  * Add key-value pair(s) to the test report
44
- * @param {{[key: string]: string} | string} keyValue - object { key: value } (multiple props allowed) or key (string)
45
- * @param {string|null} [value=null] - optional value when keyValue is a string
49
+ * @param {{[key: string]: string} | string} keyValue - object { key: value } (multiple props allowed) OR key (string)
50
+ * @param {string|undefined} [value=undefined] - optional value when keyValue is a string
46
51
  * @returns {void}
52
+ *
53
+ * @example
54
+ * meta('key', 'value');
55
+ * meta({ key: 'value' });
56
+ * meta({ key1: 'value1', key2: 'value2' });
47
57
  */
48
- function setKeyValue(keyValue, value = null) {
49
- showPlaywrightWarning('meta', 'Use test annotations instead.');
50
-
58
+ function setKeyValue(keyValue, value = undefined) {
59
+ // in this case keyValue acts as key (value passed as second argument)
51
60
  if (typeof keyValue === 'string') {
52
- keyValue = { [keyValue]: value };
61
+ const key = keyValue;
62
+ keyValue = { [key]: value };
63
+ }
64
+
65
+ if (isPlaywright) {
66
+ console.log(`${playwrightLogsMarkers.meta} ${JSON.stringify(keyValue)}`);
67
+ return;
53
68
  }
69
+
70
+ // in this case keyValue is expected to be an object
54
71
  services.keyValues.put(keyValue);
55
72
  }
56
73
 
57
74
  /**
58
- * Add a single label to the test report
59
- * @param {string} key - label key (e.g. 'severity', 'feature', or just 'smoke' for labels without values)
60
- * @param {string|null} [value=null] - optional label value (e.g. 'high', 'login')
75
+ * Adds label(s) to the test
76
+ * @param {string | {
77
+ * [key: string]: string}
78
+ * } key - just label OR custom field name OR object with custom field name and value
79
+ * @param {string | null} [value=null] - optional label value (of custom field value)
80
+ * (used when key is a string)
61
81
  * @returns {void}
82
+ *
83
+ * @example
84
+ * label('high');
85
+ * label('priority', 'high');
86
+ * label({priority: 'high'});
62
87
  */
63
88
  function setLabel(key, value = null) {
64
- showPlaywrightWarning('label', 'Use test tag instead.');
65
- if (Array.isArray(value)) {
66
- return value.forEach(label => setLabel(key, label));
89
+ let labelsArr = [];
90
+
91
+ // process label('priority', 'high') and label('high'
92
+ if (typeof key === 'string') {
93
+ labelsArr = [value ? `${key}:${value}` : key];
94
+ // process label({priority: 'high'}), label({priority: 'high', scope: 'smoke'})
95
+ } else if (key !== null && typeof key === 'object') {
96
+ labelsArr = Object.entries(key).map(([key, value]) => `${key}:${value}`);
97
+ }
98
+
99
+ const labels = labelsArr.map(l => ({ label: l }));
100
+ if (isPlaywright) {
101
+ console.log(`${playwrightLogsMarkers.label} ${JSON.stringify(labels)}`);
102
+ return;
67
103
  }
68
- const labelObject =
69
- value !== null && value !== undefined && value !== '' ? { label: `${key}:${value}` } : { label: key };
70
- services.links.put([labelObject]);
104
+ services.links.put(labels);
71
105
  }
72
106
 
73
107
  /**
74
108
  * Add link(s) to the test report
75
- * @param {...string} testIds - test IDs to link
109
+ * @param {...string | string[]} testIds - test IDs to link
76
110
  * @returns {void}
111
+ *
112
+ * @example
113
+ * linkTest('T11111111', 'T22222222')
114
+ * or
115
+ * linkTest(['T11111111', 'T22222222'])
77
116
  */
78
117
  function linkTest(...testIds) {
79
- const links = testIds.map(testId => ({ test: testId }));
118
+ const testIdsArr = testIds.flat();
119
+ const links = testIdsArr.map(testId => ({ test: testId }));
120
+ if (isPlaywright) {
121
+ console.log(`${playwrightLogsMarkers.linkTest} ${JSON.stringify(links)}`);
122
+ return;
123
+ }
80
124
  services.links.put(links);
81
125
  }
82
126
 
83
127
  /**
84
128
  * Add JIRA issue link(s) to the test report
85
- * @param {...string} jiraIds - JIRA issue IDs to link
129
+ * @param {...(string | string[])} jiraIds - JIRA issue IDs to link
86
130
  * @returns {void}
131
+ *
132
+ * @example
133
+ * linkJira('TICKET-1', 'TICKET-2')
134
+ * or
135
+ * linkJira(['TICKET-1', 'TICKET-2'])
87
136
  */
88
137
  function linkJira(...jiraIds) {
89
- const links = jiraIds.map(jiraId => ({ jira: jiraId }));
90
- services.links.put(links);
91
- }
92
-
93
- function showPlaywrightWarning(functionName, recommendation) {
138
+ const jiraIdsArr = jiraIds.flat();
139
+ const links = jiraIdsArr.map(jiraId => ({ jira: jiraId }));
94
140
  if (isPlaywright) {
95
- console.warn(`[TESTOMATIO] '${functionName}' function is not supported for Playwright. ${recommendation}`);
141
+ console.log(`${playwrightLogsMarkers.linkJira} ${JSON.stringify(links)}`);
142
+ return;
96
143
  }
144
+ services.links.put(links);
97
145
  }
98
146
 
99
147
  export default {
@@ -30,7 +30,7 @@ class LinkStorage {
30
30
  /**
31
31
  * Returns links array for the test
32
32
  * @param {*} context testId or test context from test runner
33
- * @returns {object[]} links array, e.g. [{test: 'TEST-123'}, {jira: 'JIRA-456'}]
33
+ * @returns {{[key: 'test' | 'jira']: string}[]} links array, e.g. [{test: 'TEST-123'}, {jira: 'JIRA-456'}]
34
34
  */
35
35
  get(context = null) {
36
36
  const linksList = dataStorage.getData('links', context);