@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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const { resetConfig } = require('../fileUploader');
|
|
2
|
+
const { APP_PREFIX } = require('../constants');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Set S3 credentials from the provided artifacts object.
|
|
6
|
+
* @param {Object} artifacts - The artifacts object containing S3 credentials.
|
|
7
|
+
*/
|
|
8
|
+
function setS3Credentials(artifacts) {
|
|
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.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
19
|
+
resetConfig();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Generates mode request parameters based on the input params.
|
|
23
|
+
* @param {Object} params - The input parameters for the request.
|
|
24
|
+
* @param {string} params.type - The type of the request (e.g., "tag").
|
|
25
|
+
* @param {string} params.id - The ID associated with the request.
|
|
26
|
+
* @param {string} params.apiKey - The API key for authentication.
|
|
27
|
+
* @returns {Object|null} - An object containing the generated request parameters, or null if the type is invalid.
|
|
28
|
+
*/
|
|
29
|
+
function generateFilterRequestParams(params) {
|
|
30
|
+
const { type, id, apiKey } = params;
|
|
31
|
+
|
|
32
|
+
if (!type) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!id) {
|
|
37
|
+
console.error(APP_PREFIX, `Please make sure your settings "${type.toUpperCase()}"= "${id}" is correct!`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
params: {
|
|
43
|
+
type,
|
|
44
|
+
id: encodeURIComponent(id),
|
|
45
|
+
api_key: apiKey,
|
|
46
|
+
},
|
|
47
|
+
responseType: 'json',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Parse filter parameters from a string in the format "type=id".
|
|
53
|
+
* @param {string} opts - The input string containing the filter parameters.
|
|
54
|
+
* @returns {Object} An object containing the parsed filter parameters.
|
|
55
|
+
* The object has properties "type" and "id".
|
|
56
|
+
*/
|
|
57
|
+
function parseFilterParams(opts) {
|
|
58
|
+
const [type, id] = opts.split('=');
|
|
59
|
+
const validType = updateFilterType(type);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
type: validType,
|
|
63
|
+
id,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Update and validate the filter type.
|
|
69
|
+
* @param {string} type - The original filter type.
|
|
70
|
+
* @returns {string|undefined} The updated and validated filter type.
|
|
71
|
+
* Returns undefined if the type is not valid.
|
|
72
|
+
*/
|
|
73
|
+
function updateFilterType(type) {
|
|
74
|
+
const typeLowerCase = type.toLowerCase();
|
|
75
|
+
|
|
76
|
+
const filterTypes = ['tag-name', 'plan-id', 'label', 'jira-ticket'];
|
|
77
|
+
|
|
78
|
+
const filterApi = [
|
|
79
|
+
'tag',
|
|
80
|
+
'plan',
|
|
81
|
+
'label',
|
|
82
|
+
'jira',
|
|
83
|
+
// "ims-issue", //TODO: WIP
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
if (!filterTypes.includes(typeLowerCase)) {
|
|
87
|
+
console.log(APP_PREFIX, `❗❗❗ Invalid "filter=${type}" start settings! Available option list: ${filterTypes}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const index = filterTypes.indexOf(typeLowerCase);
|
|
92
|
+
|
|
93
|
+
return index !== -1 ? filterApi[index] : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Return an emoji based on the provided status.
|
|
98
|
+
* @param {string} status - The status value ('passed', 'failed', or 'skipped').
|
|
99
|
+
* @returns {string} - An emoji corresponding to the provided status.
|
|
100
|
+
*/
|
|
101
|
+
function statusEmoji(status) {
|
|
102
|
+
if (status === 'passed') return '🟢';
|
|
103
|
+
if (status === 'failed') return '🔴';
|
|
104
|
+
if (status === 'skipped') return '🟡';
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generate a full name string based on the provided test object.
|
|
110
|
+
* @param {object} t - The test object.
|
|
111
|
+
* @returns {string} - A formatted full name string for the test object.
|
|
112
|
+
*/
|
|
113
|
+
function fullName(t) {
|
|
114
|
+
let line = '';
|
|
115
|
+
if (t.suite_title) line = `${t.suite_title}: `;
|
|
116
|
+
line += `**${t.title}**`;
|
|
117
|
+
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
118
|
+
return line;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
updateFilterType,
|
|
123
|
+
parseFilterParams,
|
|
124
|
+
generateFilterRequestParams,
|
|
125
|
+
setS3Credentials,
|
|
126
|
+
statusEmoji,
|
|
127
|
+
fullName,
|
|
128
|
+
};
|
|
@@ -12,7 +12,9 @@ const debug = require('debug')('@testomatio/reporter:util');
|
|
|
12
12
|
*/
|
|
13
13
|
const parseTest = testTitle => {
|
|
14
14
|
if (!testTitle) return null;
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
const captures = testTitle.match(/@T[\w\d]{8}/);
|
|
17
|
+
|
|
16
18
|
if (captures) {
|
|
17
19
|
return captures[1];
|
|
18
20
|
}
|
|
@@ -26,7 +28,7 @@ const parseTest = testTitle => {
|
|
|
26
28
|
* @returns {String|null} suiteId
|
|
27
29
|
*/
|
|
28
30
|
const parseSuite = suiteTitle => {
|
|
29
|
-
const captures = suiteTitle.match(/@S
|
|
31
|
+
const captures = suiteTitle.match(/@S[\w\d]{8}/);
|
|
30
32
|
if (captures) {
|
|
31
33
|
return captures[1];
|
|
32
34
|
}
|
|
@@ -53,11 +55,20 @@ const isValidUrl = s => {
|
|
|
53
55
|
}
|
|
54
56
|
};
|
|
55
57
|
|
|
58
|
+
const fileMatchRegex = /file:(\/\/?[^:\s]+?\.(png|avi|webm|jpg|html|txt))/gi;
|
|
59
|
+
|
|
56
60
|
const fetchFilesFromStackTrace = (stack = '') => {
|
|
57
|
-
const files = stack.matchAll(
|
|
58
|
-
|
|
59
|
-
.map(f => f
|
|
60
|
-
|
|
61
|
+
const files = Array.from(stack.matchAll(fileMatchRegex))
|
|
62
|
+
.map(f => f[1].trim())
|
|
63
|
+
.map(f => (f.startsWith('//') ? f.substring(1) : f));
|
|
64
|
+
|
|
65
|
+
debug('Found files in stack trace: ', files);
|
|
66
|
+
|
|
67
|
+
return files.filter(f => {
|
|
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
|
+
});
|
|
61
72
|
};
|
|
62
73
|
|
|
63
74
|
const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
@@ -85,11 +96,41 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
85
96
|
|
|
86
97
|
if (!source) return '';
|
|
87
98
|
|
|
88
|
-
return source
|
|
99
|
+
return source
|
|
100
|
+
.split('\n')
|
|
89
101
|
.map((l, i) => {
|
|
90
102
|
if (i === prepend) return `${line} > ${chalk.bold(l)}`;
|
|
91
|
-
return `${line - prepend + i} | ${l}
|
|
92
|
-
})
|
|
103
|
+
return `${line - prepend + i} | ${l}`;
|
|
104
|
+
})
|
|
105
|
+
.join('\n');
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
109
|
+
|
|
110
|
+
const fetchIdFromCode = (code, opts = {}) => {
|
|
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
|
+
});
|
|
123
|
+
|
|
124
|
+
return comments.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const fetchIdFromOutput = output => {
|
|
128
|
+
const lines = output
|
|
129
|
+
.split('\n')
|
|
130
|
+
.map(l => l.trim())
|
|
131
|
+
.filter(l => l.startsWith('tid://'));
|
|
132
|
+
|
|
133
|
+
return lines.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
|
|
93
134
|
};
|
|
94
135
|
|
|
95
136
|
const fetchSourceCode = (contents, opts = {}) => {
|
|
@@ -104,7 +145,15 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
104
145
|
// remove special chars from title
|
|
105
146
|
if (!lineIndex && opts.title) {
|
|
106
147
|
const title = opts.title.replace(/[([@].*/g, '');
|
|
107
|
-
|
|
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
|
+
}
|
|
108
157
|
}
|
|
109
158
|
|
|
110
159
|
if (opts.prepend) {
|
|
@@ -113,7 +162,7 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
113
162
|
|
|
114
163
|
if (lineIndex) {
|
|
115
164
|
const result = [];
|
|
116
|
-
for (let i = lineIndex; i <
|
|
165
|
+
for (let i = lineIndex; i < lineIndex + limit; i++) {
|
|
117
166
|
if (lines[i] === undefined) continue;
|
|
118
167
|
|
|
119
168
|
if (i > lineIndex + 2 && !opts.prepend) {
|
|
@@ -187,13 +236,92 @@ const fileSystem = {
|
|
|
187
236
|
} else {
|
|
188
237
|
debug(`Trying to delete ${dirPath} but it doesn't exist`);
|
|
189
238
|
}
|
|
190
|
-
}
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const foundedTestLog = (app, tests) => {
|
|
243
|
+
const n = tests.length;
|
|
244
|
+
|
|
245
|
+
return n === 1 ? console.log(app, `✅ We found one test!`) : console.log(app, `✅ We found ${n} tests!`);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const humanize = text => {
|
|
249
|
+
text = decamelize(text);
|
|
250
|
+
return text
|
|
251
|
+
.replace(/_./g, match => ` ${match.charAt(1).toUpperCase()}`)
|
|
252
|
+
.trim()
|
|
253
|
+
.replace(/^(.)|\s(.)/g, $1 => $1.toUpperCase())
|
|
254
|
+
.trim()
|
|
255
|
+
.replace(/\sA\s/g, ' a ') // replace a|the
|
|
256
|
+
.replace(/\sThe\s/g, ' the ') // replace a|the
|
|
257
|
+
.replace(/^Test\s/, '')
|
|
258
|
+
.replace(/^Should\s/, '');
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* From https://github.com/sindresorhus/decamelize/blob/main/index.js
|
|
263
|
+
* @param {*} text
|
|
264
|
+
* @returns
|
|
265
|
+
*/
|
|
266
|
+
const decamelize = text => {
|
|
267
|
+
const separator = '_';
|
|
268
|
+
const replacement = `$1${separator}$2`;
|
|
269
|
+
|
|
270
|
+
// Split lowercase sequences followed by uppercase character.
|
|
271
|
+
// `dataForUSACounties` → `data_For_USACounties`
|
|
272
|
+
// `myURLstring → `my_URLstring`
|
|
273
|
+
let decamelized = text.replace(/([\p{Lowercase_Letter}\d])(\p{Uppercase_Letter})/gu, replacement);
|
|
274
|
+
|
|
275
|
+
// Lowercase all single uppercase characters. As we
|
|
276
|
+
// want to preserve uppercase sequences, we cannot
|
|
277
|
+
// simply lowercase the separated string at the end.
|
|
278
|
+
// `data_For_USACounties` → `data_for_USACounties`
|
|
279
|
+
decamelized = decamelized.replace(
|
|
280
|
+
/((?<![\p{Uppercase_Letter}\d])[\p{Uppercase_Letter}\d](?![\p{Uppercase_Letter}\d]))/gu,
|
|
281
|
+
$0 => $0.toLowerCase(),
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Remaining uppercase sequences will be separated from lowercase sequences.
|
|
285
|
+
// `data_For_USACounties` → `data_for_USA_counties`
|
|
286
|
+
return decamelized.replace(
|
|
287
|
+
/(\p{Uppercase_Letter}+)(\p{Uppercase_Letter}\p{Lowercase_Letter}+)/gu,
|
|
288
|
+
(_, $1, $2) => $1 + separator + $2.toLowerCase(),
|
|
289
|
+
);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Used to remove color codes
|
|
294
|
+
* @param {*} input
|
|
295
|
+
* @returns
|
|
296
|
+
*/
|
|
297
|
+
function removeColorCodes(input) {
|
|
298
|
+
// eslint-disable-next-line no-control-regex
|
|
299
|
+
return input.replace(/\x1b\[[0-9;]*m/g, '');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const testRunnerHelper = {
|
|
303
|
+
// for Jest
|
|
304
|
+
getNameOfCurrentlyRunningTest: () => {
|
|
305
|
+
if (global.testomatioTestTitle) return global.testomatioTestTitle;
|
|
306
|
+
|
|
307
|
+
if (!process.env.JEST_WORKER_ID) return null;
|
|
308
|
+
try {
|
|
309
|
+
// TODO: expect?.getState()?.testPath + ' ' + expect?.getState()?.currentTestName
|
|
310
|
+
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
311
|
+
// eslint-disable-next-line no-undef
|
|
312
|
+
return expect?.getState()?.currentTestName;
|
|
313
|
+
} catch (e) {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
},
|
|
191
317
|
};
|
|
192
318
|
|
|
193
319
|
module.exports = {
|
|
194
320
|
isSameTest,
|
|
195
321
|
fetchSourceCode,
|
|
196
322
|
fetchSourceCodeFromStackTrace,
|
|
323
|
+
fetchIdFromCode,
|
|
324
|
+
fetchIdFromOutput,
|
|
197
325
|
fetchFilesFromStackTrace,
|
|
198
326
|
fileSystem,
|
|
199
327
|
getCurrentDateTime,
|
|
@@ -202,4 +330,8 @@ module.exports = {
|
|
|
202
330
|
ansiRegExp,
|
|
203
331
|
parseTest,
|
|
204
332
|
parseSuite,
|
|
333
|
+
humanize,
|
|
334
|
+
removeColorCodes,
|
|
335
|
+
foundedTestLog,
|
|
336
|
+
testRunnerHelper,
|
|
205
337
|
};
|