@testomatio/reporter 1.3.5-beta → 1.4.1-beta-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.
- package/README.md +4 -4
- package/lib/adapter/codecept.js +11 -9
- package/lib/adapter/cucumber/current.js +4 -4
- package/lib/adapter/cucumber/legacy.js +3 -3
- package/lib/adapter/cypress-plugin/index.js +1 -1
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +12 -17
- package/lib/adapter/mocha.js +22 -50
- package/lib/adapter/playwright.js +34 -22
- package/lib/adapter/webdriver.js +1 -1
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +93 -27
- package/lib/constants.js +4 -6
- package/lib/fileUploader.js +106 -38
- package/lib/junit-adapter/java.js +27 -9
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +5 -16
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/testomatio.js +136 -34
- package/lib/reporter-functions.js +43 -0
- package/lib/reporter.js +11 -5
- package/lib/storages/artifact-storage.js +70 -0
- package/lib/storages/data-storage.js +307 -0
- package/lib/storages/key-value-storage.js +58 -0
- package/lib/{logger.js → storages/logger.js} +103 -114
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/{util.js → utils/utils.js} +129 -12
- package/lib/xmlReader.js +132 -44
- package/package.json +9 -7
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -244
package/lib/client.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
const debug = require('debug')('@testomatio/reporter:client');
|
|
2
2
|
const createCallsiteRecord = require('callsite-record');
|
|
3
3
|
const { sep, join } = require('path');
|
|
4
|
+
const { minimatch } = require('minimatch')
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const chalk = require('chalk');
|
|
6
7
|
const { randomUUID } = require('crypto');
|
|
7
8
|
const upload = require('./fileUploader');
|
|
8
9
|
const { APP_PREFIX } = require('./constants');
|
|
9
10
|
const pipesFactory = require('./pipe');
|
|
11
|
+
const artifactStorage = require('./storages/artifact-storage');
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* @typedef {import('../types').TestData} TestData
|
|
@@ -27,21 +29,74 @@ class Client {
|
|
|
27
29
|
this.queue = Promise.resolve();
|
|
28
30
|
this.totalUploaded = 0;
|
|
29
31
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
32
|
+
this.executionList = Promise.resolve();
|
|
33
|
+
|
|
30
34
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Asynchronously prepares the execution list for running tests through various pipes.
|
|
39
|
+
* Each pipe in the client is checked for enablement,
|
|
40
|
+
* and if all pipes are disabled, the function returns a resolved Promise.
|
|
41
|
+
* Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
|
|
42
|
+
* The results are then filtered to remove any undefined values.
|
|
43
|
+
* If no valid results are found, the function returns undefined.
|
|
44
|
+
* Otherwise, it returns the first non-empty array from the filtered results.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} params - The options for preparing the test execution list.
|
|
47
|
+
* @param {string} params.pipe - Name of the executed pipe.
|
|
48
|
+
* @param {string} params.pipeOptions - Filter option.
|
|
49
|
+
* @returns {Promise<any>} - A Promise that resolves to an
|
|
50
|
+
* array containing the prepared execution list,
|
|
51
|
+
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
52
|
+
*/
|
|
53
|
+
async prepareRun(params) {
|
|
54
|
+
const { pipe, pipeOptions } = params;
|
|
55
|
+
// all pipes disabled, skipping
|
|
56
|
+
if (!this.pipes.some(p => p.isEnabled)) {
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
|
|
62
|
+
|
|
63
|
+
if (!filterPipe.isEnabled) {
|
|
64
|
+
// TODO:for the future for the another pipes
|
|
65
|
+
console.warn(
|
|
66
|
+
APP_PREFIX,
|
|
67
|
+
`At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`
|
|
68
|
+
);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const results = await Promise.all(this.pipes.map(async p =>
|
|
73
|
+
({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })
|
|
74
|
+
));
|
|
75
|
+
|
|
76
|
+
const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
|
|
77
|
+
|
|
78
|
+
if (!result || result.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
debug('Execution tests list', result);
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(APP_PREFIX, err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
33
90
|
/**
|
|
34
91
|
* Used to create a new Test run
|
|
35
92
|
*
|
|
36
93
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
37
94
|
*/
|
|
38
95
|
createRun() {
|
|
96
|
+
debug('Creating run...');
|
|
39
97
|
// all pipes disabled, skipping
|
|
40
98
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
41
99
|
|
|
42
|
-
global.testomatioArtifacts = [];
|
|
43
|
-
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
44
|
-
|
|
45
100
|
this.queue = this.queue
|
|
46
101
|
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
47
102
|
.catch(err => console.log(APP_PREFIX, err))
|
|
@@ -55,10 +110,9 @@ class Client {
|
|
|
55
110
|
*
|
|
56
111
|
* @param {string|undefined} status
|
|
57
112
|
* @param {TestData} [testData]
|
|
58
|
-
* @param {string[]} [storeArtifacts]
|
|
59
113
|
* @returns {Promise<PipeResult[]>}
|
|
60
114
|
*/
|
|
61
|
-
async addTestRun(status, testData
|
|
115
|
+
async addTestRun(status, testData) {
|
|
62
116
|
// all pipes disabled, skipping
|
|
63
117
|
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
64
118
|
|
|
@@ -92,28 +146,26 @@ class Client {
|
|
|
92
146
|
message = error?.message;
|
|
93
147
|
}
|
|
94
148
|
if (steps) {
|
|
95
|
-
stack
|
|
149
|
+
stack = this.formatSteps(stack, steps);
|
|
96
150
|
}
|
|
97
151
|
|
|
98
152
|
stack += testData.stack || '';
|
|
99
153
|
|
|
154
|
+
// ATTACH LOGS from storage
|
|
100
155
|
// in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
|
|
101
|
-
const logger = require('./logger');
|
|
156
|
+
const logger = require('./storages/logger');
|
|
102
157
|
const testLogs = logger.getLogs(test_id);
|
|
103
158
|
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
104
159
|
if (stack) stack += '\n\n';
|
|
105
160
|
stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
|
|
106
161
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
162
|
+
// GET ARTIFACTS from storage
|
|
163
|
+
const artifactFiles = artifactStorage.get(test_id);
|
|
164
|
+
if (artifactFiles) files.push(...artifactFiles);
|
|
111
165
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
global.testomatioArtifacts = [];
|
|
116
|
-
}
|
|
166
|
+
// GET KEY-VALUEs from storage
|
|
167
|
+
const keyValueStorage = require('./storages/key-value-storage');
|
|
168
|
+
const keyValues = keyValueStorage.get(test_id);
|
|
117
169
|
|
|
118
170
|
for (const file of files) {
|
|
119
171
|
uploadedFiles.push(upload.uploadFileByPath(file, this.uuid));
|
|
@@ -126,7 +178,7 @@ class Client {
|
|
|
126
178
|
|
|
127
179
|
const artifacts = await Promise.all(uploadedFiles);
|
|
128
180
|
|
|
129
|
-
global.testomatioArtifacts = [];
|
|
181
|
+
// global.testomatioArtifacts = [];
|
|
130
182
|
|
|
131
183
|
this.totalUploaded += uploadedFiles.filter(n => n).length;
|
|
132
184
|
|
|
@@ -144,6 +196,7 @@ class Client {
|
|
|
144
196
|
message,
|
|
145
197
|
run_time: parseFloat(time),
|
|
146
198
|
artifacts,
|
|
199
|
+
meta: keyValues,
|
|
147
200
|
};
|
|
148
201
|
|
|
149
202
|
this.queue = this.queue.then(() =>
|
|
@@ -170,6 +223,7 @@ class Client {
|
|
|
170
223
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
171
224
|
*/
|
|
172
225
|
updateRunStatus(status, isParallel = false) {
|
|
226
|
+
debug('Updating run status...');
|
|
173
227
|
// all pipes disabled, skipping
|
|
174
228
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
175
229
|
|
|
@@ -178,14 +232,16 @@ class Client {
|
|
|
178
232
|
this.queue = this.queue
|
|
179
233
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
180
234
|
.then(() => {
|
|
181
|
-
debug('TOTAL
|
|
235
|
+
debug('TOTAL artifacts', this.totalUploaded);
|
|
236
|
+
if (this.totalUploaded && !upload.isArtifactsEnabled())
|
|
237
|
+
debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
|
|
182
238
|
|
|
183
|
-
if (
|
|
239
|
+
if (this.totalUploaded && upload.isArtifactsEnabled()) {
|
|
184
240
|
console.log(
|
|
185
241
|
APP_PREFIX,
|
|
186
|
-
`🗄️
|
|
242
|
+
`🗄️ ${this.totalUploaded} artifacts ${
|
|
187
243
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
188
|
-
} uploaded to S3 bucket
|
|
244
|
+
} uploaded to S3 bucket`,
|
|
189
245
|
);
|
|
190
246
|
}
|
|
191
247
|
})
|
|
@@ -212,17 +268,21 @@ class Client {
|
|
|
212
268
|
stack += '\n\n';
|
|
213
269
|
}
|
|
214
270
|
|
|
271
|
+
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
272
|
+
|
|
215
273
|
try {
|
|
274
|
+
let hasFrame = false;
|
|
216
275
|
const record = createCallsiteRecord({
|
|
217
276
|
forError: error,
|
|
277
|
+
isCallsiteFrame: frame => {
|
|
278
|
+
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
279
|
+
if (hasFrame) return false;
|
|
280
|
+
if (isNotInternalFrame(frame)) hasFrame = true;
|
|
281
|
+
return hasFrame;
|
|
282
|
+
}
|
|
218
283
|
});
|
|
219
284
|
if (record && !record.filename.startsWith('http')) {
|
|
220
|
-
stack += record.renderSync({
|
|
221
|
-
stackFilter: frame =>
|
|
222
|
-
frame.getFileName().indexOf(sep) > -1 &&
|
|
223
|
-
frame.getFileName().indexOf('node_modules') < 0 &&
|
|
224
|
-
frame.getFileName().indexOf('internal') < 0,
|
|
225
|
-
});
|
|
285
|
+
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
226
286
|
}
|
|
227
287
|
return stack;
|
|
228
288
|
} catch (e) {
|
|
@@ -231,4 +291,10 @@ class Client {
|
|
|
231
291
|
}
|
|
232
292
|
}
|
|
233
293
|
|
|
294
|
+
function isNotInternalFrame(frame) {
|
|
295
|
+
return frame.getFileName().includes(sep) &&
|
|
296
|
+
!frame.getFileName().includes('node_modules') &&
|
|
297
|
+
!frame.getFileName().includes('internal')
|
|
298
|
+
}
|
|
299
|
+
|
|
234
300
|
module.exports = Client;
|
package/lib/constants.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
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_";
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
mainDir: "testomatio_tmp",
|
|
8
|
-
}
|
|
7
|
+
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
9
8
|
|
|
10
9
|
const CSV_HEADERS = [
|
|
11
10
|
{ id: 'suite_title', title: 'Suite_title' },
|
|
@@ -24,8 +23,7 @@ const STATUS = {
|
|
|
24
23
|
|
|
25
24
|
module.exports = {
|
|
26
25
|
APP_PREFIX,
|
|
27
|
-
|
|
28
|
-
TESTOMAT_TMP_STORAGE,
|
|
26
|
+
TESTOMAT_TMP_STORAGE_DIR,
|
|
29
27
|
CSV_HEADERS,
|
|
30
28
|
STATUS,
|
|
31
29
|
}
|
package/lib/fileUploader.js
CHANGED
|
@@ -3,10 +3,16 @@ 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
14
|
const memoize = require('lodash.memoize');
|
|
15
|
+
|
|
10
16
|
const { APP_PREFIX } = require('./constants');
|
|
11
17
|
|
|
12
18
|
const keys = [
|
|
@@ -86,6 +92,7 @@ const _getS3Config = () => {
|
|
|
86
92
|
const uploadUsingS3 = async (filePath, runId) => {
|
|
87
93
|
let ContentType;
|
|
88
94
|
let Key;
|
|
95
|
+
let s3FileLocation;
|
|
89
96
|
|
|
90
97
|
if (typeof filePath === 'object') {
|
|
91
98
|
ContentType = filePath.type;
|
|
@@ -93,48 +100,67 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
93
100
|
Key = filePath.name;
|
|
94
101
|
}
|
|
95
102
|
|
|
96
|
-
if (!fs.existsSync(filePath)) {
|
|
97
|
-
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
103
|
const {
|
|
102
|
-
TESTOMATIO_PRIVATE_ARTIFACTS,
|
|
104
|
+
TESTOMATIO_PRIVATE_ARTIFACTS,
|
|
105
|
+
S3_BUCKET
|
|
103
106
|
} = getConfig();
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const fileData = fs.readFileSync(filePath);
|
|
108
|
+
try {
|
|
109
|
+
debug('S3 config', getMaskedConfig());
|
|
110
|
+
debug('Try upload', filePath, 'to', S3_BUCKET);
|
|
109
111
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
// Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
|
|
113
|
+
const isFileExist = await checkFileExists(filePath, 20, 500);
|
|
112
114
|
|
|
113
|
-
|
|
115
|
+
if (!isFileExist) {
|
|
116
|
+
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
114
119
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
debug('Yes, ', filePath, 'is exist.');
|
|
121
|
+
|
|
122
|
+
const fileData = await readFile(filePath);
|
|
123
|
+
|
|
124
|
+
Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
|
|
125
|
+
|
|
126
|
+
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
127
|
+
|
|
128
|
+
if (!S3_BUCKET || !fileData) {
|
|
129
|
+
console.log(
|
|
130
|
+
APP_PREFIX,
|
|
131
|
+
chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const s3 = new S3(_getS3Config());
|
|
136
|
+
|
|
137
|
+
const params = {
|
|
138
|
+
Bucket: S3_BUCKET,
|
|
139
|
+
Key,
|
|
140
|
+
Body: fileData,
|
|
141
|
+
ContentType,
|
|
142
|
+
ACL,
|
|
143
|
+
};
|
|
122
144
|
|
|
123
|
-
try {
|
|
124
145
|
const out = new Upload({
|
|
125
146
|
client: s3,
|
|
126
147
|
params
|
|
127
148
|
});
|
|
128
149
|
|
|
129
|
-
await out.done();
|
|
130
|
-
debug('Uploaded', out.singleUploadResult.Location)
|
|
150
|
+
const result = await out.done();
|
|
131
151
|
|
|
132
|
-
|
|
133
|
-
|
|
152
|
+
s3FileLocation = result?.Location;
|
|
153
|
+
debug('Uploaded location', s3FileLocation);
|
|
154
|
+
|
|
155
|
+
return s3FileLocation || console.log(
|
|
156
|
+
chalk.bold.red(`Problems getting the S3 artifact's link , contact the ${APP_PREFIX} team!`)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
134
160
|
console.log(e);
|
|
135
|
-
console.log(APP_PREFIX, chalk.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
|
|
136
161
|
|
|
137
162
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
163
|
+
|
|
138
164
|
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
139
165
|
console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
|
|
140
166
|
} else {
|
|
@@ -144,10 +170,11 @@ const fileData = fs.readFileSync(filePath);
|
|
|
144
170
|
);
|
|
145
171
|
}
|
|
146
172
|
console.log(APP_PREFIX, '---------------');
|
|
147
|
-
}
|
|
173
|
+
}
|
|
148
174
|
};
|
|
149
175
|
|
|
150
176
|
const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
177
|
+
let s3BufferLocation;
|
|
151
178
|
|
|
152
179
|
const {
|
|
153
180
|
S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
|
|
@@ -158,6 +185,18 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
|
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 {
|
|
@@ -171,20 +210,20 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
|
171
210
|
ACL,
|
|
172
211
|
}
|
|
173
212
|
});
|
|
174
|
-
await out.done();
|
|
213
|
+
const result = await out.done();
|
|
175
214
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
});
|
|
215
|
+
s3BufferLocation = result?.Location;
|
|
216
|
+
debug('Uploaded location', s3BufferLocation);
|
|
217
|
+
|
|
218
|
+
return s3BufferLocation || console.log(
|
|
219
|
+
chalk.bold.red(`Problems getting the S3 artifact's link , contact the ${APP_PREFIX} team!`)
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
catch (e) {
|
|
223
|
+
console.log(e);
|
|
186
224
|
|
|
187
225
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
226
|
+
|
|
188
227
|
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
189
228
|
console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
|
|
190
229
|
} else {
|
|
@@ -217,6 +256,35 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
|
|
|
217
256
|
}
|
|
218
257
|
};
|
|
219
258
|
|
|
259
|
+
const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
|
|
260
|
+
const checkFile = async () => {
|
|
261
|
+
const fileStats = await stat(filePath);
|
|
262
|
+
if (fileStats.isFile()) {
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
throw new Error('File not found');
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
await promiseRetry(
|
|
271
|
+
{
|
|
272
|
+
retries: attempts,
|
|
273
|
+
minTimeout: intervalMs
|
|
274
|
+
},
|
|
275
|
+
checkFile
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return true;
|
|
279
|
+
} catch (err) {
|
|
280
|
+
console.error(
|
|
281
|
+
chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`)
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
220
288
|
module.exports = {
|
|
221
289
|
uploadFileByPath: memoize(uploadFileByPath),
|
|
222
290
|
uploadFileAsBuffer: memoize(uploadFileAsBuffer),
|
|
@@ -10,24 +10,42 @@ class JavaAdapter extends Adapter {
|
|
|
10
10
|
|
|
11
11
|
formatTest(t) {
|
|
12
12
|
const fileParts = t.suite_title.split('.')
|
|
13
|
-
const example = t.title.match(/\[(.*)\]/)?.[1];
|
|
14
|
-
if (example) t.example = { "#": example }
|
|
15
13
|
|
|
16
14
|
t.file = namespaceToFileName(t.suite_title);
|
|
17
15
|
t.title = t.title.split('(')[0];
|
|
16
|
+
|
|
17
|
+
// detect params
|
|
18
|
+
const paramMatches = t.title.match(/\[(.*?)\]/g);
|
|
19
|
+
|
|
20
|
+
if (paramMatches) {
|
|
21
|
+
const params = paramMatches.map((_match, index) => `param${index + 1}`);
|
|
22
|
+
if (params.length === 1) params[0] = 'param';
|
|
23
|
+
let paramIndex = 0;
|
|
24
|
+
|
|
25
|
+
t.title = t.title.replace(/: \[(.*?)\]/g, () => {
|
|
26
|
+
if (params.length < 2) return `\${param}`
|
|
27
|
+
const paramName = params[paramIndex] || `param${paramIndex + 1}`;
|
|
28
|
+
paramIndex++;
|
|
29
|
+
return `\${${paramName}}`;
|
|
30
|
+
});
|
|
31
|
+
const example = {};
|
|
32
|
+
paramMatches.forEach((match, index) => { example[params[index]] = match.replace(/[[\]]/g, '') });
|
|
33
|
+
t.example = example;
|
|
34
|
+
}
|
|
35
|
+
|
|
18
36
|
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
|
|
19
37
|
return t;
|
|
20
38
|
}
|
|
21
39
|
|
|
22
|
-
formatStack(t) {
|
|
23
|
-
|
|
40
|
+
// formatStack(t) {
|
|
41
|
+
// const stack = super.formatStack(t);
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
// const file = t.suite_title.split('.');
|
|
26
44
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
45
|
+
// const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
|
|
46
|
+
// const regexp = new RegExp(fileLine,"g")
|
|
47
|
+
// return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
|
|
48
|
+
// }
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
function namespaceToFileName(fileName) {
|
package/lib/pipe/csv.js
CHANGED
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const csvWriter = require('csv-writer');
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
const merge = require('lodash.merge');
|
|
7
|
-
const { isSameTest, getCurrentDateTime } = require('../
|
|
7
|
+
const { isSameTest, getCurrentDateTime } = require('../utils/utils');
|
|
8
8
|
const { CSV_HEADERS } = require('../constants');
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -40,6 +40,9 @@ class CsvPipe {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
44
|
+
async prepareRun() {}
|
|
45
|
+
|
|
43
46
|
async createRun() {
|
|
44
47
|
// empty
|
|
45
48
|
}
|
package/lib/pipe/github.js
CHANGED
|
@@ -5,7 +5,8 @@ const humanizeDuration = require('humanize-duration');
|
|
|
5
5
|
const merge = require('lodash.merge');
|
|
6
6
|
const { Octokit } = require('@octokit/rest');
|
|
7
7
|
const { APP_PREFIX } = require('../constants');
|
|
8
|
-
const { ansiRegExp, isSameTest } = require('../
|
|
8
|
+
const { ansiRegExp, isSameTest } = require('../utils/utils');
|
|
9
|
+
const { statusEmoji, fullName } = require('../utils/pipe_utils');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @typedef {import('../../types').Pipe} Pipe
|
|
@@ -37,6 +38,9 @@ class GitHubPipe {
|
|
|
37
38
|
debug('GitHub Pipe: Enabled');
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
42
|
+
async prepareRun() {}
|
|
43
|
+
|
|
40
44
|
async createRun() {}
|
|
41
45
|
|
|
42
46
|
addTest(test) {
|
|
@@ -182,21 +186,6 @@ class GitHubPipe {
|
|
|
182
186
|
}
|
|
183
187
|
}
|
|
184
188
|
|
|
185
|
-
function statusEmoji(status) {
|
|
186
|
-
if (status === 'passed') return '🟢';
|
|
187
|
-
if (status === 'failed') return '🔴';
|
|
188
|
-
if (status === 'skipped') return '🟡';
|
|
189
|
-
return '';
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function fullName(t) {
|
|
193
|
-
let line = '';
|
|
194
|
-
if (t.suite_title) line = `${t.suite_title}: `;
|
|
195
|
-
line += `**${t.title}**`;
|
|
196
|
-
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
197
|
-
return line;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
189
|
async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
|
|
201
190
|
if (process.env.GH_KEEP_OUTDATED_REPORTS) return;
|
|
202
191
|
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -5,7 +5,8 @@ const humanizeDuration = require('humanize-duration');
|
|
|
5
5
|
const merge = require('lodash.merge');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { APP_PREFIX } = require('../constants');
|
|
8
|
-
const { ansiRegExp, isSameTest } = require('../
|
|
8
|
+
const { ansiRegExp, isSameTest } = require('../utils/utils');
|
|
9
|
+
const { statusEmoji, fullName } = require('../utils/pipe_utils');
|
|
9
10
|
|
|
10
11
|
//! GITLAB_PAT environment variable is required for this functionality to work
|
|
11
12
|
//! and your pipeline trigger should be merge_request
|
|
@@ -46,6 +47,9 @@ class GitLabPipe {
|
|
|
46
47
|
debug('GitLab Pipe: Enabled');
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
51
|
+
async prepareRun() {}
|
|
52
|
+
|
|
49
53
|
async createRun() {}
|
|
50
54
|
|
|
51
55
|
addTest(test) {
|
|
@@ -179,21 +183,6 @@ class GitLabPipe {
|
|
|
179
183
|
updateRun() {}
|
|
180
184
|
}
|
|
181
185
|
|
|
182
|
-
function statusEmoji(status) {
|
|
183
|
-
if (status === 'passed') return '🟢';
|
|
184
|
-
if (status === 'failed') return '🔴';
|
|
185
|
-
if (status === 'skipped') return '🟡';
|
|
186
|
-
return '';
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function fullName(t) {
|
|
190
|
-
let line = '';
|
|
191
|
-
if (t.suite_title) line = `${t.suite_title}: `;
|
|
192
|
-
line += `**${t.title}**`;
|
|
193
|
-
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
194
|
-
return line;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
186
|
async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
|
|
198
187
|
if (process.env.GITLAB_KEEP_OUTDATED_REPORTS) return;
|
|
199
188
|
|