@testomatio/reporter 1.2.2-beta → 1.2.2-beta-html-pagination-feature-v2
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 +61 -54
- package/lib/adapter/codecept.js +140 -61
- package/lib/adapter/cucumber/current.js +103 -60
- package/lib/adapter/cucumber/legacy.js +27 -12
- package/lib/adapter/cucumber.js +2 -2
- package/lib/adapter/cypress-plugin/index.js +52 -25
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +49 -11
- package/lib/adapter/mocha.js +103 -51
- package/lib/adapter/playwright.js +100 -31
- package/lib/adapter/webdriver.js +1 -1
- package/lib/bin/reportXml.js +14 -13
- package/lib/bin/startTest.js +27 -6
- package/lib/client.js +193 -69
- package/lib/config.js +34 -0
- package/lib/constants.js +19 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +128 -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 +35 -17
- 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 +5 -3
- package/lib/pipe/github.js +27 -39
- package/lib/pipe/gitlab.js +20 -24
- package/lib/pipe/html.js +363 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +183 -56
- package/lib/reporter-functions.js +46 -0
- package/lib/reporter.js +11 -9
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/services/key-values.js +58 -0
- package/lib/services/logger.js +311 -0
- package/lib/template/testomatio.hbs +1233 -0
- package/lib/utils/pipe_utils.js +128 -0
- package/lib/{util.js → utils/utils.js} +144 -12
- package/lib/xmlReader.js +211 -122
- package/package.json +18 -8
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -180
- package/lib/logger.js +0 -278
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 = [
|
|
@@ -37,8 +42,12 @@ function getConfig() {
|
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
function getMaskedConfig() {
|
|
40
|
-
return Object.fromEntries(
|
|
41
|
-
.map(([key, value]) => [
|
|
45
|
+
return Object.fromEntries(
|
|
46
|
+
Object.entries(getConfig()).map(([key, value]) => [
|
|
47
|
+
key,
|
|
48
|
+
key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
|
|
49
|
+
]),
|
|
50
|
+
);
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
let isEnabled;
|
|
@@ -73,7 +82,7 @@ const _getS3Config = () => {
|
|
|
73
82
|
accessKeyId: S3_ACCESS_KEY_ID,
|
|
74
83
|
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
75
84
|
s3ForcePathStyle: S3_FORCE_PATH_STYLE,
|
|
76
|
-
}
|
|
85
|
+
},
|
|
77
86
|
};
|
|
78
87
|
|
|
79
88
|
if (S3_ENDPOINT) {
|
|
@@ -81,7 +90,7 @@ const _getS3Config = () => {
|
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
return cfg;
|
|
84
|
-
}
|
|
93
|
+
};
|
|
85
94
|
|
|
86
95
|
const uploadUsingS3 = async (filePath, runId) => {
|
|
87
96
|
let ContentType;
|
|
@@ -93,48 +102,62 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
93
102
|
Key = filePath.name;
|
|
94
103
|
}
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
105
|
+
const { TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
|
|
100
106
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
try {
|
|
108
|
+
debug('S3 config', getMaskedConfig());
|
|
109
|
+
debug('Started upload', filePath, 'to ', S3_BUCKET);
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const fileData = fs.readFileSync(filePath);
|
|
111
|
+
// Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
|
|
112
|
+
const isFileExist = await checkFileExists(filePath, 20, 500);
|
|
109
113
|
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
if (!isFileExist) {
|
|
115
|
+
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
112
118
|
|
|
113
|
-
|
|
119
|
+
debug('File: ', filePath, ' exists');
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Key
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
const fileData = await readFile(filePath);
|
|
122
|
+
|
|
123
|
+
Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
|
|
124
|
+
|
|
125
|
+
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
126
|
+
|
|
127
|
+
if (!S3_BUCKET || !fileData) {
|
|
128
|
+
console.log(
|
|
129
|
+
APP_PREFIX,
|
|
130
|
+
chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`),
|
|
131
|
+
getMaskedConfig(),
|
|
132
|
+
);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const s3 = new S3(_getS3Config());
|
|
137
|
+
|
|
138
|
+
const params = {
|
|
139
|
+
Bucket: S3_BUCKET,
|
|
140
|
+
Key,
|
|
141
|
+
Body: fileData,
|
|
142
|
+
ContentType,
|
|
143
|
+
ACL,
|
|
144
|
+
};
|
|
122
145
|
|
|
123
|
-
try {
|
|
124
146
|
const out = new Upload({
|
|
125
147
|
client: s3,
|
|
126
|
-
params
|
|
148
|
+
params,
|
|
127
149
|
});
|
|
128
150
|
|
|
129
|
-
await out
|
|
130
|
-
debug('Uploaded', out.singleUploadResult.Location)
|
|
151
|
+
const link = await getS3LocationLink(out);
|
|
131
152
|
|
|
132
|
-
|
|
153
|
+
debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
|
|
154
|
+
|
|
155
|
+
return link;
|
|
133
156
|
} catch (e) {
|
|
134
|
-
|
|
135
|
-
console.log(APP_PREFIX, chalk.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
|
|
157
|
+
debug('S3 file uploading error: ', e);
|
|
136
158
|
|
|
137
159
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
160
|
+
|
|
138
161
|
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
139
162
|
console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
|
|
140
163
|
} else {
|
|
@@ -144,20 +167,30 @@ const fileData = fs.readFileSync(filePath);
|
|
|
144
167
|
);
|
|
145
168
|
}
|
|
146
169
|
console.log(APP_PREFIX, '---------------');
|
|
147
|
-
}
|
|
170
|
+
}
|
|
148
171
|
};
|
|
149
172
|
|
|
150
173
|
const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
|
|
154
|
-
} = getConfig();
|
|
174
|
+
const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } =
|
|
175
|
+
getConfig();
|
|
155
176
|
|
|
156
177
|
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
157
178
|
|
|
158
179
|
const fileExtension = _getFileExtBase64(buffer.toString('base64'));
|
|
159
180
|
const Key = `${runId}/${fileName}${fileExtension}`;
|
|
160
181
|
|
|
182
|
+
if (!S3_BUCKET || !buffer) {
|
|
183
|
+
console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
|
|
184
|
+
accessKeyId: S3_ACCESS_KEY_ID,
|
|
185
|
+
secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
|
|
186
|
+
region: S3_REGION,
|
|
187
|
+
bucket: S3_BUCKET,
|
|
188
|
+
acl: ACL,
|
|
189
|
+
endpoint: S3_ENDPOINT,
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
161
194
|
const s3 = new S3(_getS3Config());
|
|
162
195
|
|
|
163
196
|
try {
|
|
@@ -169,22 +202,15 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
|
169
202
|
Key,
|
|
170
203
|
Body: buffer,
|
|
171
204
|
ACL,
|
|
172
|
-
}
|
|
205
|
+
},
|
|
173
206
|
});
|
|
174
|
-
await out.done();
|
|
175
207
|
|
|
176
|
-
return out
|
|
208
|
+
return await getS3LocationLink(out);
|
|
177
209
|
} catch (e) {
|
|
178
|
-
|
|
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
|
-
});
|
|
210
|
+
debug('S3 buffer uploading error: ', e);
|
|
186
211
|
|
|
187
212
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
213
|
+
|
|
188
214
|
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
189
215
|
console.log(APP_PREFIX, `To enable ${chalk.bold('PRIVATE')} uploads set: TESTOMATIO_PRIVATE_ARTIFACTS=1`);
|
|
190
216
|
} else {
|
|
@@ -203,7 +229,9 @@ const uploadFileByPath = async (filePath, runId) => {
|
|
|
203
229
|
return uploadUsingS3(filePath, runId);
|
|
204
230
|
}
|
|
205
231
|
} catch (e) {
|
|
206
|
-
|
|
232
|
+
debug(e);
|
|
233
|
+
|
|
234
|
+
console.error(chalk.red('Error occurred while uploading artifacts! '), e);
|
|
207
235
|
}
|
|
208
236
|
};
|
|
209
237
|
|
|
@@ -213,13 +241,60 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
|
|
|
213
241
|
return uploadUsingS3AsBuffer(buffer, fileName, runId);
|
|
214
242
|
}
|
|
215
243
|
} catch (e) {
|
|
216
|
-
|
|
244
|
+
debug(e);
|
|
245
|
+
|
|
246
|
+
console.error(chalk.red('Error occurred while uploading artifacts! '), e);
|
|
217
247
|
}
|
|
218
248
|
};
|
|
219
249
|
|
|
250
|
+
const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
|
|
251
|
+
const checkFile = async () => {
|
|
252
|
+
const fileStats = await stat(filePath);
|
|
253
|
+
if (fileStats.isFile()) {
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
throw new Error('File not found');
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
await promiseRetry(
|
|
262
|
+
{
|
|
263
|
+
retries: attempts,
|
|
264
|
+
minTimeout: intervalMs,
|
|
265
|
+
},
|
|
266
|
+
checkFile,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
return true;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.error(chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`));
|
|
272
|
+
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const getS3LocationLink = async out => {
|
|
278
|
+
const response = await out.done();
|
|
279
|
+
|
|
280
|
+
let s3Location = response?.Location;
|
|
281
|
+
|
|
282
|
+
if (!s3Location) {
|
|
283
|
+
// TODO: out: a fallback case - remove after deeper testing
|
|
284
|
+
s3Location = out?.singleUploadResult?.Location;
|
|
285
|
+
debug('Uploaded singleUploadResult.Location', s3Location);
|
|
286
|
+
|
|
287
|
+
if (!s3Location) {
|
|
288
|
+
throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return s3Location;
|
|
293
|
+
};
|
|
294
|
+
|
|
220
295
|
module.exports = {
|
|
221
|
-
uploadFileByPath
|
|
222
|
-
uploadFileAsBuffer
|
|
296
|
+
uploadFileByPath,
|
|
297
|
+
uploadFileAsBuffer,
|
|
223
298
|
isArtifactsEnabled,
|
|
224
299
|
resetConfig,
|
|
225
300
|
};
|
|
@@ -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
|
-
|
|
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
|
-
|
|
41
|
+
// formatStack(t) {
|
|
42
|
+
// const stack = super.formatStack(t);
|
|
24
43
|
|
|
25
|
-
|
|
44
|
+
// const file = t.suite_title.split('.');
|
|
26
45
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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;
|
|
@@ -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('../
|
|
7
|
+
const { isSameTest, getCurrentDateTime } = require('../utils/utils');
|
|
8
8
|
const { CSV_HEADERS } = require('../constants');
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -14,7 +14,6 @@ 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;
|
|
@@ -22,7 +21,7 @@ class CsvPipe {
|
|
|
22
21
|
|
|
23
22
|
this.outputDir = 'export';
|
|
24
23
|
this.csvFilename = process.env.TESTOMATIO_CSV_FILENAME;
|
|
25
|
-
this.isEnabled = !!this.csvFilename
|
|
24
|
+
this.isEnabled = !!this.csvFilename;
|
|
26
25
|
this.isCsvSave = false;
|
|
27
26
|
|
|
28
27
|
if (this.csvFilename !== undefined && this.csvFilename.split('.').length > 0) {
|
|
@@ -40,6 +39,9 @@ class CsvPipe {
|
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
43
|
+
async prepareRun() {}
|
|
44
|
+
|
|
43
45
|
async createRun() {
|
|
44
46
|
// empty
|
|
45
47
|
}
|
package/lib/pipe/github.js
CHANGED
|
@@ -4,8 +4,9 @@ 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');
|
|
8
|
-
const { ansiRegExp, isSameTest } = require('../
|
|
7
|
+
const { APP_PREFIX, testomatLogoURL } = require('../constants');
|
|
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
|
|
@@ -21,7 +22,8 @@ class GitHubPipe {
|
|
|
21
22
|
this.token = params.GH_PAT || process.env.GH_PAT;
|
|
22
23
|
this.ref = process.env.GITHUB_REF;
|
|
23
24
|
this.repo = process.env.GITHUB_REPOSITORY;
|
|
24
|
-
this.
|
|
25
|
+
this.jobKey = `${process.env.GITHUB_WORKFLOW || ''} / ${process.env.GITHUB_JOB || ''}`;
|
|
26
|
+
this.hiddenCommentData = `<!--- testomat.io report ${this.jobKey} -->`;
|
|
25
27
|
|
|
26
28
|
debug('GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', 'Ref:', this.ref, 'Repo:', this.repo);
|
|
27
29
|
|
|
@@ -36,6 +38,9 @@ class GitHubPipe {
|
|
|
36
38
|
debug('GitHub Pipe: Enabled');
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
42
|
+
async prepareRun() {}
|
|
43
|
+
|
|
39
44
|
async createRun() {}
|
|
40
45
|
|
|
41
46
|
addTest(test) {
|
|
@@ -72,36 +77,34 @@ class GitHubPipe {
|
|
|
72
77
|
|
|
73
78
|
let summary = `${this.hiddenCommentData}
|
|
74
79
|
|
|
75
|
-
| [](https://testomat.io) | ${statusEmoji(
|
|
81
|
+
runParams.status,
|
|
82
|
+
)} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()} |
|
|
77
83
|
| --- | --- |
|
|
78
84
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
79
|
-
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
85
|
+
| Summary | ${failedCount ? `${statusEmoji('failed')} **${failedCount}** failed; ` : ''} ${statusEmoji(
|
|
80
86
|
'passed',
|
|
81
87
|
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
82
|
-
| Duration | 🕐 **${humanizeDuration(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
| Duration | 🕐 **${humanizeDuration(
|
|
89
|
+
parseInt(
|
|
90
|
+
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
|
91
|
+
10,
|
|
92
|
+
),
|
|
93
|
+
{
|
|
94
|
+
maxDecimalPoints: 0,
|
|
95
|
+
},
|
|
96
|
+
)}** |`;
|
|
97
|
+
|
|
86
98
|
if (this.store.runUrl) {
|
|
87
|
-
summary +=
|
|
99
|
+
summary += `\n| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
|
|
88
100
|
}
|
|
89
101
|
if (process.env.GITHUB_WORKFLOW) {
|
|
90
|
-
summary +=
|
|
102
|
+
summary += `\n| Job | 🗂️ [${this.jobKey}](${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
|
|
103
|
+
this.repo
|
|
104
|
+
}/actions/runs/${process.env.GITHUB_RUN_ID}) | `;
|
|
91
105
|
}
|
|
92
106
|
if (process.env.RUNNER_OS) {
|
|
93
|
-
summary +=
|
|
94
|
-
}
|
|
95
|
-
if (process.env.GITHUB_HEAD_REF) {
|
|
96
|
-
summary += `| Branch | 🌳 \`${process.env.GITHUB_HEAD_REF}\` | `;
|
|
97
|
-
}
|
|
98
|
-
if (process.env.GITHUB_RUN_ATTEMPT) {
|
|
99
|
-
summary += `| Run Attempt | 🌒 \`${process.env.GITHUB_RUN_ATTEMPT}\` | `;
|
|
100
|
-
}
|
|
101
|
-
if (process.env.GITHUB_RUN_ID) {
|
|
102
|
-
summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
|
|
103
|
-
this.repo
|
|
104
|
-
}/actions/runs/${process.env.GITHUB_RUN_ID} | `;
|
|
107
|
+
summary += `\n| Operating System | 🖥️ \`${process.env.RUNNER_OS}\` ${process.env.RUNNER_ARCH || ''} | `;
|
|
105
108
|
}
|
|
106
109
|
|
|
107
110
|
const failures = this.tests
|
|
@@ -184,21 +187,6 @@ class GitHubPipe {
|
|
|
184
187
|
}
|
|
185
188
|
}
|
|
186
189
|
|
|
187
|
-
function statusEmoji(status) {
|
|
188
|
-
if (status === 'passed') return '🟢';
|
|
189
|
-
if (status === 'failed') return '🔴';
|
|
190
|
-
if (status === 'skipped') return '🟡';
|
|
191
|
-
return '';
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function fullName(t) {
|
|
195
|
-
let line = '';
|
|
196
|
-
if (t.suite_title) line = `${t.suite_title}: `;
|
|
197
|
-
line += `**${t.title}**`;
|
|
198
|
-
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
199
|
-
return line;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
190
|
async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
|
|
203
191
|
if (process.env.GH_KEEP_OUTDATED_REPORTS) return;
|
|
204
192
|
|