@testomatio/reporter 1.2.0-beta-3 → 1.2.0
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 +27 -18
- package/lib/adapter/codecept.js +83 -11
- package/lib/adapter/cucumber/current.js +17 -10
- package/lib/adapter/cucumber/legacy.js +4 -3
- package/lib/adapter/cypress-plugin/index.js +51 -24
- package/lib/adapter/jest.js +47 -23
- package/lib/adapter/mocha.js +99 -45
- package/lib/adapter/playwright.js +94 -32
- package/lib/bin/startTest.js +8 -7
- package/lib/client.js +119 -62
- package/lib/config.js +34 -0
- package/lib/constants.js +9 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +125 -46
- package/lib/junit-adapter/index.js +3 -2
- package/lib/pipe/gitlab.js +1 -1
- package/lib/pipe/html.js +269 -232
- package/lib/pipe/testomatio.js +73 -50
- package/lib/reporter-functions.js +46 -0
- package/lib/reporter.js +11 -10
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/services/key-values.js +58 -0
- package/lib/{logger.js → services/logger.js} +58 -66
- package/lib/template/testomatio.hbs +94 -62
- package/lib/utils/utils.js +28 -2
- package/lib/xmlReader.js +106 -105
- package/package.json +6 -3
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -232
- package/lib/template/real-data-example.js +0 -47
- /package/lib/template/{template.hbs → template-draft.hbs} +0 -0
package/lib/fileUploader.js
CHANGED
|
@@ -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
|
-
|
|
14
|
+
|
|
10
15
|
const { APP_PREFIX } = require('./constants');
|
|
11
16
|
|
|
12
17
|
const keys = [
|
|
@@ -93,51 +98,69 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
93
98
|
Key = filePath.name;
|
|
94
99
|
}
|
|
95
100
|
|
|
96
|
-
if (!fs.existsSync(filePath)) {
|
|
97
|
-
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
101
|
const {
|
|
102
|
-
TESTOMATIO_PRIVATE_ARTIFACTS,
|
|
102
|
+
TESTOMATIO_PRIVATE_ARTIFACTS,
|
|
103
|
+
S3_BUCKET
|
|
103
104
|
} = getConfig();
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const fileData = fs.readFileSync(filePath);
|
|
106
|
+
try {
|
|
107
|
+
debug('S3 config', getMaskedConfig());
|
|
108
|
+
debug('Started upload', filePath, 'to ', S3_BUCKET);
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
// Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
|
|
111
|
+
const isFileExist = await checkFileExists(filePath, 20, 500);
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
if (!isFileExist) {
|
|
114
|
+
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
114
117
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Body: fileData,
|
|
119
|
-
ContentType,
|
|
120
|
-
ACL,
|
|
121
|
-
};
|
|
118
|
+
debug('File: ', filePath, ' exists');
|
|
119
|
+
|
|
120
|
+
const fileData = await readFile(filePath);
|
|
122
121
|
|
|
123
|
-
|
|
122
|
+
Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
|
|
123
|
+
|
|
124
|
+
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
125
|
+
|
|
126
|
+
if (!S3_BUCKET || !fileData) {
|
|
127
|
+
console.log(
|
|
128
|
+
APP_PREFIX,
|
|
129
|
+
chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const s3 = new S3(_getS3Config());
|
|
134
|
+
|
|
135
|
+
const params = {
|
|
136
|
+
Bucket: S3_BUCKET,
|
|
137
|
+
Key,
|
|
138
|
+
Body: fileData,
|
|
139
|
+
ContentType,
|
|
140
|
+
ACL,
|
|
141
|
+
};
|
|
142
|
+
|
|
124
143
|
const out = new Upload({
|
|
125
144
|
client: s3,
|
|
126
145
|
params
|
|
127
146
|
});
|
|
128
147
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
} catch (e) {
|
|
134
|
-
console.log(e);
|
|
135
|
-
console.log(APP_PREFIX, chalk.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
|
|
148
|
+
|
|
149
|
+
const link = await getS3LocationLink(out);
|
|
150
|
+
|
|
151
|
+
debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
|
|
136
152
|
|
|
153
|
+
return link;
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
debug('S3 file uploading error: ', e);
|
|
157
|
+
|
|
137
158
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
159
|
+
|
|
138
160
|
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
139
161
|
console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
|
|
140
|
-
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
141
164
|
console.log(
|
|
142
165
|
APP_PREFIX,
|
|
143
166
|
`To enable ${chalk.bold('PUBLIC')} uploads remove TESTOMATIO_PRIVATE_ARTIFACTS env variable`,
|
|
@@ -148,7 +171,6 @@ const fileData = fs.readFileSync(filePath);
|
|
|
148
171
|
};
|
|
149
172
|
|
|
150
173
|
const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
151
|
-
|
|
152
174
|
const {
|
|
153
175
|
S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
|
|
154
176
|
} = getConfig();
|
|
@@ -158,6 +180,18 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
|
158
180
|
const fileExtension = _getFileExtBase64(buffer.toString('base64'));
|
|
159
181
|
const Key = `${runId}/${fileName}${fileExtension}`;
|
|
160
182
|
|
|
183
|
+
if (!S3_BUCKET || !buffer) {
|
|
184
|
+
console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
|
|
185
|
+
accessKeyId: S3_ACCESS_KEY_ID,
|
|
186
|
+
secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
|
|
187
|
+
region: S3_REGION,
|
|
188
|
+
bucket: S3_BUCKET,
|
|
189
|
+
acl: ACL,
|
|
190
|
+
endpoint: S3_ENDPOINT,
|
|
191
|
+
});
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
161
195
|
const s3 = new S3(_getS3Config());
|
|
162
196
|
|
|
163
197
|
try {
|
|
@@ -171,20 +205,14 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
|
171
205
|
ACL,
|
|
172
206
|
}
|
|
173
207
|
});
|
|
174
|
-
await out.done();
|
|
175
208
|
|
|
176
|
-
return out
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
|
|
181
|
-
region: S3_REGION,
|
|
182
|
-
bucket: S3_BUCKET,
|
|
183
|
-
acl: ACL,
|
|
184
|
-
endpoint: S3_ENDPOINT,
|
|
185
|
-
});
|
|
209
|
+
return await getS3LocationLink(out);
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
debug('S3 buffer uploading error: ', e);
|
|
186
213
|
|
|
187
214
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
215
|
+
|
|
188
216
|
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
189
217
|
console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
|
|
190
218
|
} else {
|
|
@@ -203,7 +231,9 @@ const uploadFileByPath = async (filePath, runId) => {
|
|
|
203
231
|
return uploadUsingS3(filePath, runId);
|
|
204
232
|
}
|
|
205
233
|
} catch (e) {
|
|
206
|
-
|
|
234
|
+
debug(e);
|
|
235
|
+
|
|
236
|
+
console.error(chalk.red('Error occurred while uploading artifacts! '), e);
|
|
207
237
|
}
|
|
208
238
|
};
|
|
209
239
|
|
|
@@ -213,13 +243,62 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
|
|
|
213
243
|
return uploadUsingS3AsBuffer(buffer, fileName, runId);
|
|
214
244
|
}
|
|
215
245
|
} catch (e) {
|
|
216
|
-
|
|
246
|
+
debug(e);
|
|
247
|
+
|
|
248
|
+
console.error(chalk.red('Error occurred while uploading artifacts! '), e);
|
|
217
249
|
}
|
|
218
250
|
};
|
|
219
251
|
|
|
252
|
+
const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
|
|
253
|
+
const checkFile = async () => {
|
|
254
|
+
const fileStats = await stat(filePath);
|
|
255
|
+
if (fileStats.isFile()) {
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
throw new Error('File not found');
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
await promiseRetry(
|
|
264
|
+
{
|
|
265
|
+
retries: attempts,
|
|
266
|
+
minTimeout: intervalMs
|
|
267
|
+
},
|
|
268
|
+
checkFile
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
return true;
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error(
|
|
274
|
+
chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`)
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const getS3LocationLink = async (out) => {
|
|
282
|
+
const response = await out.done();
|
|
283
|
+
|
|
284
|
+
let s3Location = response?.Location;
|
|
285
|
+
|
|
286
|
+
if (!s3Location) {
|
|
287
|
+
// TODO: out: a fallback case - remove after deeper testing
|
|
288
|
+
s3Location = out?.singleUploadResult?.Location;
|
|
289
|
+
debug('Uploaded singleUploadResult.Location', s3Location);
|
|
290
|
+
|
|
291
|
+
if (!s3Location) {
|
|
292
|
+
throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return s3Location;
|
|
297
|
+
};
|
|
298
|
+
|
|
220
299
|
module.exports = {
|
|
221
|
-
uploadFileByPath
|
|
222
|
-
uploadFileAsBuffer
|
|
300
|
+
uploadFileByPath,
|
|
301
|
+
uploadFileAsBuffer,
|
|
223
302
|
isArtifactsEnabled,
|
|
224
303
|
resetConfig,
|
|
225
304
|
};
|
|
@@ -6,8 +6,7 @@ const RubyAdapter = require('./ruby');
|
|
|
6
6
|
const CSharpAdapter = require('./csharp');
|
|
7
7
|
|
|
8
8
|
function AdapterFactory(lang, opts) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
if (lang === 'java') {
|
|
12
11
|
return new JavaAdapter(opts);
|
|
13
12
|
}
|
|
@@ -23,6 +22,8 @@ function AdapterFactory(lang, opts) {
|
|
|
23
22
|
if (lang === 'c#' || lang === 'csharp') {
|
|
24
23
|
return new CSharpAdapter(opts);
|
|
25
24
|
}
|
|
25
|
+
|
|
26
|
+
return new Adapter(opts);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
module.exports = AdapterFactory;
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -23,7 +23,7 @@ class GitLabPipe {
|
|
|
23
23
|
this.store = store;
|
|
24
24
|
this.tests = [];
|
|
25
25
|
// GitLab PAT looks like glpat-nKGdja3jsG4850sGksh7
|
|
26
|
-
this.token = params.GITLAB_PAT || this.ENV.GITLAB_PAT;
|
|
26
|
+
this.token = params.GITLAB_PAT || process.env.GITLAB_PAT || this.ENV.GITLAB_PAT;
|
|
27
27
|
this.hiddenCommentData = `<!--- testomat.io report ${process.env.CI_JOB_NAME || ''} -->`;
|
|
28
28
|
|
|
29
29
|
debug(
|