@testomatio/reporter 1.4.1-beta-1 → 1.4.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 +60 -57
- package/lib/adapter/codecept.js +86 -27
- package/lib/adapter/cucumber/current.js +17 -10
- package/lib/adapter/cucumber/legacy.js +5 -4
- package/lib/adapter/cucumber.js +2 -2
- package/lib/adapter/cypress-plugin/index.js +53 -26
- package/lib/adapter/jasmine.js +2 -2
- package/lib/adapter/jest.js +48 -10
- package/lib/adapter/mocha.js +90 -10
- package/lib/adapter/playwright.js +68 -18
- package/lib/adapter/webdriver.js +47 -2
- package/lib/bin/reportXml.js +21 -16
- package/lib/bin/startTest.js +12 -12
- package/lib/client.js +112 -52
- package/lib/config.js +34 -0
- package/lib/constants.js +28 -1
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +66 -53
- package/lib/junit-adapter/adapter.js +0 -2
- package/lib/junit-adapter/csharp.js +3 -4
- package/lib/junit-adapter/index.js +3 -3
- package/lib/junit-adapter/java.js +11 -11
- package/lib/junit-adapter/javascript.js +1 -2
- package/lib/junit-adapter/python.js +12 -14
- package/lib/junit-adapter/ruby.js +1 -2
- package/lib/pipe/csv.js +35 -32
- package/lib/pipe/github.js +4 -3
- package/lib/pipe/gitlab.js +17 -10
- package/lib/pipe/html.js +361 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +230 -51
- package/lib/reporter-functions.js +12 -9
- package/lib/reporter.js +8 -12
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/{storages/key-value-storage.js → services/key-values.js} +19 -19
- package/lib/{storages → services}/logger.js +58 -37
- package/lib/template/emptyData.svg +23 -0
- package/lib/template/testomatio.hbs +1421 -0
- package/lib/utils/pipe_utils.js +73 -79
- package/lib/utils/utils.js +53 -40
- package/lib/xmlReader.js +113 -105
- package/package.json +13 -7
- package/lib/storages/artifact-storage.js +0 -70
- package/lib/storages/data-storage.js +0 -307
package/lib/fileUploader.js
CHANGED
|
@@ -11,7 +11,6 @@ const readFile = util.promisify(fs.readFile);
|
|
|
11
11
|
const stat = util.promisify(fs.stat);
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
const { randomUUID } = require('crypto');
|
|
14
|
-
const memoize = require('lodash.memoize');
|
|
15
14
|
|
|
16
15
|
const { APP_PREFIX } = require('./constants');
|
|
17
16
|
|
|
@@ -21,6 +20,7 @@ const keys = [
|
|
|
21
20
|
'S3_BUCKET',
|
|
22
21
|
'S3_ACCESS_KEY_ID',
|
|
23
22
|
'S3_SECRET_ACCESS_KEY',
|
|
23
|
+
'S3_SESSION_TOKEN',
|
|
24
24
|
'TESTOMATIO_DISABLE_ARTIFACTS',
|
|
25
25
|
'TESTOMATIO_PRIVATE_ARTIFACTS',
|
|
26
26
|
'S3_FORCE_PATH_STYLE',
|
|
@@ -43,8 +43,12 @@ function getConfig() {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function getMaskedConfig() {
|
|
46
|
-
return Object.fromEntries(
|
|
47
|
-
.map(([key, 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
|
+
);
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
let isEnabled;
|
|
@@ -71,7 +75,8 @@ const _getFileExtBase64 = str => {
|
|
|
71
75
|
};
|
|
72
76
|
|
|
73
77
|
const _getS3Config = () => {
|
|
74
|
-
const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
|
|
78
|
+
const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
|
|
79
|
+
getConfig();
|
|
75
80
|
|
|
76
81
|
const cfg = {
|
|
77
82
|
region: S3_REGION,
|
|
@@ -79,35 +84,35 @@ const _getS3Config = () => {
|
|
|
79
84
|
accessKeyId: S3_ACCESS_KEY_ID,
|
|
80
85
|
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
81
86
|
s3ForcePathStyle: S3_FORCE_PATH_STYLE,
|
|
82
|
-
}
|
|
87
|
+
},
|
|
83
88
|
};
|
|
84
89
|
|
|
90
|
+
if (S3_SESSION_TOKEN) {
|
|
91
|
+
cfg.credentials.sessionToken = S3_SESSION_TOKEN;
|
|
92
|
+
}
|
|
93
|
+
|
|
85
94
|
if (S3_ENDPOINT) {
|
|
86
95
|
cfg.endpoint = S3_ENDPOINT;
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
return cfg;
|
|
90
|
-
}
|
|
99
|
+
};
|
|
91
100
|
|
|
92
101
|
const uploadUsingS3 = async (filePath, runId) => {
|
|
93
102
|
let ContentType;
|
|
94
103
|
let Key;
|
|
95
|
-
let s3FileLocation;
|
|
96
104
|
|
|
97
105
|
if (typeof filePath === 'object') {
|
|
98
|
-
ContentType = filePath
|
|
99
|
-
filePath = filePath
|
|
100
|
-
Key = filePath
|
|
106
|
+
ContentType = filePath?.type;
|
|
107
|
+
filePath = filePath?.path;
|
|
108
|
+
Key = filePath?.name;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
const {
|
|
104
|
-
TESTOMATIO_PRIVATE_ARTIFACTS,
|
|
105
|
-
S3_BUCKET
|
|
106
|
-
} = getConfig();
|
|
111
|
+
const { TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
|
|
107
112
|
|
|
108
113
|
try {
|
|
109
114
|
debug('S3 config', getMaskedConfig());
|
|
110
|
-
debug('
|
|
115
|
+
debug('Started upload', filePath, 'to ', S3_BUCKET);
|
|
111
116
|
|
|
112
117
|
// Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
|
|
113
118
|
const isFileExist = await checkFileExists(filePath, 20, 500);
|
|
@@ -117,7 +122,7 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
117
122
|
return;
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
debug('
|
|
125
|
+
debug('File: ', filePath, ' exists');
|
|
121
126
|
|
|
122
127
|
const fileData = await readFile(filePath);
|
|
123
128
|
|
|
@@ -128,7 +133,9 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
128
133
|
if (!S3_BUCKET || !fileData) {
|
|
129
134
|
console.log(
|
|
130
135
|
APP_PREFIX,
|
|
131
|
-
chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`),
|
|
136
|
+
chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`),
|
|
137
|
+
getMaskedConfig(),
|
|
138
|
+
);
|
|
132
139
|
return;
|
|
133
140
|
}
|
|
134
141
|
|
|
@@ -144,20 +151,16 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
144
151
|
|
|
145
152
|
const out = new Upload({
|
|
146
153
|
client: s3,
|
|
147
|
-
params
|
|
154
|
+
params,
|
|
148
155
|
});
|
|
149
156
|
|
|
150
|
-
const
|
|
157
|
+
const link = await getS3LocationLink(out);
|
|
151
158
|
|
|
152
|
-
|
|
153
|
-
debug('Uploaded location', s3FileLocation);
|
|
159
|
+
debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
|
|
154
160
|
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
160
|
-
console.log(e);
|
|
161
|
+
return link;
|
|
162
|
+
} catch (e) {
|
|
163
|
+
debug('S3 file uploading error: ', e);
|
|
161
164
|
|
|
162
165
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
163
166
|
|
|
@@ -174,11 +177,8 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
174
177
|
};
|
|
175
178
|
|
|
176
179
|
const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const {
|
|
180
|
-
S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
|
|
181
|
-
} = getConfig();
|
|
180
|
+
const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } =
|
|
181
|
+
getConfig();
|
|
182
182
|
|
|
183
183
|
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
184
184
|
|
|
@@ -208,19 +208,12 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
|
208
208
|
Key,
|
|
209
209
|
Body: buffer,
|
|
210
210
|
ACL,
|
|
211
|
-
}
|
|
211
|
+
},
|
|
212
212
|
});
|
|
213
|
-
const result = await out.done();
|
|
214
|
-
|
|
215
|
-
s3BufferLocation = result?.Location;
|
|
216
|
-
debug('Uploaded location', s3BufferLocation);
|
|
217
213
|
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
catch (e) {
|
|
223
|
-
console.log(e);
|
|
214
|
+
return await getS3LocationLink(out);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
debug('S3 buffer uploading error: ', e);
|
|
224
217
|
|
|
225
218
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
226
219
|
|
|
@@ -242,7 +235,9 @@ const uploadFileByPath = async (filePath, runId) => {
|
|
|
242
235
|
return uploadUsingS3(filePath, runId);
|
|
243
236
|
}
|
|
244
237
|
} catch (e) {
|
|
245
|
-
|
|
238
|
+
debug(e);
|
|
239
|
+
|
|
240
|
+
console.error(chalk.red('Error occurred while uploading artifacts! '), e);
|
|
246
241
|
}
|
|
247
242
|
};
|
|
248
243
|
|
|
@@ -252,7 +247,9 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
|
|
|
252
247
|
return uploadUsingS3AsBuffer(buffer, fileName, runId);
|
|
253
248
|
}
|
|
254
249
|
} catch (e) {
|
|
255
|
-
|
|
250
|
+
debug(e);
|
|
251
|
+
|
|
252
|
+
console.error(chalk.red('Error occurred while uploading artifacts! '), e);
|
|
256
253
|
}
|
|
257
254
|
};
|
|
258
255
|
|
|
@@ -268,26 +265,42 @@ const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
|
|
|
268
265
|
|
|
269
266
|
try {
|
|
270
267
|
await promiseRetry(
|
|
271
|
-
{
|
|
268
|
+
{
|
|
272
269
|
retries: attempts,
|
|
273
|
-
minTimeout: intervalMs
|
|
270
|
+
minTimeout: intervalMs,
|
|
274
271
|
},
|
|
275
|
-
checkFile
|
|
272
|
+
checkFile,
|
|
276
273
|
);
|
|
277
274
|
|
|
278
275
|
return true;
|
|
279
276
|
} catch (err) {
|
|
280
|
-
console.error(
|
|
281
|
-
chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`)
|
|
282
|
-
);
|
|
277
|
+
console.error(chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`));
|
|
283
278
|
|
|
284
279
|
return false;
|
|
285
280
|
}
|
|
286
281
|
};
|
|
287
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
|
+
|
|
288
301
|
module.exports = {
|
|
289
|
-
uploadFileByPath
|
|
290
|
-
uploadFileAsBuffer
|
|
302
|
+
uploadFileByPath,
|
|
303
|
+
uploadFileAsBuffer,
|
|
291
304
|
isArtifactsEnabled,
|
|
292
305
|
resetConfig,
|
|
293
306
|
};
|
|
@@ -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,14 +2,13 @@ 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('.')
|
|
11
|
+
const fileParts = t.suite_title.split('.');
|
|
13
12
|
|
|
14
13
|
t.file = namespaceToFileName(t.suite_title);
|
|
15
14
|
t.title = t.title.split('(')[0];
|
|
@@ -21,19 +20,21 @@ class JavaAdapter extends Adapter {
|
|
|
21
20
|
const params = paramMatches.map((_match, index) => `param${index + 1}`);
|
|
22
21
|
if (params.length === 1) params[0] = 'param';
|
|
23
22
|
let paramIndex = 0;
|
|
24
|
-
|
|
23
|
+
|
|
25
24
|
t.title = t.title.replace(/: \[(.*?)\]/g, () => {
|
|
26
|
-
if (params.length < 2) return `\${param}
|
|
25
|
+
if (params.length < 2) return `\${param}`;
|
|
27
26
|
const paramName = params[paramIndex] || `param${paramIndex + 1}`;
|
|
28
27
|
paramIndex++;
|
|
29
28
|
return `\${${paramName}}`;
|
|
30
29
|
});
|
|
31
30
|
const example = {};
|
|
32
|
-
paramMatches.forEach((match, index) => {
|
|
31
|
+
paramMatches.forEach((match, index) => {
|
|
32
|
+
example[params[index]] = match.replace(/[[\]]/g, '');
|
|
33
|
+
});
|
|
33
34
|
t.example = example;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
|
|
37
|
+
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ');
|
|
37
38
|
return t;
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -49,10 +50,9 @@ class JavaAdapter extends Adapter {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
function namespaceToFileName(fileName) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const fileParts = fileName.split('.');
|
|
54
|
+
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
55
|
+
return `${fileParts.join(path.sep)}.java`;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
module.exports = JavaAdapter;
|
|
@@ -3,12 +3,11 @@ const path = require('path');
|
|
|
3
3
|
const Adapter = require('./adapter');
|
|
4
4
|
|
|
5
5
|
class JavaScriptAdapter extends Adapter {
|
|
6
|
-
|
|
7
6
|
formatStack(t) {
|
|
8
7
|
let stack = super.formatStack(t);
|
|
9
8
|
|
|
10
9
|
try {
|
|
11
|
-
const error = new Error(stack.split('\n')[0])
|
|
10
|
+
const error = new Error(stack.split('\n')[0]);
|
|
12
11
|
error.stack = stack;
|
|
13
12
|
const record = createCallsiteRecord({
|
|
14
13
|
forError: error,
|
|
@@ -3,7 +3,6 @@ const fs = require('fs');
|
|
|
3
3
|
const Adapter = require('./adapter');
|
|
4
4
|
|
|
5
5
|
class PythonAdapter extends Adapter {
|
|
6
|
-
|
|
7
6
|
getFilePath(t) {
|
|
8
7
|
let fileName = namespaceToFileName(t.suite_title, { checkFile: true });
|
|
9
8
|
if (!fileName) fileName = namespaceToFileName(t.suite_title, { checkFile: false });
|
|
@@ -11,34 +10,33 @@ class PythonAdapter extends Adapter {
|
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
formatTest(t) {
|
|
14
|
-
const fileParts = t.suite_title.split('.')
|
|
13
|
+
const fileParts = t.suite_title.split('.');
|
|
15
14
|
const example = t.title.match(/\[(.*)\]/)?.[1];
|
|
16
|
-
if (example) t.example = {
|
|
15
|
+
if (example) t.example = { '#': example };
|
|
17
16
|
|
|
18
17
|
t.file = namespaceToFileName(t.suite_title);
|
|
19
18
|
t.title = t.title.split('[')[0];
|
|
20
|
-
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
|
|
19
|
+
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ');
|
|
21
20
|
return t;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
formatMessage(t) {
|
|
25
24
|
return t.message.split(' ')[0];
|
|
26
25
|
}
|
|
27
|
-
|
|
28
26
|
}
|
|
29
27
|
|
|
30
28
|
function namespaceToFileName(fileName, opts = {}) {
|
|
31
|
-
|
|
29
|
+
const fileParts = fileName.split('.');
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
fileParts.pop();
|
|
31
|
+
while (fileParts.length > 0) {
|
|
32
|
+
const file = `${fileParts.join(path.sep)}.py`;
|
|
33
|
+
if (!opts.checkFile) return file;
|
|
34
|
+
if (fs.existsSync(`${fileParts.join(path.sep)}.py`)) {
|
|
35
|
+
return file;
|
|
40
36
|
}
|
|
41
|
-
|
|
37
|
+
fileParts.pop();
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
module.exports = PythonAdapter;
|
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('../utils/utils');
|
|
7
|
+
const { isSameTest, getCurrentDateTime, ansiRegExp } = require('../utils/utils');
|
|
8
8
|
const { CSV_HEADERS } = require('../constants');
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -14,35 +14,37 @@ const { CSV_HEADERS } = require('../constants');
|
|
|
14
14
|
* @implements {Pipe}
|
|
15
15
|
*/
|
|
16
16
|
class CsvPipe {
|
|
17
|
-
|
|
18
17
|
constructor(params, store) {
|
|
19
18
|
this.store = store || {};
|
|
20
19
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
21
20
|
this.results = [];
|
|
22
21
|
|
|
23
22
|
this.outputDir = 'export';
|
|
23
|
+
this.defaultReportName = 'report.csv';
|
|
24
24
|
this.csvFilename = process.env.TESTOMATIO_CSV_FILENAME;
|
|
25
|
-
this.isEnabled =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
this.isEnabled = false;
|
|
26
|
+
|
|
27
|
+
if (this.csvFilename) {
|
|
28
|
+
const filenameParts = this.csvFilename.split('.');
|
|
29
|
+
|
|
30
|
+
if (filenameParts.length > 0) {
|
|
31
|
+
this.isEnabled = true;
|
|
32
|
+
const baseFilename = filenameParts[0];
|
|
33
|
+
const defaultOutputFile = path.resolve(process.cwd(), this.outputDir, this.defaultReportName);
|
|
34
|
+
|
|
35
|
+
const outputFile =
|
|
36
|
+
baseFilename === this.defaultReportName.split('.')[0] // = 'report'
|
|
37
|
+
? defaultOutputFile
|
|
38
|
+
: path.resolve(process.cwd(), this.outputDir, `${getCurrentDateTime()}_${baseFilename}.csv`);
|
|
39
|
+
|
|
40
|
+
this.outputFile = outputFile;
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
44
46
|
async prepareRun() {}
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
async createRun() {
|
|
47
49
|
// empty
|
|
48
50
|
}
|
|
@@ -70,22 +72,22 @@ class CsvPipe {
|
|
|
70
72
|
this.checkExportDir();
|
|
71
73
|
|
|
72
74
|
if (!this.outputFile) {
|
|
73
|
-
console.log(
|
|
75
|
+
console.log(chalk.yellow(`⚠️ CSV file is not set, ignoring`));
|
|
74
76
|
return;
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
console.log(
|
|
78
|
-
|
|
79
|
-
const writer = csvWriter.createObjectCsvWriter({
|
|
80
|
-
path: this.outputFile,
|
|
81
|
-
header: headers,
|
|
82
|
-
});
|
|
83
|
-
// Save csv file based on the current data
|
|
79
|
+
console.log(chalk.yellow(`⏳ The test results will be added to the csv. It will take some time...`));
|
|
80
|
+
|
|
84
81
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
// Create csv writer object
|
|
83
|
+
const writer = csvWriter.createObjectCsvWriter({
|
|
84
|
+
path: this.outputFile,
|
|
85
|
+
header: headers,
|
|
86
|
+
});
|
|
87
|
+
// Save csv file based on the current data
|
|
88
|
+
return await writer.writeRecords(data);
|
|
87
89
|
} catch (e) {
|
|
88
|
-
console.log('Unknown error: ', e);
|
|
90
|
+
console.log('Unknown csv error: ', e);
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
|
|
@@ -109,8 +111,8 @@ class CsvPipe {
|
|
|
109
111
|
suite_title,
|
|
110
112
|
title,
|
|
111
113
|
status,
|
|
112
|
-
message,
|
|
113
|
-
stack,
|
|
114
|
+
message: message.replace(ansiRegExp(), ''),
|
|
115
|
+
stack: stack.replace(ansiRegExp(), ''),
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
|
|
@@ -123,8 +125,9 @@ class CsvPipe {
|
|
|
123
125
|
|
|
124
126
|
if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
|
|
125
127
|
// Save results based on the default headers
|
|
126
|
-
if (this.
|
|
127
|
-
this.saveToCsv(this.results, CSV_HEADERS);
|
|
128
|
+
if (this.isEnabled) {
|
|
129
|
+
await this.saveToCsv(this.results, CSV_HEADERS);
|
|
130
|
+
console.log(chalk.green(`🗃️ Recording completed! You can check the result in file = ${this.outputFile}`));
|
|
128
131
|
}
|
|
129
132
|
}
|
|
130
133
|
|
package/lib/pipe/github.js
CHANGED
|
@@ -4,7 +4,7 @@ const chalk = require('chalk');
|
|
|
4
4
|
const humanizeDuration = require('humanize-duration');
|
|
5
5
|
const merge = require('lodash.merge');
|
|
6
6
|
const { Octokit } = require('@octokit/rest');
|
|
7
|
-
const { APP_PREFIX } = require('../constants');
|
|
7
|
+
const { APP_PREFIX, testomatLogoURL } = require('../constants');
|
|
8
8
|
const { ansiRegExp, isSameTest } = require('../utils/utils');
|
|
9
9
|
const { statusEmoji, fullName } = require('../utils/pipe_utils');
|
|
10
10
|
|
|
@@ -77,8 +77,9 @@ class GitHubPipe {
|
|
|
77
77
|
|
|
78
78
|
let summary = `${this.hiddenCommentData}
|
|
79
79
|
|
|
80
|
-
| [](https://testomat.io) | ${statusEmoji(
|
|
81
|
+
runParams.status,
|
|
82
|
+
)} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()} |
|
|
82
83
|
| --- | --- |
|
|
83
84
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
84
85
|
| Summary | ${failedCount ? `${statusEmoji('failed')} **${failedCount}** failed; ` : ''} ${statusEmoji(
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -4,7 +4,7 @@ const chalk = require('chalk');
|
|
|
4
4
|
const humanizeDuration = require('humanize-duration');
|
|
5
5
|
const merge = require('lodash.merge');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const { APP_PREFIX } = require('../constants');
|
|
7
|
+
const { APP_PREFIX, testomatLogoURL } = require('../constants');
|
|
8
8
|
const { ansiRegExp, isSameTest } = require('../utils/utils');
|
|
9
9
|
const { statusEmoji, fullName } = require('../utils/pipe_utils');
|
|
10
10
|
|
|
@@ -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(
|
|
@@ -77,17 +77,24 @@ class GitLabPipe {
|
|
|
77
77
|
|
|
78
78
|
// constructing the table
|
|
79
79
|
let summary = `${this.hiddenCommentData}
|
|
80
|
-
|
|
81
|
-
| [](https://testomat.io) | ${statusEmoji(
|
|
82
|
+
runParams.status,
|
|
83
|
+
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
83
84
|
| --- | --- |
|
|
84
85
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
85
86
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
| Duration | 🕐 **${humanizeDuration(
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
'passed',
|
|
88
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
89
|
+
| Duration | 🕐 **${humanizeDuration(
|
|
90
|
+
parseInt(
|
|
91
|
+
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
|
92
|
+
10,
|
|
93
|
+
),
|
|
94
|
+
{
|
|
95
|
+
maxDecimalPoints: 0,
|
|
96
|
+
},
|
|
97
|
+
)}** |
|
|
91
98
|
`;
|
|
92
99
|
|
|
93
100
|
if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {
|