@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
@@ -0,0 +1,204 @@
1
+ import createDebugMessages from 'debug';
2
+ import fs from 'fs';
3
+ import path, { join } from 'path';
4
+ import os from 'os';
5
+ import { TESTOMAT_TMP_STORAGE_DIR } from './constants.js';
6
+ import { fileSystem, testRunnerHelper } from './utils/utils.js';
7
+ import crypto from 'crypto';
8
+
9
+ const debug = createDebugMessages('@testomatio/reporter:storage');
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
+ export const dataStorage = DataStorage.getInstance();
200
+
201
+ export { stringToMD5Hash };
202
+
203
+ // TODO: consider using fs promises instead of writeSync/appendFileSync to
204
+ // prevent blocking and improve performance (probably queue usage will be required)
@@ -0,0 +1,307 @@
1
+ import createDebugMessages from 'debug';
2
+ import { S3 } from '@aws-sdk/client-s3';
3
+ import { Upload } from '@aws-sdk/lib-storage';
4
+ import fs from 'fs';
5
+ import util from 'util';
6
+ import path from 'path';
7
+ import promiseRetry from 'promise-retry';
8
+ import pc from 'picocolors';
9
+ import { randomUUID } from 'crypto';
10
+ import { APP_PREFIX } from './constants.js';
11
+
12
+ const readFile = util.promisify(fs.readFile);
13
+ const stat = util.promisify(fs.stat);
14
+ const debug = createDebugMessages('@testomatio/reporter:file-uploader');
15
+ const keys = [
16
+ 'S3_ENDPOINT',
17
+ 'S3_REGION',
18
+ 'S3_BUCKET',
19
+ 'S3_ACCESS_KEY_ID',
20
+ 'S3_SECRET_ACCESS_KEY',
21
+ 'S3_SESSION_TOKEN',
22
+ 'TESTOMATIO_DISABLE_ARTIFACTS',
23
+ 'TESTOMATIO_PRIVATE_ARTIFACTS',
24
+ 'S3_FORCE_PATH_STYLE',
25
+ ];
26
+
27
+ let config;
28
+
29
+ function resetConfig() {
30
+ config = undefined;
31
+ isEnabled = undefined;
32
+ }
33
+
34
+ function getConfig() {
35
+ if (config) return config;
36
+ config = keys.reduce((acc, key) => {
37
+ acc[key] = process.env[key];
38
+ return acc;
39
+ }, {});
40
+ return config;
41
+ }
42
+
43
+ function getMaskedConfig() {
44
+ return Object.fromEntries(
45
+ Object.entries(getConfig()).map(([key, value]) => [
46
+ key,
47
+ key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
48
+ ]),
49
+ );
50
+ }
51
+
52
+ let isEnabled;
53
+
54
+ const isArtifactsEnabled = () => {
55
+ if (isEnabled !== undefined) return isEnabled;
56
+ const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = getConfig();
57
+ isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
58
+ debug(`Upload is ${isEnabled ? 'enabled' : 'disabled'}`);
59
+ return isEnabled;
60
+ };
61
+
62
+ const _getFileExtBase64 = str => {
63
+ const type = str.charAt(0);
64
+
65
+ return (
66
+ {
67
+ '/': '.jpg',
68
+ i: '.png',
69
+ R: '.gif',
70
+ U: '.webp',
71
+ }[type] || ''
72
+ );
73
+ };
74
+
75
+ const _getS3Config = () => {
76
+ const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
77
+ getConfig();
78
+
79
+ const cfg = {
80
+ region: S3_REGION,
81
+ credentials: {
82
+ accessKeyId: S3_ACCESS_KEY_ID,
83
+ secretAccessKey: S3_SECRET_ACCESS_KEY,
84
+ s3ForcePathStyle: S3_FORCE_PATH_STYLE,
85
+ },
86
+ };
87
+
88
+ if (S3_SESSION_TOKEN) {
89
+ cfg.credentials.sessionToken = S3_SESSION_TOKEN;
90
+ }
91
+
92
+ if (S3_ENDPOINT) {
93
+ cfg.endpoint = S3_ENDPOINT;
94
+ }
95
+
96
+ return cfg;
97
+ };
98
+
99
+ const uploadUsingS3 = async (filePath, runId) => {
100
+ let ContentType;
101
+ let Key;
102
+
103
+ if (typeof filePath === 'object') {
104
+ ContentType = filePath?.type;
105
+ filePath = filePath?.path;
106
+ Key = filePath?.name;
107
+ }
108
+
109
+ const { TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
110
+
111
+ try {
112
+ debug('S3 config', getMaskedConfig());
113
+ debug('Started upload', filePath, 'to ', S3_BUCKET);
114
+
115
+ // Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
116
+ const isFileExist = await checkFileExists(filePath, 20, 500);
117
+
118
+ if (!isFileExist) {
119
+ console.error(pc.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
120
+ return;
121
+ }
122
+
123
+ debug('File: ', filePath, ' exists');
124
+
125
+ const fileData = await readFile(filePath);
126
+
127
+ Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
128
+
129
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
130
+
131
+ if (!S3_BUCKET || !fileData) {
132
+ console.log(
133
+ APP_PREFIX,
134
+ pc.bold(pc.red(`Failed uploading '${Key}'. Please check S3 credentials`)),
135
+ getMaskedConfig(),
136
+ );
137
+ return;
138
+ }
139
+
140
+ const s3 = new S3(_getS3Config());
141
+
142
+ /**
143
+ * @type {import('@aws-sdk/client-s3').PutObjectCommandInput}
144
+ */
145
+ const params = {
146
+ Bucket: S3_BUCKET,
147
+ Key,
148
+ Body: fileData,
149
+ ContentType,
150
+ ACL,
151
+ };
152
+
153
+ const out = new Upload({
154
+ client: s3,
155
+ params,
156
+ });
157
+
158
+ const link = await getS3LocationLink(out);
159
+
160
+ debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
161
+
162
+ return link;
163
+ } catch (e) {
164
+ debug('S3 file uploading error: ', e);
165
+
166
+ console.log(APP_PREFIX, `To ${pc.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
167
+
168
+ if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
169
+ console.log(APP_PREFIX, `To enable ${pc.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
170
+ } else {
171
+ console.log(
172
+ APP_PREFIX,
173
+ `To enable ${pc.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
174
+ );
175
+ }
176
+ console.log(APP_PREFIX, '---------------');
177
+ }
178
+ };
179
+
180
+ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
181
+ const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } =
182
+ getConfig();
183
+
184
+ const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
185
+
186
+ const fileExtension = _getFileExtBase64(buffer.toString('base64'));
187
+ const Key = `${runId}/${fileName}${fileExtension}`;
188
+
189
+ if (!S3_BUCKET || !buffer) {
190
+ console.log(APP_PREFIX, pc.bold(pc.red(`Failed uploading '${Key}'. Please check S3 credentials`)), {
191
+ accessKeyId: S3_ACCESS_KEY_ID,
192
+ secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
193
+ region: S3_REGION,
194
+ bucket: S3_BUCKET,
195
+ acl: ACL,
196
+ endpoint: S3_ENDPOINT,
197
+ });
198
+ return;
199
+ }
200
+
201
+ const s3 = new S3(_getS3Config());
202
+
203
+ try {
204
+ const out = new Upload({
205
+ client: s3,
206
+
207
+ params: {
208
+ Bucket: S3_BUCKET,
209
+ Key,
210
+ Body: buffer,
211
+ ACL,
212
+ },
213
+ });
214
+
215
+ return await getS3LocationLink(out);
216
+ } catch (e) {
217
+ debug('S3 buffer uploading error: ', e);
218
+
219
+ console.log(APP_PREFIX, `To ${pc.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
220
+
221
+ if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
222
+ console.log(APP_PREFIX, `To enable ${pc.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
223
+ } else {
224
+ console.log(
225
+ APP_PREFIX,
226
+ `To enable ${pc.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
227
+ );
228
+ }
229
+ console.log(APP_PREFIX, '---------------');
230
+ }
231
+ };
232
+
233
+ const uploadFileByPath = async (filePath, runId) => {
234
+ try {
235
+ if (isArtifactsEnabled()) {
236
+ return uploadUsingS3(filePath, runId);
237
+ }
238
+ } catch (e) {
239
+ debug(e);
240
+
241
+ console.error(pc.red('Error occurred while uploading artifacts! '), e);
242
+ }
243
+ };
244
+
245
+ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
246
+ try {
247
+ if (isArtifactsEnabled()) {
248
+ return uploadUsingS3AsBuffer(buffer, fileName, runId);
249
+ }
250
+ } catch (e) {
251
+ debug(e);
252
+
253
+ console.error(pc.red('Error occurred while uploading artifacts! '), e);
254
+ }
255
+ };
256
+
257
+ const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
258
+ const checkFile = async () => {
259
+ const fileStats = await stat(filePath);
260
+ if (fileStats.isFile()) {
261
+ return true;
262
+ }
263
+
264
+ throw new Error('File not found');
265
+ };
266
+
267
+ try {
268
+ await promiseRetry(
269
+ {
270
+ retries: attempts,
271
+ minTimeout: intervalMs,
272
+ },
273
+ checkFile,
274
+ );
275
+
276
+ return true;
277
+ } catch (err) {
278
+ console.error(pc.yellow(`File ${filePath} was not found or did not have time to be generated...`));
279
+
280
+ return false;
281
+ }
282
+ };
283
+
284
+ const getS3LocationLink = async out => {
285
+ const response = await out.done();
286
+
287
+ let s3Location = response?.Location;
288
+
289
+ if (!s3Location) {
290
+ // TODO: out: a fallback case - remove after deeper testing
291
+ s3Location = out?.singleUploadResult?.Location;
292
+ debug('Uploaded singleUploadResult.Location', s3Location);
293
+
294
+ if (!s3Location) {
295
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
296
+ }
297
+ }
298
+
299
+ return s3Location;
300
+ };
301
+
302
+ export const upload = {
303
+ uploadFileByPath,
304
+ uploadFileAsBuffer,
305
+ isArtifactsEnabled,
306
+ resetConfig,
307
+ };
@@ -0,0 +1,23 @@
1
+ class Adapter {
2
+ constructor(opts) {
3
+ this.opts = opts;
4
+ }
5
+
6
+ getFilePath(t) {
7
+ return t.file;
8
+ }
9
+
10
+ formatTest(t) {
11
+ return t;
12
+ }
13
+
14
+ formatStack(t) {
15
+ return t.stack || '';
16
+ }
17
+
18
+ formatMessage(t) {
19
+ return t.message;
20
+ }
21
+ }
22
+
23
+ export default Adapter;
@@ -0,0 +1,16 @@
1
+ import Adapter from './adapter.js';
2
+
3
+ class CSharpAdapter extends Adapter {
4
+ formatTest(t) {
5
+ const title = t.title.replace(/\(.*?\)/, '').trim();
6
+ const example = t.title.match(/\((.*?)\)/);
7
+ if (example) t.example = { ...example[1].split(',') };
8
+ const suite = t.suite_title.split('.');
9
+ t.suite_title = suite.pop();
10
+ t.file = suite.join('/');
11
+ t.title = title.trim();
12
+ return t;
13
+ }
14
+ }
15
+
16
+ export default CSharpAdapter;
@@ -0,0 +1,28 @@
1
+ import Adapter from './adapter.js';
2
+ import JavaScriptAdapter from './javascript.js';
3
+ import JavaAdapter from './java.js';
4
+ import PythonAdapter from './python.js';
5
+ import RubyAdapter from './ruby.js';
6
+ import CSharpAdapter from './csharp.js';
7
+
8
+ function AdapterFactory(lang, opts) {
9
+ if (lang === 'java') {
10
+ return new JavaAdapter(opts);
11
+ }
12
+ if (lang === 'js') {
13
+ return new JavaScriptAdapter(opts);
14
+ }
15
+ if (lang === 'python') {
16
+ return new PythonAdapter(opts);
17
+ }
18
+ if (lang === 'ruby') {
19
+ return new RubyAdapter(opts);
20
+ }
21
+ if (lang === 'c#' || lang === 'csharp') {
22
+ return new CSharpAdapter(opts);
23
+ }
24
+
25
+ return new Adapter(opts);
26
+ }
27
+
28
+ export default AdapterFactory;
@@ -0,0 +1,58 @@
1
+ import path from 'path';
2
+ import Adapter from './adapter.js';
3
+
4
+ class JavaAdapter extends Adapter {
5
+ getFilePath(t) {
6
+ const fileName = namespaceToFileName(t.suite_title);
7
+ return this.opts.javaTests + path.sep + fileName;
8
+ }
9
+
10
+ formatTest(t) {
11
+ const fileParts = t.suite_title.split('.');
12
+
13
+ t.file = namespaceToFileName(t.suite_title);
14
+ t.title = t.title.split('(')[0];
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, ' | ');
38
+ return t;
39
+ }
40
+
41
+ // formatStack(t) {
42
+ // const stack = super.formatStack(t);
43
+
44
+ // const file = t.suite_title.split('.');
45
+
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
+ // }
50
+ }
51
+
52
+ function namespaceToFileName(fileName) {
53
+ const fileParts = fileName.split('.');
54
+ fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
55
+ return `${fileParts.join(path.sep)}.java`;
56
+ }
57
+
58
+ export default JavaAdapter;
@@ -0,0 +1,31 @@
1
+ import createCallsiteRecord from 'callsite-record';
2
+ import path from 'path';
3
+ import Adapter from './adapter.js';
4
+
5
+ class JavaScriptAdapter extends Adapter {
6
+ formatStack(t) {
7
+ let stack = super.formatStack(t);
8
+
9
+ try {
10
+ const error = new Error(stack.split('\n')[0]);
11
+ error.stack = stack;
12
+ const record = createCallsiteRecord({
13
+ forError: error,
14
+ });
15
+ // @ts-ignore
16
+ if (record && !record.filename.startsWith('http')) {
17
+ stack += record.renderSync({
18
+ stackFilter: frame =>
19
+ frame.fileName?.indexOf(path.sep) > -1 &&
20
+ frame.fileName?.indexOf('node_modules') < 0 &&
21
+ frame.fileName?.indexOf('internal') < 0,
22
+ });
23
+ }
24
+ return stack;
25
+ } catch (err) {
26
+ return stack;
27
+ }
28
+ }
29
+ }
30
+
31
+ export default JavaScriptAdapter;