@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/utils/pipe_utils.js
CHANGED
|
@@ -6,17 +6,18 @@ const { APP_PREFIX } = require('../constants');
|
|
|
6
6
|
* @param {Object} artifacts - The artifacts object containing S3 credentials.
|
|
7
7
|
*/
|
|
8
8
|
function setS3Credentials(artifacts) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
if (!Object.keys(artifacts).length) return;
|
|
10
|
+
|
|
11
|
+
console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
|
|
12
|
+
|
|
13
|
+
if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
|
|
14
|
+
if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
|
|
15
|
+
if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
|
|
16
|
+
if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
|
|
17
|
+
if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
18
|
+
if (artifacts.SESSION_TOKEN) process.env.S3_SESSION_TOKEN = artifacts.SESSION_TOKEN;
|
|
19
|
+
if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
20
|
+
resetConfig();
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
23
|
* Generates mode request parameters based on the input params.
|
|
@@ -27,25 +28,25 @@ function setS3Credentials(artifacts) {
|
|
|
27
28
|
* @returns {Object|null} - An object containing the generated request parameters, or null if the type is invalid.
|
|
28
29
|
*/
|
|
29
30
|
function generateFilterRequestParams(params) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
const { type, id, apiKey } = params;
|
|
32
|
+
|
|
33
|
+
if (!type) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!id) {
|
|
38
|
+
console.error(APP_PREFIX, `Please make sure your settings "${type.toUpperCase()}"= "${id}" is correct!`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
params: {
|
|
44
|
+
type,
|
|
45
|
+
id: encodeURIComponent(id),
|
|
46
|
+
api_key: apiKey,
|
|
47
|
+
},
|
|
48
|
+
responseType: 'json',
|
|
49
|
+
};
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -55,13 +56,13 @@ function generateFilterRequestParams(params) {
|
|
|
55
56
|
* The object has properties "type" and "id".
|
|
56
57
|
*/
|
|
57
58
|
function parseFilterParams(opts) {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const [type, id] = opts.split('=');
|
|
60
|
+
const validType = updateFilterType(type);
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
return {
|
|
63
|
+
type: validType,
|
|
64
|
+
id,
|
|
65
|
+
};
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
|
@@ -71,33 +72,26 @@ function parseFilterParams(opts) {
|
|
|
71
72
|
* Returns undefined if the type is not valid.
|
|
72
73
|
*/
|
|
73
74
|
function updateFilterType(type) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const index = filterTypes.indexOf(typeLowerCase);
|
|
97
|
-
|
|
98
|
-
return (index !== -1)
|
|
99
|
-
? filterApi[index]
|
|
100
|
-
: undefined
|
|
75
|
+
const typeLowerCase = type.toLowerCase();
|
|
76
|
+
|
|
77
|
+
const filterTypes = ['tag-name', 'plan-id', 'label', 'jira-ticket'];
|
|
78
|
+
|
|
79
|
+
const filterApi = [
|
|
80
|
+
'tag',
|
|
81
|
+
'plan',
|
|
82
|
+
'label',
|
|
83
|
+
'jira',
|
|
84
|
+
// "ims-issue", //TODO: WIP
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
if (!filterTypes.includes(typeLowerCase)) {
|
|
88
|
+
console.log(APP_PREFIX, `❗❗❗ Invalid "filter=${type}" start settings! Available option list: ${filterTypes}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const index = filterTypes.indexOf(typeLowerCase);
|
|
93
|
+
|
|
94
|
+
return index !== -1 ? filterApi[index] : undefined;
|
|
101
95
|
}
|
|
102
96
|
|
|
103
97
|
/**
|
|
@@ -106,10 +100,10 @@ function updateFilterType(type) {
|
|
|
106
100
|
* @returns {string} - An emoji corresponding to the provided status.
|
|
107
101
|
*/
|
|
108
102
|
function statusEmoji(status) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
103
|
+
if (status === 'passed') return '🟢';
|
|
104
|
+
if (status === 'failed') return '🔴';
|
|
105
|
+
if (status === 'skipped') return '🟡';
|
|
106
|
+
return '';
|
|
113
107
|
}
|
|
114
108
|
|
|
115
109
|
/**
|
|
@@ -118,18 +112,18 @@ function statusEmoji(status) {
|
|
|
118
112
|
* @returns {string} - A formatted full name string for the test object.
|
|
119
113
|
*/
|
|
120
114
|
function fullName(t) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
let line = '';
|
|
116
|
+
if (t.suite_title) line = `${t.suite_title}: `;
|
|
117
|
+
line += `**${t.title}**`;
|
|
118
|
+
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
119
|
+
return line;
|
|
126
120
|
}
|
|
127
121
|
|
|
128
122
|
module.exports = {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
123
|
+
updateFilterType,
|
|
124
|
+
parseFilterParams,
|
|
125
|
+
generateFilterRequestParams,
|
|
126
|
+
setS3Credentials,
|
|
127
|
+
statusEmoji,
|
|
128
|
+
fullName,
|
|
129
|
+
};
|
package/lib/utils/utils.js
CHANGED
|
@@ -10,13 +10,13 @@ const debug = require('debug')('@testomatio/reporter:util');
|
|
|
10
10
|
*
|
|
11
11
|
* @returns {String|null} testId
|
|
12
12
|
*/
|
|
13
|
-
const
|
|
13
|
+
const getTestomatIdFromTestTitle = testTitle => {
|
|
14
14
|
if (!testTitle) return null;
|
|
15
15
|
|
|
16
|
-
const captures = testTitle.match(/@T
|
|
16
|
+
const captures = testTitle.match(/@T[\w\d]{8}/);
|
|
17
17
|
|
|
18
18
|
if (captures) {
|
|
19
|
-
return captures[
|
|
19
|
+
return captures[0];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
return null;
|
|
@@ -28,7 +28,7 @@ const parseTest = testTitle => {
|
|
|
28
28
|
* @returns {String|null} suiteId
|
|
29
29
|
*/
|
|
30
30
|
const parseSuite = suiteTitle => {
|
|
31
|
-
const captures = suiteTitle.match(/@S
|
|
31
|
+
const captures = suiteTitle.match(/@S[\w\d]{8}/);
|
|
32
32
|
if (captures) {
|
|
33
33
|
return captures[1];
|
|
34
34
|
}
|
|
@@ -55,21 +55,20 @@ const isValidUrl = s => {
|
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
const fileMatchRegex = /file:(\/\/?[^:\s]+?\.(png|avi|webm|jpg|html|txt))/
|
|
58
|
+
const fileMatchRegex = /file:(\/\/?[^:\s]+?\.(png|avi|webm|jpg|html|txt))/gi;
|
|
59
59
|
|
|
60
60
|
const fetchFilesFromStackTrace = (stack = '') => {
|
|
61
|
-
|
|
62
61
|
const files = Array.from(stack.matchAll(fileMatchRegex))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
.map(f => f[1].trim())
|
|
63
|
+
.map(f => (f.startsWith('//') ? f.substring(1) : f));
|
|
64
|
+
|
|
66
65
|
debug('Found files in stack trace: ', files);
|
|
67
66
|
|
|
68
67
|
return files.filter(f => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
const isFile = fs.existsSync(f);
|
|
69
|
+
if (!isFile) debug('File %s could not be found and uploaded as artifact', f);
|
|
70
|
+
return isFile;
|
|
71
|
+
});
|
|
73
72
|
};
|
|
74
73
|
|
|
75
74
|
const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
@@ -109,25 +108,30 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
109
108
|
const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
110
109
|
|
|
111
110
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
112
|
-
const comments = code
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
const comments = code
|
|
112
|
+
.split('\n')
|
|
113
|
+
.map(l => l.trim())
|
|
114
|
+
.filter(l => {
|
|
115
|
+
switch (opts.lang) {
|
|
116
|
+
case 'ruby':
|
|
117
|
+
case 'python':
|
|
118
|
+
return l.startsWith('# ');
|
|
119
|
+
default:
|
|
120
|
+
return l.startsWith('// ');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
121
123
|
|
|
122
124
|
return comments.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
+
};
|
|
125
126
|
|
|
126
|
-
const fetchIdFromOutput =
|
|
127
|
-
const lines = output
|
|
127
|
+
const fetchIdFromOutput = output => {
|
|
128
|
+
const lines = output
|
|
129
|
+
.split('\n')
|
|
130
|
+
.map(l => l.trim())
|
|
131
|
+
.filter(l => l.startsWith('tid://'));
|
|
128
132
|
|
|
129
133
|
return lines.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
|
|
130
|
-
}
|
|
134
|
+
};
|
|
131
135
|
|
|
132
136
|
const fetchSourceCode = (contents, opts = {}) => {
|
|
133
137
|
if (!opts.title && !opts.line) return '';
|
|
@@ -141,7 +145,15 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
141
145
|
// remove special chars from title
|
|
142
146
|
if (!lineIndex && opts.title) {
|
|
143
147
|
const title = opts.title.replace(/[([@].*/g, '');
|
|
144
|
-
|
|
148
|
+
|
|
149
|
+
if (opts.lang === 'java') {
|
|
150
|
+
lineIndex = lines.findIndex(l => l.includes(`test${title}`));
|
|
151
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
152
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
153
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
154
|
+
} else {
|
|
155
|
+
lineIndex = lines.findIndex(l => l.includes(title));
|
|
156
|
+
}
|
|
145
157
|
}
|
|
146
158
|
|
|
147
159
|
if (opts.prepend) {
|
|
@@ -227,14 +239,11 @@ const fileSystem = {
|
|
|
227
239
|
},
|
|
228
240
|
};
|
|
229
241
|
|
|
230
|
-
|
|
231
242
|
const foundedTestLog = (app, tests) => {
|
|
232
243
|
const n = tests.length;
|
|
233
244
|
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
: console.log(app, `✅ We found ${n} tests!`);
|
|
237
|
-
}
|
|
245
|
+
return n === 1 ? console.log(app, `✅ We found one test!`) : console.log(app, `✅ We found ${n} tests!`);
|
|
246
|
+
};
|
|
238
247
|
|
|
239
248
|
const humanize = text => {
|
|
240
249
|
text = decamelize(text);
|
|
@@ -290,13 +299,17 @@ function removeColorCodes(input) {
|
|
|
290
299
|
return input.replace(/\x1b\[[0-9;]*m/g, '');
|
|
291
300
|
}
|
|
292
301
|
|
|
293
|
-
const
|
|
294
|
-
|
|
302
|
+
const testRunnerHelper = {
|
|
303
|
+
// for Jest
|
|
304
|
+
getNameOfCurrentlyRunningTest: () => {
|
|
305
|
+
if (global.testomatioTestTitle) return global.testomatioTestTitle;
|
|
306
|
+
|
|
295
307
|
if (!process.env.JEST_WORKER_ID) return null;
|
|
296
308
|
try {
|
|
309
|
+
// TODO: expect?.getState()?.testPath + ' ' + expect?.getState()?.currentTestName
|
|
297
310
|
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
298
311
|
// eslint-disable-next-line no-undef
|
|
299
|
-
|
|
312
|
+
return expect?.getState()?.currentTestName;
|
|
300
313
|
} catch (e) {
|
|
301
314
|
return null;
|
|
302
315
|
}
|
|
@@ -312,13 +325,13 @@ module.exports = {
|
|
|
312
325
|
fetchFilesFromStackTrace,
|
|
313
326
|
fileSystem,
|
|
314
327
|
getCurrentDateTime,
|
|
315
|
-
jestHelpers,
|
|
316
328
|
specificTestInfo,
|
|
317
329
|
isValidUrl,
|
|
318
330
|
ansiRegExp,
|
|
319
|
-
|
|
331
|
+
getTestomatIdFromTestTitle,
|
|
320
332
|
parseSuite,
|
|
321
333
|
humanize,
|
|
322
334
|
removeColorCodes,
|
|
323
|
-
foundedTestLog
|
|
324
|
-
|
|
335
|
+
foundedTestLog,
|
|
336
|
+
testRunnerHelper,
|
|
337
|
+
};
|