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