@testomatio/reporter 2.0.0-beta-esm → 2.0.0-beta.1-xml

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 (123) hide show
  1. package/lib/adapter/codecept.d.ts +2 -0
  2. package/lib/adapter/codecept.js +31 -26
  3. package/lib/adapter/cucumber/current.d.ts +14 -0
  4. package/lib/adapter/cucumber/legacy.d.ts +0 -0
  5. package/lib/adapter/cucumber.d.ts +2 -0
  6. package/lib/adapter/cypress-plugin/index.d.ts +2 -0
  7. package/lib/adapter/cypress-plugin/index.js +10 -10
  8. package/lib/adapter/jasmine.d.ts +11 -0
  9. package/lib/adapter/jest.d.ts +13 -0
  10. package/lib/adapter/mocha.d.ts +2 -0
  11. package/lib/adapter/mocha.js +4 -4
  12. package/lib/adapter/nightwatch.d.ts +4 -0
  13. package/lib/adapter/nightwatch.js +80 -0
  14. package/lib/adapter/playwright.d.ts +14 -0
  15. package/lib/adapter/playwright.js +58 -33
  16. package/lib/adapter/vitest.d.ts +35 -0
  17. package/lib/adapter/vitest.js +6 -6
  18. package/lib/adapter/webdriver.d.ts +24 -0
  19. package/lib/adapter/webdriver.js +51 -14
  20. package/lib/bin/cli.d.ts +2 -0
  21. package/lib/bin/cli.js +250 -0
  22. package/lib/bin/reportXml.d.ts +2 -0
  23. package/lib/bin/reportXml.js +15 -11
  24. package/lib/bin/startTest.d.ts +2 -0
  25. package/lib/bin/startTest.js +12 -7
  26. package/lib/bin/uploadArtifacts.d.ts +2 -0
  27. package/lib/bin/uploadArtifacts.js +82 -0
  28. package/lib/client.d.ts +76 -0
  29. package/lib/client.js +128 -53
  30. package/lib/config.d.ts +1 -0
  31. package/lib/config.js +2 -2
  32. package/lib/constants.d.ts +25 -0
  33. package/lib/constants.js +5 -1
  34. package/lib/data-storage.d.ts +34 -0
  35. package/lib/data-storage.js +19 -9
  36. package/lib/junit-adapter/adapter.d.ts +9 -0
  37. package/lib/junit-adapter/csharp.d.ts +5 -0
  38. package/lib/junit-adapter/csharp.js +11 -1
  39. package/lib/junit-adapter/index.d.ts +3 -0
  40. package/lib/junit-adapter/java.d.ts +5 -0
  41. package/lib/junit-adapter/javascript.d.ts +4 -0
  42. package/lib/junit-adapter/python.d.ts +5 -0
  43. package/lib/junit-adapter/ruby.d.ts +4 -0
  44. package/lib/output.d.ts +11 -0
  45. package/lib/package.json +3 -1
  46. package/lib/pipe/bitbucket.d.ts +23 -0
  47. package/lib/pipe/bitbucket.js +19 -9
  48. package/lib/pipe/csv.d.ts +47 -0
  49. package/lib/pipe/csv.js +2 -2
  50. package/lib/pipe/debug.d.ts +29 -0
  51. package/lib/pipe/debug.js +108 -0
  52. package/lib/pipe/github.d.ts +30 -0
  53. package/lib/pipe/github.js +37 -5
  54. package/lib/pipe/gitlab.d.ts +23 -0
  55. package/lib/pipe/gitlab.js +2 -3
  56. package/lib/pipe/html.d.ts +35 -0
  57. package/lib/pipe/html.js +9 -4
  58. package/lib/pipe/index.d.ts +1 -0
  59. package/lib/pipe/index.js +20 -10
  60. package/lib/pipe/testomatio.d.ts +70 -0
  61. package/lib/pipe/testomatio.js +54 -39
  62. package/lib/reporter-functions.d.ts +34 -0
  63. package/lib/reporter-functions.js +17 -7
  64. package/lib/reporter.d.ts +232 -0
  65. package/lib/reporter.js +19 -33
  66. package/lib/services/artifacts.d.ts +33 -0
  67. package/lib/services/index.d.ts +9 -0
  68. package/lib/services/key-values.d.ts +27 -0
  69. package/lib/services/key-values.js +1 -1
  70. package/lib/services/logger.d.ts +64 -0
  71. package/lib/services/logger.js +1 -2
  72. package/lib/template/testomatio.hbs +651 -1366
  73. package/lib/uploader.d.ts +60 -0
  74. package/lib/uploader.js +312 -0
  75. package/lib/utils/pipe_utils.d.ts +41 -0
  76. package/lib/utils/pipe_utils.js +3 -5
  77. package/lib/utils/utils.d.ts +47 -0
  78. package/lib/utils/utils.js +99 -12
  79. package/lib/xmlReader.d.ts +92 -0
  80. package/lib/xmlReader.js +64 -25
  81. package/package.json +19 -13
  82. package/src/adapter/codecept.js +30 -26
  83. package/src/adapter/cypress-plugin/index.js +5 -5
  84. package/src/adapter/mocha.cjs +1 -1
  85. package/src/adapter/mocha.js +4 -4
  86. package/src/adapter/nightwatch.js +88 -0
  87. package/src/adapter/playwright.js +59 -31
  88. package/src/adapter/vitest.js +6 -6
  89. package/src/adapter/webdriver.js +42 -12
  90. package/src/bin/cli.js +303 -0
  91. package/src/bin/reportXml.js +19 -9
  92. package/src/bin/startTest.js +9 -4
  93. package/src/bin/uploadArtifacts.js +91 -0
  94. package/src/client.js +137 -57
  95. package/src/config.js +2 -2
  96. package/src/constants.js +5 -1
  97. package/src/data-storage.js +2 -2
  98. package/src/junit-adapter/csharp.js +13 -1
  99. package/src/pipe/bitbucket.js +2 -2
  100. package/src/pipe/csv.js +3 -3
  101. package/src/pipe/debug.js +104 -0
  102. package/src/pipe/github.js +3 -5
  103. package/src/pipe/gitlab.js +6 -7
  104. package/src/pipe/html.js +14 -7
  105. package/src/pipe/index.js +5 -7
  106. package/src/pipe/testomatio.js +75 -76
  107. package/src/reporter-functions.js +18 -7
  108. package/src/reporter.cjs_decprecated +21 -0
  109. package/src/reporter.js +20 -11
  110. package/src/services/key-values.js +1 -1
  111. package/src/services/logger.js +5 -4
  112. package/src/template/testomatio.hbs +651 -1366
  113. package/src/uploader.js +371 -0
  114. package/src/utils/pipe_utils.js +4 -12
  115. package/src/utils/utils.js +64 -15
  116. package/src/xmlReader.js +76 -26
  117. package/lib/adapter/jasmine/jasmine.js +0 -63
  118. package/lib/adapter/mocha/mocha.js +0 -125
  119. package/lib/fileUploader.js +0 -245
  120. package/lib/utils/chalk.js +0 -10
  121. package/src/fileUploader.js +0 -307
  122. package/src/reporter.cjs +0 -22
  123. package/src/utils/chalk.js +0 -13
@@ -0,0 +1,104 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import createDebugMessages from 'debug';
5
+ import { APP_PREFIX } from '../constants.js';
6
+ import prettyMs from 'pretty-ms';
7
+
8
+ const debug = createDebugMessages('@testomatio/reporter:pipe:debug');
9
+
10
+ export class DebugPipe {
11
+ constructor(params, store) {
12
+ this.params = params || {};
13
+ this.store = store || {};
14
+
15
+ this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
16
+ if (this.isEnabled) {
17
+ this.batch = {
18
+ isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
19
+ intervalFunction: null,
20
+ intervalTime: 5000,
21
+ tests: [],
22
+ batchIndex: 0,
23
+ };
24
+ this.logFilePath = path.join(os.tmpdir(), `testomatio.debug.${Date.now()}.json`);
25
+
26
+ debug('Creating debug file:', this.logFilePath);
27
+ fs.writeFileSync(this.logFilePath, '');
28
+ console.log(APP_PREFIX, '🪲. Debug created:');
29
+ this.testomatioEnvVars = Object.keys(process.env)
30
+ .filter(key => key.startsWith('TESTOMATIO_'))
31
+ .reduce((acc, key) => {
32
+ acc[key] = process.env[key];
33
+ return acc;
34
+ }, {});
35
+ this.logToFile({ datetime: new Date().toISOString(), timestamp: Date.now() });
36
+ this.logToFile({ data: 'variables', testomatioEnvVars: this.testomatioEnvVars });
37
+ this.logToFile({ data: 'store', store: this.store || {} });
38
+ // Bind batchUpload to the instance
39
+ this.batchUpload = this.batchUpload.bind(this);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Logs data to a file if logging is enabled.
45
+ *
46
+ * @param {Object} logData - The data to be logged.
47
+ * @returns {Promise<void>} A promise that resolves when the log data has been appended to the file.
48
+ */
49
+ logToFile(logData) {
50
+ if (!this.isEnabled) return;
51
+ const timePassedFromLastAction = Date.now() - (this.lastActionTimestamp || Date.now());
52
+ this.lastActionTimestamp = Date.now();
53
+
54
+ const logLine = JSON.stringify({ t: `+${prettyMs(timePassedFromLastAction)}`, ...logData });
55
+ fs.appendFileSync(this.logFilePath, `${logLine}\n`);
56
+ }
57
+
58
+ async prepareRun(opts) {
59
+ if (!this.isEnabled) return [];
60
+
61
+ this.logToFile({ action: 'prepareRun', data: opts });
62
+ }
63
+
64
+ async createRun(params = {}) {
65
+ if (!this.isEnabled) return;
66
+ if (params.isBatchEnabled === true || params.isBatchEnabled === false) this.batch.isEnabled = params.isBatchEnabled;
67
+
68
+ if (!this.isEnabled) return {};
69
+ if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.batchUpload, this.batch.intervalTime);
70
+
71
+ this.logToFile({ action: 'createRun', params });
72
+ }
73
+
74
+ async addTest(data) {
75
+ if (!this.isEnabled) return;
76
+
77
+ if (!this.batch.isEnabled) this.logToFile({ action: 'addTest', testId: data });
78
+ else this.batch.tests.push(data);
79
+
80
+ if (!this.batch.intervalFunction) await this.batchUpload();
81
+ }
82
+
83
+ async batchUpload() {
84
+ this.batch.batchIndex++;
85
+ if (!this.batch.isEnabled) return;
86
+ if (!this.batch.tests.length) return;
87
+
88
+ const testsToSend = this.batch.tests.splice(0);
89
+
90
+ this.logToFile({ action: 'addTestsBatch', tests: testsToSend });
91
+ }
92
+
93
+ async finishRun(params) {
94
+ if (!this.isEnabled) return;
95
+ this.logToFile({ actions: 'finishRun', params });
96
+ await this.batchUpload();
97
+ if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
98
+ console.log(APP_PREFIX, '🪲. Debug Saved to', this.logFilePath);
99
+ }
100
+
101
+ toString() {
102
+ return 'Debug Reporter';
103
+ }
104
+ }
@@ -3,17 +3,15 @@ import path from 'path';
3
3
  import pc from 'picocolors';
4
4
  import humanizeDuration from 'humanize-duration';
5
5
  import merge from 'lodash.merge';
6
- import { Octokit } from '@octokit/rest';
7
6
  import { APP_PREFIX, testomatLogoURL } from '../constants.js';
8
7
  import { ansiRegExp, isSameTest } from '../utils/utils.js';
9
8
  import { statusEmoji, fullName } from '../utils/pipe_utils.js';
10
9
 
11
10
  const debug = createDebugMessages('@testomatio/reporter:pipe:github');
12
11
 
13
-
14
12
  /**
15
- * @typedef {import('../../types').Pipe} Pipe
16
- * @typedef {import('../../types').TestData} TestData
13
+ * @typedef {import('../../types/types.js').Pipe} Pipe
14
+ * @typedef {import('../../types/types.js').TestData} TestData
17
15
  * @class GitHubPipe
18
16
  * @implements {Pipe}
19
17
  */
@@ -65,6 +63,7 @@ class GitHubPipe {
65
63
  if (!this.issue) return;
66
64
 
67
65
  if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
66
+ const { Octokit } = await import('@octokit/rest');
68
67
 
69
68
  this.octokit = new Octokit({
70
69
  auth: this.token,
@@ -155,7 +154,6 @@ class GitHubPipe {
155
154
  if (this.tests.length > 0) {
156
155
  body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
157
156
  body += this.tests
158
- // eslint-disable-next-line no-unsafe-optional-chaining
159
157
  .sort((a, b) => b?.run_time - a?.run_time)
160
158
  .slice(0, 5)
161
159
  .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
@@ -15,8 +15,8 @@ const debug = createDebugMessages('@testomatio/reporter:pipe:gitlab');
15
15
 
16
16
  /**
17
17
  * @class GitLabPipe
18
- * @typedef {import('../../types').Pipe} Pipe
19
- * @typedef {import('../../types').TestData} TestData
18
+ * @typedef {import('../../types/types.js').Pipe} Pipe
19
+ * @typedef {import('../../types/types.js').TestData} TestData
20
20
  */
21
21
  class GitLabPipe {
22
22
  constructor(params, store = {}) {
@@ -81,13 +81,13 @@ class GitLabPipe {
81
81
  let summary = `${this.hiddenCommentData}
82
82
 
83
83
  | [![Testomat.io Report](${testomatLogoURL})](https://testomat.io) | ${statusEmoji(
84
- runParams.status,
85
- )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
84
+ runParams.status,
85
+ )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
86
86
  | --- | --- |
87
87
  | Tests | ✔️ **${this.tests.length}** tests run |
88
88
  | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
89
- 'passed',
90
- )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
89
+ 'passed',
90
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
91
91
  | Duration | 🕐 **${humanizeDuration(
92
92
  parseInt(
93
93
  this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
@@ -149,7 +149,6 @@ class GitLabPipe {
149
149
  if (this.tests.length > 0) {
150
150
  body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
151
151
  body += this.tests
152
- // eslint-disable-next-line no-unsafe-optional-chaining
153
152
  .sort((a, b) => b?.run_time - a?.run_time)
154
153
  .slice(0, 5)
155
154
  .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
package/src/pipe/html.js CHANGED
@@ -5,9 +5,9 @@ import path from 'path';
5
5
  import pc from 'picocolors';
6
6
  import handlebars from 'handlebars';
7
7
  import fileUrl from 'file-url';
8
- import { fileSystem, isSameTest, ansiRegExp } from '../utils/utils.js';
8
+ import { fileSystem, isSameTest, ansiRegExp, formatStep } from '../utils/utils.js';
9
9
  import { HTML_REPORT } from '../constants.js';
10
- import { fileURLToPath } from "node:url";
10
+ import { fileURLToPath } from 'node:url';
11
11
 
12
12
  const debug = createDebugMessages('@testomatio/reporter:pipe:html');
13
13
 
@@ -66,13 +66,15 @@ class HtmlPipe {
66
66
  // empty
67
67
  }
68
68
 
69
+ async prepareRun() {}
70
+
69
71
  updateRun() {
70
72
  // empty
71
73
  }
72
74
 
73
75
  /**
74
76
  * Add test data to the result array for saving. As a result of this function, we get a result object to save.
75
- * @param {import('../../types').RunData} test - object which includes each test entry.
77
+ * @param {import('../../types/types.js').RunData} test - object which includes each test entry.
76
78
  */
77
79
  addTest(test) {
78
80
  if (!this.isEnabled) return;
@@ -128,6 +130,14 @@ class HtmlPipe {
128
130
  }
129
131
 
130
132
  tests.forEach(test => {
133
+ // steps could be an array or a string
134
+ test.steps = Array.isArray(test.steps)
135
+ ? (test.steps = test.steps
136
+ .map(step => formatStep(step))
137
+ .flat()
138
+ .join('\n'))
139
+ : test.steps;
140
+
131
141
  if (!test.message?.trim()) {
132
142
  test.message = "This test has no 'message' code";
133
143
  }
@@ -227,7 +237,6 @@ class HtmlPipe {
227
237
  </select>`,
228
238
  ),
229
239
  );
230
- /* eslint-disable */
231
240
  handlebars.registerHelper('emptyDataComponent', () => {
232
241
  const svgFilePath = path.join(__dirname, '..', 'template', 'emptyData.svg');
233
242
  const svgContent = fs.readFileSync(svgFilePath, 'utf8');
@@ -244,13 +253,12 @@ class HtmlPipe {
244
253
  <div>`,
245
254
  );
246
255
  });
247
- /* eslint-enable */
248
256
  handlebars.registerHelper('pageDispleyElements', tests => {
249
257
  // We wrapp the lines to the HTML format we need
250
258
  const totalTests = JSON.parse(
251
259
  JSON.stringify(tests)
252
260
  .replace(/<script>/g, '&lt;script&gt;')
253
- .replace(/<\/script>/g, '&lt;/script&gt;'), // eslint-disable-line
261
+ .replace(/<\/script>/g, '&lt;/script&gt;'),
254
262
  );
255
263
 
256
264
  const paginationOptions = {
@@ -277,7 +285,6 @@ class HtmlPipe {
277
285
 
278
286
  statuses.forEach(status => {
279
287
  for (const option in paginationOptions) {
280
- // eslint-disable-next-line no-prototype-builtins
281
288
  if (paginationOptions.hasOwnProperty(option)) {
282
289
  const pageSize = paginationOptions[option];
283
290
  let filteredItems = totalTests;
package/src/pipe/index.js CHANGED
@@ -7,7 +7,8 @@ import GitHubPipe from './github.js';
7
7
  import GitLabPipe from './gitlab.js';
8
8
  import CsvPipe from './csv.js';
9
9
  import HtmlPipe from './html.js';
10
- import {BitbucketPipe} from './bitbucket.js';
10
+ import { BitbucketPipe } from './bitbucket.js';
11
+ import { DebugPipe } from './debug.js';
11
12
 
12
13
  export async function pipesFactory(params, opts) {
13
14
  const extraPipes = [];
@@ -47,6 +48,7 @@ export async function pipesFactory(params, opts) {
47
48
  new CsvPipe(params, opts),
48
49
  new HtmlPipe(params, opts),
49
50
  new BitbucketPipe(params, opts),
51
+ new DebugPipe(params, opts),
50
52
  ...extraPipes,
51
53
  ];
52
54
 
@@ -55,13 +57,9 @@ export async function pipesFactory(params, opts) {
55
57
  console.log(
56
58
  APP_PREFIX,
57
59
  pc.cyan('Pipes:'),
58
- pc.cyan(
59
- pipesEnabled
60
- .map(p => p.toString())
61
- .join(', ') || 'No pipes enabled',
62
- ),
60
+ pc.cyan(pipesEnabled.map(p => p.toString()).join(', ') || 'No pipes enabled'),
63
61
  );
64
-
62
+
65
63
  if (!pipesEnabled.length) {
66
64
  console.log(
67
65
  APP_PREFIX,
@@ -11,18 +11,15 @@ import JsonCycle from 'json-cycle';
11
11
  import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
12
12
  import { isValidUrl, foundedTestLog } from '../utils/utils.js';
13
13
  import { parseFilterParams, generateFilterRequestParams, setS3Credentials } from '../utils/pipe_utils.js';
14
- import {config} from '../config.js';
14
+ import { config } from '../config.js';
15
15
 
16
16
  const debug = createDebugMessages('@testomatio/reporter:pipe:testomatio');
17
17
 
18
-
19
- if (process.env.TESTOMATIO_RUN) {
20
- // process.env.runId = process.env.TESTOMATIO_RUN;
21
- }
18
+ if (process.env.TESTOMATIO_RUN) process.env.runId = process.env.TESTOMATIO_RUN;
22
19
 
23
20
  /**
24
- * @typedef {import('../../types').Pipe} Pipe
25
- * @typedef {import('../../types').TestData} TestData
21
+ * @typedef {import('../../types/types.js').Pipe} Pipe
22
+ * @typedef {import('../../types/types.js').TestData} TestData
26
23
  * @class TestomatioPipe
27
24
  * @implements {Pipe}
28
25
  */
@@ -64,11 +61,13 @@ class TestomatioPipe {
64
61
  this.axios = axios.create({
65
62
  baseURL: `${this.url.trim()}`,
66
63
  timeout: AXIOS_TIMEOUT,
67
- proxy: proxy ? {
68
- host: proxy.hostname,
69
- port: parseInt(proxy.port, 10),
70
- protocol: proxy.protocol,
71
- } : false,
64
+ proxy: proxy
65
+ ? {
66
+ host: proxy.hostname,
67
+ port: parseInt(proxy.port, 10),
68
+ protocol: proxy.protocol,
69
+ }
70
+ : false,
72
71
  });
73
72
 
74
73
  // Pass the axios instance to the retry function
@@ -101,9 +100,10 @@ class TestomatioPipe {
101
100
  // do not finish this run (for parallel testing)
102
101
  this.proceed = process.env.TESTOMATIO_PROCEED;
103
102
  this.jiraId = process.env.TESTOMATIO_JIRA_ID;
104
- this.runId = params.runId || process.env.runId;
103
+ this.runId = params.runId || process.env.TESTOMATIO_RUN;
105
104
  this.createNewTests = params.createNewTests ?? !!process.env.TESTOMATIO_CREATE;
106
105
  this.hasUnmatchedTests = false;
106
+ this.requestFailures = 0;
107
107
 
108
108
  if (!isValidUrl(this.url.trim())) {
109
109
  this.isEnabled = false;
@@ -155,9 +155,9 @@ class TestomatioPipe {
155
155
  */
156
156
  async createRun(params = {}) {
157
157
  this.batch.isEnabled = params.isBatchEnabled ?? this.batch.isEnabled;
158
- debug('Creating run...');
159
158
  if (!this.isEnabled) return;
160
- if (this.batch.isEnabled) this.batch.intervalFunction = setInterval(this.#batchUpload, this.batch.intervalTime);
159
+ if (this.batch.isEnabled && this.isEnabled)
160
+ this.batch.intervalFunction = setInterval(this.#batchUpload, this.batch.intervalTime);
161
161
 
162
162
  let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
163
163
 
@@ -197,12 +197,14 @@ class TestomatioPipe {
197
197
  debug(' >>>>>> Run params', JSON.stringify(runParams, null, 2));
198
198
 
199
199
  if (this.runId) {
200
+ this.store.runId = this.runId;
200
201
  debug(`Run with id ${this.runId} already created, updating...`);
201
202
  const resp = await this.axios.put(`/api/reporter/${this.runId}`, runParams);
202
203
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
203
204
  return;
204
205
  }
205
206
 
207
+ debug('Creating run...');
206
208
  try {
207
209
  const resp = await this.axios.post(`/api/reporter`, runParams, {
208
210
  maxContentLength: Infinity,
@@ -222,10 +224,14 @@ class TestomatioPipe {
222
224
  process.env.runId = this.runId;
223
225
  debug('Run created', this.runId);
224
226
  } catch (err) {
227
+ const errorText = err.response?.data?.message || err.message;
228
+ console.log(errorText || err);
229
+ if (!this.apiKey) console.error('Testomat.io API key is not set');
230
+ if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
231
+
225
232
  console.error(
226
233
  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,
234
+ 'Error creating Testomat.io report (see details above), please check if your API key is valid. Skipping report',
229
235
  );
230
236
  printCreateIssue(err);
231
237
  }
@@ -234,37 +240,25 @@ class TestomatioPipe {
234
240
 
235
241
  /**
236
242
  * Decides whether to skip test reporting in case of too many request failures
237
- * @param {TestData} testData
238
243
  * @returns {boolean}
239
244
  */
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}`);
245
+ #cancelTestReportingInCaseOfTooManyReqFailures() {
246
+ if (!process.env.TESTOMATIO_MAX_REQUEST_FAILURES) return;
254
247
 
248
+ const cancelReporting = this.requestFailures >= parseInt(process.env.TESTOMATIO_MAX_REQUEST_FAILURES, 10);
249
+ if (cancelReporting) {
255
250
  this.reportingCanceledDueToReqFailures = true;
256
- this.notReportedTestsCount++;
257
-
258
- return true;
251
+ let errorMessage = `⚠️ ${process.env.TESTOMATIO_MAX_REQUEST_FAILURES}`;
252
+ errorMessage += ' requests were failed, reporting to Testomat aborted.';
253
+ console.warn(`${APP_PREFIX} ${pc.yellow(errorMessage)}`);
259
254
  }
260
-
261
- return false;
255
+ return cancelReporting;
262
256
  }
263
257
 
264
258
  #uploadSingleTest = async data => {
265
259
  if (!this.isEnabled) return;
266
260
  if (!this.runId) return;
267
- if (this.#cancelTestReportingInCaseOfTooManyReqFailures(data)) return;
261
+ if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
268
262
 
269
263
  data.api_key = this.apiKey;
270
264
  data.create = this.createNewTests;
@@ -277,40 +271,41 @@ class TestomatioPipe {
277
271
 
278
272
  debug('Adding test', json);
279
273
 
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
- }
274
+ return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, axiosAddTestrunRequestConfig).catch(err => {
275
+ this.requestFailures++;
276
+ this.notReportedTestsCount++;
277
+ if (err.response) {
278
+ if (err.response.status >= 400) {
279
+ const responseData = err.response.data || { message: '' };
296
280
  console.log(
297
281
  APP_PREFIX,
298
- pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
299
- `Report couldn't be processed: ${err?.response?.data?.message}`,
282
+ pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
283
+ pc.gray(data?.title || ''),
300
284
  );
301
- printCreateIssue(err);
302
- } else {
303
- console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
285
+ if (err.response?.data?.message?.includes('could not be matched')) {
286
+ this.hasUnmatchedTests = true;
287
+ }
288
+ return;
304
289
  }
305
- });
306
- };
307
-
290
+ console.log(
291
+ APP_PREFIX,
292
+ pc.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
293
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
294
+ );
295
+ printCreateIssue(err);
296
+ } else {
297
+ console.log(APP_PREFIX, pc.blue(data?.title || ''), "Report couldn't be processed", err);
298
+ }
299
+ });
300
+ };
308
301
 
309
302
  /**
310
303
  * Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
311
304
  */
312
305
  #batchUpload = async () => {
313
306
  if (!this.batch.isEnabled) return;
307
+ if (!this.batch.tests.length) return;
308
+ if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
314
309
  // prevent infinite loop
315
310
  if (this.batch.numberOfTimesCalledWithoutTests > 10) {
316
311
  debug('📨 Batch upload: no tests to send for 10 times, stopping batch');
@@ -322,7 +317,7 @@ class TestomatioPipe {
322
317
  this.batch.numberOfTimesCalledWithoutTests++;
323
318
  return;
324
319
  }
325
-
320
+
326
321
  this.batch.batchIndex++;
327
322
  // get tests from batch and clear batch
328
323
  const testsToSend = this.batch.tests.splice(0);
@@ -335,6 +330,8 @@ class TestomatioPipe {
335
330
  axiosAddTestrunRequestConfig,
336
331
  )
337
332
  .catch(err => {
333
+ this.requestFailures++;
334
+ this.notReportedTestsCount += testsToSend.length;
338
335
  if (err.response) {
339
336
  if (err.response.status >= 400) {
340
337
  const responseData = err.response.data || { message: '' };
@@ -380,14 +377,21 @@ class TestomatioPipe {
380
377
  }
381
378
 
382
379
  /**
383
- * @param {import('../../types').RunData} params
380
+ * @param {import('../../types/types.js').RunData} params
384
381
  * @returns
385
382
  */
386
383
  async finishRun(params) {
387
384
  if (!this.isEnabled) return;
388
-
389
- if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
385
+
390
386
  await this.#batchUpload();
387
+ if (this.batch.intervalFunction) {
388
+ clearInterval(this.batch.intervalFunction);
389
+ // this code is required in case test is added after run is finished
390
+ // (e.g. if test has artifacts, add test function will be invoked only after artifacts are uploaded)
391
+ // batch stops working after run is finished; thus, disable it to use single test uploading
392
+ this.batch.intervalFunction = null;
393
+ this.batch.isEnabled = false;
394
+ }
391
395
 
392
396
  debug('Finishing run...');
393
397
 
@@ -411,7 +415,9 @@ class TestomatioPipe {
411
415
  if (this.runId && !this.proceed) {
412
416
  await this.axios.put(`/api/reporter/${this.runId}`, {
413
417
  api_key: this.apiKey,
418
+ duration: params.duration,
414
419
  status_event,
420
+ detach: params.detach,
415
421
  tests: params.tests,
416
422
  });
417
423
  if (this.runUrl) {
@@ -426,16 +432,14 @@ class TestomatioPipe {
426
432
  console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${pc.magenta(this.runUrl)}`);
427
433
  console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
428
434
  }
435
+
429
436
  if (this.hasUnmatchedTests) {
430
437
  console.log('');
431
- // eslint-disable-next-line max-len
432
438
  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
439
  console.log(
435
440
  APP_PREFIX,
436
441
  `If you use Testomat.io as a reporter only, please re-run tests using ${pc.bold('TESTOMATIO_CREATE=1')}`,
437
442
  );
438
- // eslint-disable-next-line max-len
439
443
  console.log(
440
444
  APP_PREFIX,
441
445
  `But to keep your tests consistent it is recommended to ${pc.bold('import tests first')}`,
@@ -444,12 +448,7 @@ class TestomatioPipe {
444
448
  console.log(APP_PREFIX, 'You can do that automatically via command line tools:');
445
449
  console.log(APP_PREFIX, pc.bold('npx check-tests ... --update-ids'), 'See: https://bit.ly/js-update-ids');
446
450
  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
- );
451
+ console.log(APP_PREFIX, pc.bold('npx check-cucumber ... --update-ids'), 'See: https://bit.ly/bdd-update-ids');
453
452
  }
454
453
  } catch (err) {
455
454
  console.log(APP_PREFIX, 'Error updating status, skipping...', err);
@@ -473,7 +472,7 @@ function printCreateIssue(err) {
473
472
  console.log(
474
473
  APP_PREFIX,
475
474
  'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new',
476
- ); // eslint-disable-line max-len
475
+ );
477
476
  console.log(APP_PREFIX, 'Provide this information:');
478
477
  console.log('Error:', err.message || err.code);
479
478
  if (!err.config) return;
@@ -1,38 +1,49 @@
1
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
2
 
6
3
  /**
7
4
  * Stores path to file as artifact and uploads it to the S3 storage
8
5
  * @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
9
6
  */
10
7
  function saveArtifact(data, context = null) {
8
+ if (process.env.IS_PLAYWRIGHT)
9
+ throw new Error(`This function is not available in Playwright framework.
10
+ /Playwright supports artifacts out of the box`);
11
11
  if (!data) return;
12
12
  services.artifacts.put(data, context);
13
13
  }
14
14
 
15
15
  /**
16
16
  * Attach log message(s) to the test report
17
- * @param {...any} args
17
+ * @param string
18
18
  */
19
19
  function logMessage(...args) {
20
+ if (process.env.IS_PLAYWRIGHT) throw new Error('This function is not available in Playwright framework');
20
21
  services.logger._templateLiteralLog(...args);
21
22
  }
22
23
 
23
24
  /**
24
25
  * Similar to "log" function but marks message in report as a step
25
- * @param {*} message
26
+ * @param {string} message
26
27
  */
27
28
  function addStep(message) {
29
+ if (process.env.IS_PLAYWRIGHT)
30
+ throw new Error('This function is not available in Playwright framework. Use playwright steps');
31
+
28
32
  services.logger.step(message);
29
33
  }
30
34
 
31
35
  /**
32
36
  * Add key-value pair(s) to the test report
33
- * @param {*} keyValue
37
+ * @param {{[key: string]: string} | string} keyValue object { key: value } (multiple props allowed) or key (string)
38
+ * @param {string?} value
34
39
  */
35
- function setKeyValue(keyValue) {
40
+ function setKeyValue(keyValue, value = null) {
41
+ if (process.env.IS_PLAYWRIGHT)
42
+ throw new Error('This function is not available in Playwright framework. Use test tag instead.');
43
+
44
+ if (typeof keyValue === 'string') {
45
+ keyValue = { [keyValue]: value };
46
+ }
36
47
  services.keyValues.put(keyValue);
37
48
  }
38
49
 
@@ -0,0 +1,21 @@
1
+ // const _reporter = require('../lib/reporter.js');
2
+
3
+
4
+ // /**
5
+ // * @typedef {import('../lib/reporter-functions.js').saveArtifact} artifact
6
+ // * @typedef {import('../lib/reporter-functions.js').log} log
7
+ // * @typedef {import('../lib/reporter-functions.js').logger} logger
8
+ // * @typedef {import('../lib/reporter-functions.js')} meta
9
+ // * @typedef {import('../lib/reporter-functions.js')} step
10
+ // *
11
+ // * "Reporter" type which is object containing all types from the above
12
+ // * @typedef {{artifact: artifact, log: log, logger: logger, meta: meta, step: step, }} Reporter
13
+ // */
14
+
15
+ // // const reporter = _reporter;
16
+
17
+ // /**
18
+ // * @type {Reporter}
19
+ // */
20
+ // const reporter = _reporter;
21
+ // module.exports = reporter;