@testomatio/reporter 1.0.0 → 1.0.1-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.
@@ -3,8 +3,8 @@ const chalk = require('chalk');
3
3
  const axios = require('axios');
4
4
  const JsonCycle = require('json-cycle');
5
5
  const { APP_PREFIX, STATUS } = require('../constants');
6
- const { isValidUrl } = require('../util');
7
- const { resetConfig } = require('../fileUploader');
6
+ const { isValidUrl, foundedTestLog } = require('../utils/utils');
7
+ const { parseFilterParams, generateFilterRequestParams, setS3Credentials, } = require('../utils/pipe_utils');
8
8
 
9
9
  const { TESTOMATIO_RUN } = process.env;
10
10
  if (TESTOMATIO_RUN) {
@@ -27,17 +27,25 @@ class TestomatioPipe {
27
27
  return;
28
28
  }
29
29
  debug('Testomatio Pipe: Enabled');
30
+ this.parallel = params.parallel;
30
31
  this.store = store || {};
31
32
  this.title = params.title || process.env.TESTOMATIO_TITLE;
32
33
  this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
33
34
  this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
34
35
  this.env = process.env.TESTOMATIO_ENV;
35
- this.axios = axios.create();
36
+
37
+ this.axios = axios.create({
38
+ baseURL: `${this.url.trim()}`,
39
+ timeout: 30000
40
+ });
41
+
36
42
  this.isEnabled = true;
37
43
  // do not finish this run (for parallel testing)
38
44
  this.proceed = process.env.TESTOMATIO_PROCEED;
45
+ this.jiraId = process.env.TESTOMATIO_JIRA_ID;
39
46
  this.runId = params.runId || process.env.runId;
40
- this.createNewTests = !!process.env.TESTOMATIO_CREATE;
47
+ this.createNewTests = params.createNewTests ?? !!process.env.TESTOMATIO_CREATE;
48
+ this.hasUnmatchedTests = false;
41
49
 
42
50
  if (!isValidUrl(this.url.trim())) {
43
51
  this.isEnabled = false;
@@ -45,61 +53,135 @@ class TestomatioPipe {
45
53
  }
46
54
  }
47
55
 
56
+ /**
57
+ * Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
58
+ * @param {Object} opts - The options for preparing the test grepList.
59
+ * @returns {Promise<string[]>} - An array containing the retrieved
60
+ * test grepList, or an empty array if no tests are found or the request is disabled.
61
+ * @throws {Error} - Throws an error if there was a problem while making the request.
62
+ */
63
+ async prepareRun(opts) {
64
+ if (!this.isEnabled) return [];
65
+
66
+ const { type, id } = parseFilterParams(opts);
67
+
68
+ try {
69
+ const q = generateFilterRequestParams({
70
+ type,
71
+ id,
72
+ apiKey: this.apiKey.trim()
73
+ });
74
+
75
+ if (!q) {
76
+ return;
77
+ }
78
+
79
+ const resp = await this.axios.get('/api/test_grep', q);
80
+ const { data } = resp;
81
+
82
+ if (Array.isArray(data?.tests) && data?.tests?.length > 0) {
83
+ foundedTestLog(APP_PREFIX, data.tests);
84
+ return data.tests;
85
+ }
86
+
87
+ console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
88
+ }
89
+ catch (err) {
90
+ console.error(
91
+ APP_PREFIX,
92
+ `🚩 Error getting Testomat.io test grepList: ${err}`,
93
+ );
94
+ }
95
+ }
96
+
48
97
  /**
49
98
  * @returns Promise<void>
50
99
  */
51
100
  async createRun() {
101
+ debug('Creating run...');
52
102
  if (!this.isEnabled) return;
53
103
 
104
+ let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
105
+
106
+ // GitHub Actions Url
107
+ if (!buildUrl && process.env.GITHUB_RUN_ID) {
108
+ // eslint-disable-next-line max-len
109
+ buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
110
+ }
111
+
112
+ // Azure DevOps Url
113
+ if (!buildUrl && process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) {
114
+ const collectionUri = process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI;
115
+ const project = process.env.SYSTEM_TEAMPROJECT;
116
+ const buildId = process.env.BUILD_BUILDID;
117
+ buildUrl = `${collectionUri}/${project}/_build/results?buildId=${buildId}`;
118
+ }
119
+
120
+ if (buildUrl && !buildUrl.startsWith('http')) buildUrl = undefined;
121
+
122
+ const accessEvent = process.env.TESTOMATIO_PUBLISH ? 'publish' : null;
123
+
54
124
  const runParams = Object.fromEntries(
55
125
  Object.entries({
126
+ ci_build_url: buildUrl,
127
+ parallel: this.parallel,
56
128
  api_key: this.apiKey.trim(),
57
129
  group_title: this.groupTitle,
130
+ access_event: accessEvent,
131
+ jira_id: this.jiraId,
58
132
  env: this.env,
59
133
  title: this.title,
60
134
  shared_run: this.sharedRun,
61
- }).filter(([, value]) => !!value)
135
+ }).filter(([, value]) => !!value),
62
136
  );
137
+ debug('Run params', JSON.stringify(runParams, null, 2));
63
138
 
64
139
  if (this.runId) {
65
- const resp = await this.axios.put(`${this.url.trim()}/api/reporter/${this.runId}`, runParams);
140
+ debug(`Run with id ${this.runId} already created, updating...`);
141
+ const resp = await this.axios.put(`/api/reporter/${this.runId}`, runParams);
66
142
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
67
143
  return;
68
144
  }
69
145
 
70
146
  try {
71
- const resp = await this.axios.post(`${this.url.trim()}/api/reporter`, runParams, {
147
+ const resp = await this.axios.post(`/api/reporter`, runParams, {
72
148
  maxContentLength: Infinity,
73
149
  maxBodyLength: Infinity,
74
150
  });
75
151
  this.runId = resp.data.uid;
76
152
  this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
153
+ this.runPublicUrl = resp.data.public_url;
77
154
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
78
155
  this.store.runUrl = this.runUrl;
156
+ this.store.runPublicUrl = this.runPublicUrl;
79
157
  this.store.runId = this.runId;
80
158
  console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId);
81
159
  process.env.runId = this.runId;
160
+ debug('Run created', this.runId);
82
161
  } catch (err) {
83
162
  console.error(
84
163
  APP_PREFIX,
85
164
  'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
165
+ err,
86
166
  );
87
167
  }
168
+ debug('"createRun" function finished');
88
169
  }
89
170
 
90
171
  /**
91
- *
92
- * @param testData data
93
- * @returns
172
+ *
173
+ * @param testData data
174
+ * @returns
94
175
  */
95
176
  addTest(data) {
177
+ debug('Adding test...');
96
178
  if (!this.isEnabled) return;
97
179
  if (!this.runId) return;
98
180
  data.api_key = this.apiKey;
99
181
  data.create = this.createNewTests;
100
182
  const json = JsonCycle.stringify(data);
101
183
 
102
- return this.axios.post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
184
+ return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, {
103
185
  maxContentLength: Infinity,
104
186
  maxBodyLength: Infinity,
105
187
  headers: {
@@ -107,29 +189,37 @@ class TestomatioPipe {
107
189
  'Content-Type': 'application/json',
108
190
  },
109
191
  })
110
- .catch((err) => {
192
+ .catch(err => {
111
193
  if (err.response) {
112
194
  if (err.response.status >= 400) {
113
195
  const responseData = err.response.data || { message: '' };
114
196
  console.log(
115
197
  APP_PREFIX,
116
- chalk.blue(this.title),
117
- `Report couldn't be processed: (${err.response.status}) ${responseData.message}`,
198
+ chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
199
+ chalk.grey(data?.title || ''),
118
200
  );
201
+ if (err.response.data.message.includes('could not be matched')) {
202
+ this.hasUnmatchedTests = true;
203
+ }
119
204
  return;
120
205
  }
121
- console.log(APP_PREFIX, chalk.blue(this.title), `Report couldn't be processed: ${err.response.data.message}`);
206
+ console.log(
207
+ APP_PREFIX,
208
+ chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
209
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
210
+ );
122
211
  } else {
123
- console.log(APP_PREFIX, chalk.blue(this.title), "Report couldn't be processed", err);
212
+ console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
124
213
  }
125
214
  });
126
215
  }
127
216
 
128
217
  /**
129
- * @param {import('../../types').RunData} params
130
- * @returns
218
+ * @param {import('../../types').RunData} params
219
+ * @returns
131
220
  */
132
221
  async finishRun(params) {
222
+ debug('Finishing run...');
133
223
  if (!this.isEnabled) return;
134
224
 
135
225
  const { status, parallel } = params;
@@ -143,7 +233,7 @@ class TestomatioPipe {
143
233
 
144
234
  try {
145
235
  if (this.runId && !this.proceed) {
146
- await this.axios.put(`${this.url}/api/reporter/${this.runId}`, {
236
+ await this.axios.put(`/api/reporter/${this.runId}`, {
147
237
  api_key: this.apiKey,
148
238
  status_event,
149
239
  tests: params.tests,
@@ -151,15 +241,44 @@ class TestomatioPipe {
151
241
  if (this.runUrl) {
152
242
  console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
153
243
  }
244
+ if (this.runPublicUrl) {
245
+ console.log(APP_PREFIX, '🌟 Public URL:', chalk.magenta(this.runPublicUrl));
246
+ }
154
247
  }
155
248
  if (this.runUrl && this.proceed) {
156
249
  const notFinishedMessage = chalk.yellow.bold('Run was not finished because of $TESTOMATIO_PROCEED');
157
250
  console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${chalk.magenta(this.runUrl)}`);
158
251
  console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
159
252
  }
253
+ if (this.hasUnmatchedTests) {
254
+ console.log('');
255
+ // eslint-disable-next-line max-len
256
+ console.log(APP_PREFIX, chalk.yellow.bold('⚠️ Some reported tests were not found in Testomat.io project'));
257
+ // eslint-disable-next-line max-len
258
+ console.log(
259
+ APP_PREFIX,
260
+ `If you use Testomat.io as a reporter only, please re-run tests using ${chalk.bold('TESTOMATIO_CREATE=1')}`,
261
+ );
262
+ // eslint-disable-next-line max-len
263
+ console.log(
264
+ APP_PREFIX,
265
+ `But to keep your tests consistent it is recommended to ${chalk.bold('import tests first')}`,
266
+ );
267
+ console.log(APP_PREFIX, 'If tests were imported but still not matched, assign test IDs to your tests.');
268
+ console.log(APP_PREFIX, 'You can do that automatically via command line tools:');
269
+ console.log(APP_PREFIX, chalk.bold('npx check-tests ... --update-ids'), 'See: https://bit.ly/js-update-ids');
270
+ console.log(APP_PREFIX, 'or for Cucumber:');
271
+ // eslint-disable-next-line max-len
272
+ console.log(
273
+ APP_PREFIX,
274
+ chalk.bold('npx check-cucumber ... --update-ids'),
275
+ 'See: https://bit.ly/bdd-update-ids',
276
+ );
277
+ }
160
278
  } catch (err) {
161
279
  console.log(APP_PREFIX, 'Error updating status, skipping...', err);
162
280
  }
281
+ debug('Run finished');
163
282
  }
164
283
 
165
284
  toString() {
@@ -168,18 +287,3 @@ class TestomatioPipe {
168
287
  }
169
288
 
170
289
  module.exports = TestomatioPipe;
171
-
172
-
173
- function setS3Credentials(artifacts) {
174
- if (!Object.keys(artifacts).length) return;
175
-
176
- console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
177
-
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
- }
package/lib/reporter.js CHANGED
@@ -1,8 +1,16 @@
1
+ const logger = require('./logger');
1
2
  const TestomatClient = require('./client');
2
3
  const TRConstants = require('./constants');
3
- const TRArtifacts = require('./ArtifactStorage');
4
+ const TRArtifacts = require('./_ArtifactStorageOld');
5
+
6
+ const log = logger.templateLiteralLog.bind(logger);
7
+ const step = logger.step.bind(logger);
4
8
 
5
9
  module.exports = {
10
+ logger,
11
+ testomatioLogger: logger,
12
+ log,
13
+ step,
6
14
  TestomatClient,
7
15
  TRConstants,
8
16
  TRArtifacts,
@@ -0,0 +1,135 @@
1
+ const { resetConfig } = require('../fileUploader');
2
+ const { APP_PREFIX } = require('../constants');
3
+
4
+ /**
5
+ * Set S3 credentials from the provided artifacts object.
6
+ * @param {Object} artifacts - The artifacts object containing S3 credentials.
7
+ */
8
+ function setS3Credentials(artifacts) {
9
+ if (!Object.keys(artifacts).length) return;
10
+
11
+ console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
12
+
13
+ if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
14
+ if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
15
+ if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
16
+ if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
17
+ if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
18
+ if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
19
+ resetConfig();
20
+ }
21
+ /**
22
+ * Generates mode request parameters based on the input params.
23
+ * @param {Object} params - The input parameters for the request.
24
+ * @param {string} params.type - The type of the request (e.g., "tag").
25
+ * @param {string} params.id - The ID associated with the request.
26
+ * @param {string} params.apiKey - The API key for authentication.
27
+ * @returns {Object|null} - An object containing the generated request parameters, or null if the type is invalid.
28
+ */
29
+ function generateFilterRequestParams(params) {
30
+ const { type, id, apiKey } = params;
31
+
32
+ if (!type) {
33
+ return;
34
+ }
35
+
36
+ if (!id) {
37
+ console.error(APP_PREFIX, `Please make sure your settings "${type.toUpperCase()}"= "${id}" is correct!`);
38
+ return;
39
+ }
40
+
41
+ return {
42
+ params: {
43
+ type,
44
+ id: encodeURIComponent(id),
45
+ api_key: apiKey
46
+ },
47
+ responseType: "json"
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Parse filter parameters from a string in the format "type=id".
53
+ * @param {string} opts - The input string containing the filter parameters.
54
+ * @returns {Object} An object containing the parsed filter parameters.
55
+ * The object has properties "type" and "id".
56
+ */
57
+ function parseFilterParams(opts) {
58
+ const [type, id] = opts.split("=");
59
+ const validType = updateFilterType(type);
60
+
61
+ return {
62
+ type: validType,
63
+ id
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Update and validate the filter type.
69
+ * @param {string} type - The original filter type.
70
+ * @returns {string|undefined} The updated and validated filter type.
71
+ * Returns undefined if the type is not valid.
72
+ */
73
+ function updateFilterType(type) {
74
+ const typeLowerCase = type.toLowerCase();
75
+
76
+ const filterTypes = [
77
+ "tag-name",
78
+ "plan-id",
79
+ "label",
80
+ "jira-ticket",
81
+ ];
82
+
83
+ const filterApi = [
84
+ "tag",
85
+ "plan",
86
+ "label",
87
+ "jira",
88
+ // "ims-issue", //TODO: WIP
89
+ ];
90
+
91
+ if (!filterTypes.includes(typeLowerCase)) {
92
+ console.log(APP_PREFIX, `❗❗❗ Invalid "filter=${type}" start settings! Available option list: ${filterTypes}`);
93
+ return;
94
+ }
95
+
96
+ const index = filterTypes.indexOf(typeLowerCase);
97
+
98
+ return (index !== -1)
99
+ ? filterApi[index]
100
+ : undefined
101
+ }
102
+
103
+ /**
104
+ * Return an emoji based on the provided status.
105
+ * @param {string} status - The status value ('passed', 'failed', or 'skipped').
106
+ * @returns {string} - An emoji corresponding to the provided status.
107
+ */
108
+ function statusEmoji(status) {
109
+ if (status === 'passed') return '🟢';
110
+ if (status === 'failed') return '🔴';
111
+ if (status === 'skipped') return '🟡';
112
+ return '';
113
+ }
114
+
115
+ /**
116
+ * Generate a full name string based on the provided test object.
117
+ * @param {object} t - The test object.
118
+ * @returns {string} - A formatted full name string for the test object.
119
+ */
120
+ function fullName(t) {
121
+ let line = '';
122
+ if (t.suite_title) line = `${t.suite_title}: `;
123
+ line += `**${t.title}**`;
124
+ if (t.example) line += ` \`[${Object.values(t.example)}]\``;
125
+ return line;
126
+ }
127
+
128
+ module.exports = {
129
+ updateFilterType,
130
+ parseFilterParams,
131
+ generateFilterRequestParams,
132
+ setS3Credentials,
133
+ statusEmoji,
134
+ fullName
135
+ };