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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +61 -54
  2. package/lib/adapter/codecept.js +96 -35
  3. package/lib/adapter/cucumber/current.js +19 -12
  4. package/lib/adapter/cucumber/legacy.js +7 -6
  5. package/lib/adapter/cucumber.js +2 -2
  6. package/lib/adapter/cypress-plugin/index.js +53 -26
  7. package/lib/adapter/jasmine.js +2 -2
  8. package/lib/adapter/jest.js +50 -17
  9. package/lib/adapter/mocha.js +110 -58
  10. package/lib/adapter/playwright.js +95 -33
  11. package/lib/adapter/webdriver.js +47 -2
  12. package/lib/bin/reportXml.js +22 -16
  13. package/lib/bin/startTest.js +27 -6
  14. package/lib/client.js +185 -62
  15. package/lib/config.js +34 -0
  16. package/lib/constants.js +32 -7
  17. package/lib/data-storage.js +203 -0
  18. package/lib/fileUploader.js +135 -54
  19. package/lib/junit-adapter/adapter.js +0 -2
  20. package/lib/junit-adapter/csharp.js +3 -4
  21. package/lib/junit-adapter/index.js +3 -3
  22. package/lib/junit-adapter/java.js +35 -17
  23. package/lib/junit-adapter/javascript.js +1 -2
  24. package/lib/junit-adapter/python.js +12 -14
  25. package/lib/junit-adapter/ruby.js +1 -2
  26. package/lib/pipe/csv.js +37 -31
  27. package/lib/pipe/github.js +27 -39
  28. package/lib/pipe/gitlab.js +22 -26
  29. package/lib/pipe/html.js +361 -0
  30. package/lib/pipe/index.js +3 -1
  31. package/lib/pipe/testomatio.js +337 -54
  32. package/lib/reporter-functions.js +46 -0
  33. package/lib/reporter.js +11 -9
  34. package/lib/services/artifacts.js +57 -0
  35. package/lib/services/index.js +13 -0
  36. package/lib/services/key-values.js +58 -0
  37. package/lib/services/logger.js +311 -0
  38. package/lib/template/emptyData.svg +23 -0
  39. package/lib/template/testomatio.hbs +1421 -0
  40. package/lib/utils/pipe_utils.js +129 -0
  41. package/lib/{util.js → utils/utils.js} +147 -17
  42. package/lib/xmlReader.js +218 -122
  43. package/package.json +17 -9
  44. package/lib/_ArtifactStorageOld.js +0 -142
  45. package/lib/artifactStorage.js +0 -25
  46. package/lib/dataStorage.js +0 -244
  47. package/lib/logger.js +0 -301
package/lib/constants.js CHANGED
@@ -1,11 +1,11 @@
1
1
  const chalk = require('chalk');
2
+ const os = require('os');
3
+ const path = require('path');
2
4
 
3
5
  const APP_PREFIX = chalk.gray('[TESTOMATIO]');
4
- const TESTOMAT_ARTIFACT_SUFFIX = "testomatio_artifact_";
6
+ const AXIOS_TIMEOUT = 20 * 1000; // sum = 20sec
5
7
 
6
- const TESTOMAT_TMP_STORAGE = {
7
- mainDir: "testomatio_tmp",
8
- }
8
+ const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
9
9
 
10
10
  const CSV_HEADERS = [
11
11
  { id: 'suite_title', title: 'Suite_title' },
@@ -21,11 +21,36 @@ const STATUS = {
21
21
  SKIPPED: 'skipped',
22
22
  FINISHED: 'finished',
23
23
  };
24
+ // html pipe var
25
+ const HTML_REPORT = {
26
+ FOLDER: 'html-report',
27
+ REPORT_DEFAULT_NAME: 'testomatio-report.html',
28
+ TEMPLATE_NAME: 'testomatio.hbs',
29
+ };
30
+
31
+ const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
32
+
33
+ const REPORTER_REQUEST_RETRIES = {
34
+ retryTimeout: 5 * 1000, // sum = 5sec
35
+ retriesPerRequest: 2,
36
+ maxTotalRetries: Number(process.env.TESTOMATIO_MAX_REQUEST_FAILURES_COUNT) || 10,
37
+ withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
38
+ };
39
+
40
+ const RunStatus = {
41
+ Passed: 'passed',
42
+ Failed: 'failed',
43
+ Finished: 'finished',
44
+ };
24
45
 
25
46
  module.exports = {
26
47
  APP_PREFIX,
27
- TESTOMAT_ARTIFACT_SUFFIX,
28
- TESTOMAT_TMP_STORAGE,
48
+ TESTOMAT_TMP_STORAGE_DIR,
29
49
  CSV_HEADERS,
30
50
  STATUS,
31
- }
51
+ HTML_REPORT,
52
+ AXIOS_TIMEOUT,
53
+ testomatLogoURL,
54
+ REPORTER_REQUEST_RETRIES,
55
+ RunStatus,
56
+ };
@@ -0,0 +1,203 @@
1
+ const debug = require('debug')('@testomatio/reporter:storage');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { join } = require('path');
6
+ const { TESTOMAT_TMP_STORAGE_DIR } = require('./constants');
7
+ const { fileSystem, testRunnerHelper } = require('./utils/utils');
8
+ const crypto = require('crypto');
9
+
10
+ class DataStorage {
11
+ static #instance;
12
+
13
+ context;
14
+
15
+ /**
16
+ *
17
+ * @returns {DataStorage}
18
+ */
19
+ static getInstance() {
20
+ if (!this.#instance) {
21
+ this.#instance = new DataStorage();
22
+ }
23
+ return this.#instance;
24
+ }
25
+
26
+ setContext(context) {
27
+ this.context = context;
28
+ }
29
+
30
+ /**
31
+ * Creates data storage instance as singleton
32
+ * Stores data to global variable or to file depending on what is applicable for current test runner (adapter)
33
+ * Recommend to use composition while using this class (instead of inheritance).
34
+ * ! Also the class which will use data storage should be singleton (to avoid data loss).
35
+ */
36
+ constructor() {
37
+ // some frameworks use global variable to store data, some use file storage
38
+ this.isFileStorage = true;
39
+ }
40
+
41
+ /**
42
+ * Puts any data to storage (file or global variable).
43
+ * If file: stores data as text, if global variable – stores as array of data.
44
+ * @param {'log' | 'artifact' | 'keyvalue'} dataType
45
+ * @param {*} data anything you want to store (string, object, array, etc)
46
+ * @param {*} context could be testId or any context (test name, suite name, including their IDs etc)
47
+ * suite name + test name is used by default
48
+ * @returns
49
+ */
50
+ putData(dataType, data, context = null) {
51
+ if (!dataType || !data) return;
52
+
53
+ context = context || this.context || testRunnerHelper.getNameOfCurrentlyRunningTest();
54
+ if (!context) {
55
+ debug(`No context provided for "${dataType}" data:`, data);
56
+ return;
57
+ }
58
+ const contextHash = stringToMD5Hash(context);
59
+
60
+ if (this.isFileStorage) {
61
+ const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
62
+ fileSystem.createDir(dataDirPath);
63
+ this.#putDataToFile(dataType, data, contextHash);
64
+ } else {
65
+ this.#putDataToGlobalVar(dataType, data, contextHash);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Returns data, stored for specific test/context (or data which was stored without test id specified).
71
+ * This method will get data from global variable and/or from from file (previosly saved with put method).
72
+ *
73
+ * @param {'log' | 'artifact' | 'keyvalue'} dataType
74
+ * @param {string} context
75
+ * @returns {any []} array of data (any type), null (if no data found for context) or string (if data type is log)
76
+ */
77
+ getData(dataType, context) {
78
+ // TODO: think if it could be useful
79
+ // context = context || this.context || testRunnerHelper.getNameOfCurrentlyRunningTest();
80
+
81
+ if (!context) {
82
+ debug(`Trying to get "${dataType}" data without context`);
83
+ return null;
84
+ }
85
+
86
+ const contextHash = stringToMD5Hash(context);
87
+
88
+ let testDataFromFile = [];
89
+ let testDataFromGlobalVar = [];
90
+
91
+ if (global?.testomatioDataStore) {
92
+ testDataFromGlobalVar = this.#getDataFromGlobalVar(dataType, contextHash);
93
+ if (testDataFromGlobalVar) {
94
+ if (testDataFromGlobalVar.length) return testDataFromGlobalVar;
95
+ }
96
+ // don't return nothing if no data in global variable
97
+ }
98
+
99
+ testDataFromFile = this.#getDataFromFile(dataType, contextHash);
100
+
101
+ if (testDataFromFile.length) {
102
+ return testDataFromFile;
103
+ }
104
+ debug(`No "${dataType}" data for context "${contextHash}" in both file and global variable`);
105
+
106
+ // in case no data found for context
107
+ return null;
108
+ }
109
+
110
+ /**
111
+ * @param {'log' | 'artifact' | 'keyvalue'} dataType
112
+ * @param {string} context
113
+ * @returns aray of data (any type)
114
+ */
115
+ #getDataFromGlobalVar(dataType, context) {
116
+ try {
117
+ if (global?.testomatioDataStore[dataType]) {
118
+ const testData = global.testomatioDataStore[dataType][context];
119
+ if (testData) debug(`"${dataType}" data for constext "${context}":`, testData.join(', '));
120
+ return testData || [];
121
+ }
122
+ // debug(`No ${this.dataType} data for context ${context} in <global> storage`);
123
+ return [];
124
+ } catch (e) {
125
+ // there could be no data, ignore
126
+ }
127
+ }
128
+
129
+ /**
130
+ * @param {'log' | 'artifact' | 'keyvalue'} dataType
131
+ * @param {*} context
132
+ * @returns array of data (any type)
133
+ */
134
+ #getDataFromFile(dataType, context) {
135
+ const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
136
+ try {
137
+ const filepath = join(dataDirPath, `${dataType}_${context}`);
138
+ if (fs.existsSync(filepath)) {
139
+ const testDataAsText = fs.readFileSync(filepath, 'utf-8');
140
+ if (testDataAsText) debug(`"${dataType}" data for context "${context}":`, testDataAsText);
141
+ const testDataArr = testDataAsText?.split(os.EOL) || [];
142
+ return testDataArr;
143
+ }
144
+ // debug(`No ${this.dataType} data for ${context} in <file> storage`);
145
+ return [];
146
+ } catch (e) {
147
+ // there could be no data, ignore
148
+ }
149
+ return [];
150
+ }
151
+
152
+ /**
153
+ * Puts data to global variable. Unlike the file storage, stores data in array (file storage just append as string).
154
+ * @param {'log' | 'artifact' | 'keyvalue'} dataType
155
+ * @param {*} data
156
+ * @param {*} context
157
+ */
158
+ #putDataToGlobalVar(dataType, data, context) {
159
+ debug('Saving data to global variable for ', context, ':', data);
160
+ if (!global.testomatioDataStore) global.testomatioDataStore = {};
161
+ if (!global.testomatioDataStore?.[dataType]) global.testomatioDataStore[dataType] = {};
162
+
163
+ if (!global.testomatioDataStore?.[dataType][context]) global.testomatioDataStore[dataType][context] = [];
164
+ global.testomatioDataStore[dataType][context].push(data);
165
+ }
166
+
167
+ /**
168
+ * Puts data to file. Unlike the global variable storage, stores data as string
169
+ * @param {'log' | 'artifact' | 'keyvalue'} dataType
170
+ * @param {*} data
171
+ * @param {string} context
172
+ * @returns
173
+ */
174
+ #putDataToFile(dataType, data, context) {
175
+ const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
176
+ if (typeof data !== 'string') data = JSON.stringify(data);
177
+ const filename = `${dataType}_${context}`;
178
+ const filepath = join(dataDirPath, filename);
179
+ if (!fs.existsSync(dataDirPath)) fileSystem.createDir(dataDirPath);
180
+ debug(`Saving data to file for context "${context}" to ${filepath}. Data: ${JSON.stringify(data)}`);
181
+
182
+ // append new line if file already exists (in this case its definitely includes some data)
183
+ if (fs.existsSync(filepath)) {
184
+ fs.appendFileSync(filepath, os.EOL + data, 'utf-8');
185
+ } else {
186
+ fs.writeFileSync(filepath, data, 'utf-8');
187
+ }
188
+ }
189
+ }
190
+
191
+ function stringToMD5Hash(str) {
192
+ const md5 = crypto.createHash('md5');
193
+ md5.update(str);
194
+ const hash = md5.digest('hex');
195
+
196
+ return hash;
197
+ }
198
+
199
+ module.exports.dataStorage = DataStorage.getInstance();
200
+ module.exports.stringToMD5Hash = stringToMD5Hash;
201
+
202
+ // TODO: consider using fs promises instead of writeSync/appendFileSync to
203
+ // prevent blocking and improve performance (probably queue usage will be required)
@@ -3,10 +3,15 @@ const { S3 } = require('@aws-sdk/client-s3');
3
3
  const { Upload } = require('@aws-sdk/lib-storage');
4
4
 
5
5
  const fs = require('fs');
6
+ const util = require('util');
6
7
  const path = require('path');
8
+ const promiseRetry = require('promise-retry');
9
+
10
+ const readFile = util.promisify(fs.readFile);
11
+ const stat = util.promisify(fs.stat);
7
12
  const chalk = require('chalk');
8
13
  const { randomUUID } = require('crypto');
9
- const memoize = require('lodash.memoize');
14
+
10
15
  const { APP_PREFIX } = require('./constants');
11
16
 
12
17
  const keys = [
@@ -15,6 +20,7 @@ const keys = [
15
20
  'S3_BUCKET',
16
21
  'S3_ACCESS_KEY_ID',
17
22
  'S3_SECRET_ACCESS_KEY',
23
+ 'S3_SESSION_TOKEN',
18
24
  'TESTOMATIO_DISABLE_ARTIFACTS',
19
25
  'TESTOMATIO_PRIVATE_ARTIFACTS',
20
26
  'S3_FORCE_PATH_STYLE',
@@ -37,8 +43,12 @@ function getConfig() {
37
43
  }
38
44
 
39
45
  function getMaskedConfig() {
40
- return Object.fromEntries(Object.entries(getConfig())
41
- .map(([key, value]) => [key, key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value]));
46
+ return Object.fromEntries(
47
+ Object.entries(getConfig()).map(([key, value]) => [
48
+ key,
49
+ key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
50
+ ]),
51
+ );
42
52
  }
43
53
 
44
54
  let isEnabled;
@@ -65,7 +75,8 @@ const _getFileExtBase64 = str => {
65
75
  };
66
76
 
67
77
  const _getS3Config = () => {
68
- const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } = getConfig();
78
+ const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
79
+ getConfig();
69
80
 
70
81
  const cfg = {
71
82
  region: S3_REGION,
@@ -73,15 +84,19 @@ const _getS3Config = () => {
73
84
  accessKeyId: S3_ACCESS_KEY_ID,
74
85
  secretAccessKey: S3_SECRET_ACCESS_KEY,
75
86
  s3ForcePathStyle: S3_FORCE_PATH_STYLE,
76
- }
87
+ },
77
88
  };
78
89
 
90
+ if (S3_SESSION_TOKEN) {
91
+ cfg.credentials.sessionToken = S3_SESSION_TOKEN;
92
+ }
93
+
79
94
  if (S3_ENDPOINT) {
80
95
  cfg.endpoint = S3_ENDPOINT;
81
96
  }
82
97
 
83
98
  return cfg;
84
- }
99
+ };
85
100
 
86
101
  const uploadUsingS3 = async (filePath, runId) => {
87
102
  let ContentType;
@@ -93,48 +108,62 @@ const uploadUsingS3 = async (filePath, runId) => {
93
108
  Key = filePath.name;
94
109
  }
95
110
 
96
- if (!fs.existsSync(filePath)) {
97
- console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
98
- return;
99
- }
111
+ const { TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
100
112
 
101
- const {
102
- TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
103
- } = getConfig();
113
+ try {
114
+ debug('S3 config', getMaskedConfig());
115
+ debug('Started upload', filePath, 'to ', S3_BUCKET);
104
116
 
105
- debug('S3 config', getMaskedConfig());
106
- debug('Uploading', filePath, 'to', S3_BUCKET);
107
-
108
- const fileData = fs.readFileSync(filePath);
117
+ // Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
118
+ const isFileExist = await checkFileExists(filePath, 20, 500);
109
119
 
110
- Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
111
- const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
120
+ if (!isFileExist) {
121
+ console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
122
+ return;
123
+ }
112
124
 
113
- const s3 = new S3(_getS3Config());
125
+ debug('File: ', filePath, ' exists');
114
126
 
115
- const params = {
116
- Bucket: S3_BUCKET,
117
- Key,
118
- Body: fileData,
119
- ContentType,
120
- ACL,
121
- };
127
+ const fileData = await readFile(filePath);
128
+
129
+ Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
130
+
131
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
132
+
133
+ if (!S3_BUCKET || !fileData) {
134
+ console.log(
135
+ APP_PREFIX,
136
+ chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`),
137
+ getMaskedConfig(),
138
+ );
139
+ return;
140
+ }
141
+
142
+ const s3 = new S3(_getS3Config());
143
+
144
+ const params = {
145
+ Bucket: S3_BUCKET,
146
+ Key,
147
+ Body: fileData,
148
+ ContentType,
149
+ ACL,
150
+ };
122
151
 
123
- try {
124
152
  const out = new Upload({
125
153
  client: s3,
126
- params
154
+ params,
127
155
  });
128
156
 
129
- await out.done();
130
- debug('Uploaded', out.singleUploadResult.Location)
157
+ const link = await getS3LocationLink(out);
158
+
159
+ debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
131
160
 
132
- return out.singleUploadResult.Location;
161
+ return link;
133
162
  } catch (e) {
134
- console.log(e);
135
- console.log(APP_PREFIX, chalk.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
163
+ debug('S3 file uploading error: ', e);
136
164
 
137
165
  console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
166
+
138
167
  if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
139
168
  console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
140
169
  } else {
@@ -144,20 +173,30 @@ const fileData = fs.readFileSync(filePath);
144
173
  );
145
174
  }
146
175
  console.log(APP_PREFIX, '---------------');
147
- }
176
+ }
148
177
  };
149
178
 
150
179
  const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
151
-
152
- const {
153
- S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
154
- } = getConfig();
180
+ const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } =
181
+ getConfig();
155
182
 
156
183
  const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
157
184
 
158
185
  const fileExtension = _getFileExtBase64(buffer.toString('base64'));
159
186
  const Key = `${runId}/${fileName}${fileExtension}`;
160
187
 
188
+ if (!S3_BUCKET || !buffer) {
189
+ console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
190
+ accessKeyId: S3_ACCESS_KEY_ID,
191
+ secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
192
+ region: S3_REGION,
193
+ bucket: S3_BUCKET,
194
+ acl: ACL,
195
+ endpoint: S3_ENDPOINT,
196
+ });
197
+ return;
198
+ }
199
+
161
200
  const s3 = new S3(_getS3Config());
162
201
 
163
202
  try {
@@ -169,22 +208,15 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
169
208
  Key,
170
209
  Body: buffer,
171
210
  ACL,
172
- }
211
+ },
173
212
  });
174
- await out.done();
175
213
 
176
- return out.singleUploadResult.Location;
214
+ return await getS3LocationLink(out);
177
215
  } catch (e) {
178
- console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
179
- accessKeyId: S3_ACCESS_KEY_ID,
180
- secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
181
- region: S3_REGION,
182
- bucket: S3_BUCKET,
183
- acl: ACL,
184
- endpoint: S3_ENDPOINT,
185
- });
216
+ debug('S3 buffer uploading error: ', e);
186
217
 
187
218
  console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
219
+
188
220
  if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
189
221
  console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
190
222
  } else {
@@ -203,7 +235,9 @@ const uploadFileByPath = async (filePath, runId) => {
203
235
  return uploadUsingS3(filePath, runId);
204
236
  }
205
237
  } catch (e) {
206
- console.error(chalk.red('Error occurred while uploading artifacts'), e);
238
+ debug(e);
239
+
240
+ console.error(chalk.red('Error occurred while uploading artifacts! '), e);
207
241
  }
208
242
  };
209
243
 
@@ -213,13 +247,60 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
213
247
  return uploadUsingS3AsBuffer(buffer, fileName, runId);
214
248
  }
215
249
  } catch (e) {
216
- console.error(chalk.red('Error occurred while uploading artifacts'), e);
250
+ debug(e);
251
+
252
+ console.error(chalk.red('Error occurred while uploading artifacts! '), e);
217
253
  }
218
254
  };
219
255
 
256
+ const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
257
+ const checkFile = async () => {
258
+ const fileStats = await stat(filePath);
259
+ if (fileStats.isFile()) {
260
+ return true;
261
+ }
262
+
263
+ throw new Error('File not found');
264
+ };
265
+
266
+ try {
267
+ await promiseRetry(
268
+ {
269
+ retries: attempts,
270
+ minTimeout: intervalMs,
271
+ },
272
+ checkFile,
273
+ );
274
+
275
+ return true;
276
+ } catch (err) {
277
+ console.error(chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`));
278
+
279
+ return false;
280
+ }
281
+ };
282
+
283
+ const getS3LocationLink = async out => {
284
+ const response = await out.done();
285
+
286
+ let s3Location = response?.Location;
287
+
288
+ if (!s3Location) {
289
+ // TODO: out: a fallback case - remove after deeper testing
290
+ s3Location = out?.singleUploadResult?.Location;
291
+ debug('Uploaded singleUploadResult.Location', s3Location);
292
+
293
+ if (!s3Location) {
294
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
295
+ }
296
+ }
297
+
298
+ return s3Location;
299
+ };
300
+
220
301
  module.exports = {
221
- uploadFileByPath: memoize(uploadFileByPath),
222
- uploadFileAsBuffer: memoize(uploadFileAsBuffer),
302
+ uploadFileByPath,
303
+ uploadFileAsBuffer,
223
304
  isArtifactsEnabled,
224
305
  resetConfig,
225
306
  };
@@ -1,5 +1,4 @@
1
1
  class Adapter {
2
-
3
2
  constructor(opts) {
4
3
  this.opts = opts;
5
4
  }
@@ -19,7 +18,6 @@ class Adapter {
19
18
  formatMessage(t) {
20
19
  return t.message;
21
20
  }
22
-
23
21
  }
24
22
 
25
23
  module.exports = Adapter;
@@ -1,12 +1,11 @@
1
1
  const Adapter = require('./adapter');
2
2
 
3
3
  class CSharpAdapter extends Adapter {
4
-
5
4
  formatTest(t) {
6
5
  const title = t.title.replace(/\(.*?\)/, '').trim();
7
6
  const example = t.title.match(/\((.*?)\)/);
8
- if (example) t.example = { ...example[1].split(',')};
9
- const suite = t.suite_title.split('.')
7
+ if (example) t.example = { ...example[1].split(',') };
8
+ const suite = t.suite_title.split('.');
10
9
  t.suite_title = suite.pop();
11
10
  t.file = suite.join('/');
12
11
  t.title = title.trim();
@@ -14,4 +13,4 @@ class CSharpAdapter extends Adapter {
14
13
  }
15
14
  }
16
15
 
17
- module.exports = CSharpAdapter;
16
+ module.exports = CSharpAdapter;
@@ -6,8 +6,6 @@ const RubyAdapter = require('./ruby');
6
6
  const CSharpAdapter = require('./csharp');
7
7
 
8
8
  function AdapterFactory(lang, opts) {
9
- if (!lang) return new Adapter(opts);
10
-
11
9
  if (lang === 'java') {
12
10
  return new JavaAdapter(opts);
13
11
  }
@@ -23,6 +21,8 @@ function AdapterFactory(lang, opts) {
23
21
  if (lang === 'c#' || lang === 'csharp') {
24
22
  return new CSharpAdapter(opts);
25
23
  }
24
+
25
+ return new Adapter(opts);
26
26
  }
27
27
 
28
- module.exports = AdapterFactory;
28
+ module.exports = AdapterFactory;
@@ -2,39 +2,57 @@ const path = require('path');
2
2
  const Adapter = require('./adapter');
3
3
 
4
4
  class JavaAdapter extends Adapter {
5
-
6
5
  getFilePath(t) {
7
- const fileName = namespaceToFileName(t.suite_title)
6
+ const fileName = namespaceToFileName(t.suite_title);
8
7
  return this.opts.javaTests + path.sep + fileName;
9
8
  }
10
9
 
11
10
  formatTest(t) {
12
- const fileParts = t.suite_title.split('.')
13
- const example = t.title.match(/\[(.*)\]/)?.[1];
14
- if (example) t.example = { "#": example }
11
+ const fileParts = t.suite_title.split('.');
15
12
 
16
13
  t.file = namespaceToFileName(t.suite_title);
17
14
  t.title = t.title.split('(')[0];
18
- t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
15
+
16
+ // detect params
17
+ const paramMatches = t.title.match(/\[(.*?)\]/g);
18
+
19
+ if (paramMatches) {
20
+ const params = paramMatches.map((_match, index) => `param${index + 1}`);
21
+ if (params.length === 1) params[0] = 'param';
22
+ let paramIndex = 0;
23
+
24
+ t.title = t.title.replace(/: \[(.*?)\]/g, () => {
25
+ if (params.length < 2) return `\${param}`;
26
+ const paramName = params[paramIndex] || `param${paramIndex + 1}`;
27
+ paramIndex++;
28
+ return `\${${paramName}}`;
29
+ });
30
+ const example = {};
31
+ paramMatches.forEach((match, index) => {
32
+ example[params[index]] = match.replace(/[[\]]/g, '');
33
+ });
34
+ t.example = example;
35
+ }
36
+
37
+ t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ');
19
38
  return t;
20
39
  }
21
40
 
22
- formatStack(t) {
23
- const stack = super.formatStack(t);
41
+ // formatStack(t) {
42
+ // const stack = super.formatStack(t);
24
43
 
25
- const file = t.suite_title.split('.');
44
+ // const file = t.suite_title.split('.');
26
45
 
27
- const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
28
- const regexp = new RegExp(fileLine,"g")
29
- return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
30
- }
46
+ // const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
47
+ // const regexp = new RegExp(fileLine,"g")
48
+ // return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
49
+ // }
31
50
  }
32
51
 
33
52
  function namespaceToFileName(fileName) {
34
- const fileParts = fileName.split('.')
35
- fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '')
36
- return `${fileParts.join(path.sep) }.java`;
37
-
53
+ const fileParts = fileName.split('.');
54
+ fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
55
+ return `${fileParts.join(path.sep)}.java`;
38
56
  }
39
57
 
40
58
  module.exports = JavaAdapter;