@testomatio/reporter 1.3.6-beta → 1.4.0-beta-csv-enable
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 +96 -35
- package/lib/adapter/cucumber/current.js +19 -12
- package/lib/adapter/cucumber/legacy.js +7 -6
- 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 +50 -17
- package/lib/adapter/mocha.js +110 -58
- package/lib/adapter/playwright.js +95 -33
- package/lib/adapter/webdriver.js +2 -2
- package/lib/bin/reportXml.js +22 -16
- package/lib/bin/startTest.js +27 -6
- package/lib/client.js +179 -53
- package/lib/config.js +34 -0
- package/lib/constants.js +32 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +135 -54
- 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 +37 -31
- package/lib/pipe/github.js +9 -19
- package/lib/pipe/gitlab.js +22 -26
- package/lib/pipe/html.js +361 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +256 -53
- 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/emptyData.svg +23 -0
- package/lib/template/testomatio.hbs +1421 -0
- package/lib/utils/pipe_utils.js +129 -0
- package/lib/{util.js → utils/utils.js} +147 -17
- package/lib/xmlReader.js +211 -122
- package/package.json +17 -9
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -244
- package/lib/logger.js +0 -301
package/lib/bin/reportXml.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const program = require(
|
|
3
|
-
const chalk = require(
|
|
2
|
+
const program = require('commander');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
4
|
const glob = require('glob');
|
|
5
5
|
const debug = require('debug')('@testomatio/reporter:xml-cli');
|
|
6
6
|
const { APP_PREFIX } = require('../constants');
|
|
7
|
-
const XmlReader = require(
|
|
7
|
+
const XmlReader = require('../xmlReader');
|
|
8
8
|
|
|
9
9
|
const { version } = require('../../package.json');
|
|
10
10
|
|
|
11
11
|
console.log(chalk.cyan.bold(` 🤩 Testomat.io XML Reporter v${version}`));
|
|
12
12
|
|
|
13
13
|
program
|
|
14
|
-
.arguments(
|
|
15
|
-
.option(
|
|
16
|
-
.option(
|
|
17
|
-
.option(
|
|
18
|
-
.option(
|
|
19
|
-
.option(
|
|
14
|
+
.arguments('<pattern>')
|
|
15
|
+
.option('-d, --dir <dir>', 'Project directory')
|
|
16
|
+
.option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
|
|
17
|
+
.option('--lang <lang>', 'Language used (python, ruby, java)')
|
|
18
|
+
.option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
|
|
19
|
+
.option('--env-file <envfile>', 'Load environment variables from env file')
|
|
20
20
|
.action(async (pattern, opts) => {
|
|
21
21
|
if (!pattern.endsWith('.xml')) {
|
|
22
22
|
pattern += '.xml';
|
|
23
23
|
}
|
|
24
24
|
let { javaTests, lang } = opts;
|
|
25
25
|
if (opts.envFile) {
|
|
26
|
-
|
|
26
|
+
console.log(APP_PREFIX, 'Loading env file:', opts.envFile);
|
|
27
|
+
debug('Loading env file: %s', opts.envFile);
|
|
27
28
|
require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
|
|
28
29
|
}
|
|
29
30
|
if (javaTests === true) javaTests = 'src/test/java';
|
|
@@ -37,16 +38,21 @@ program
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
for (const file of files) {
|
|
40
|
-
console.log(APP_PREFIX
|
|
41
|
+
console.log(APP_PREFIX, `Parsed ${file}`);
|
|
41
42
|
runReader.parse(file);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
let timeoutTimer;
|
|
45
46
|
if (opts.timelimit) {
|
|
46
|
-
timeoutTimer = setTimeout(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
timeoutTimer = setTimeout(
|
|
48
|
+
() => {
|
|
49
|
+
console.log(
|
|
50
|
+
`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`,
|
|
51
|
+
);
|
|
52
|
+
process.exit(0);
|
|
53
|
+
},
|
|
54
|
+
parseInt(opts.timelimit, 10) * 1000,
|
|
55
|
+
);
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
try {
|
|
@@ -56,7 +62,7 @@ program
|
|
|
56
62
|
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
57
63
|
}
|
|
58
64
|
|
|
59
|
-
if (timeoutTimer) clearTimeout(timeoutTimer)
|
|
65
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
60
66
|
});
|
|
61
67
|
|
|
62
68
|
if (process.argv.length < 3) {
|
package/lib/bin/startTest.js
CHANGED
|
@@ -5,6 +5,7 @@ const chalk = require('chalk');
|
|
|
5
5
|
const TestomatClient = require('../client');
|
|
6
6
|
const { APP_PREFIX, STATUS } = require('../constants');
|
|
7
7
|
const { version } = require('../../package.json');
|
|
8
|
+
const config = require('../config');
|
|
8
9
|
|
|
9
10
|
console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
|
|
10
11
|
|
|
@@ -12,13 +13,15 @@ program
|
|
|
12
13
|
.option('-c, --command <cmd>', 'Test runner command')
|
|
13
14
|
.option('--launch', 'Start a new run and return its ID')
|
|
14
15
|
.option('--finish', 'Finish Run by its ID')
|
|
15
|
-
.option(
|
|
16
|
-
.
|
|
16
|
+
.option('--env-file <envfile>', 'Load environment variables from env file')
|
|
17
|
+
.option('--filter <filter>', 'Additional execution filter')
|
|
18
|
+
.action(async opts => {
|
|
19
|
+
const { launch, finish, filter } = opts;
|
|
20
|
+
let { command } = opts;
|
|
17
21
|
|
|
18
|
-
const { command, launch, finish } = opts;
|
|
19
22
|
if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
|
|
20
23
|
|
|
21
|
-
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] ||
|
|
24
|
+
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
22
25
|
const title = process.env.TESTOMATIO_TITLE;
|
|
23
26
|
|
|
24
27
|
if (launch) {
|
|
@@ -57,6 +60,26 @@ program
|
|
|
57
60
|
return;
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
64
|
+
|
|
65
|
+
if (filter) {
|
|
66
|
+
const [pipe, ...optsArray] = filter.split(':');
|
|
67
|
+
const pipeOptions = optsArray.join(':');
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const tests = await client.prepareRun({ pipe, pipeOptions });
|
|
71
|
+
|
|
72
|
+
if (!tests || tests.length === 0) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const grep = ` --grep (${tests.join('|')})`;
|
|
77
|
+
command += grep;
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.log(APP_PREFIX, err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
const testCmds = command.split(' ');
|
|
61
84
|
console.log(APP_PREFIX, `🚀 Running`, chalk.green(command));
|
|
62
85
|
|
|
@@ -73,8 +96,6 @@ program
|
|
|
73
96
|
return;
|
|
74
97
|
}
|
|
75
98
|
|
|
76
|
-
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
77
|
-
|
|
78
99
|
client.createRun().then(() => {
|
|
79
100
|
const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
|
|
80
101
|
|
package/lib/client.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
const debug = require('debug')('@testomatio/reporter:client');
|
|
2
2
|
const createCallsiteRecord = require('callsite-record');
|
|
3
3
|
const { sep, join } = require('path');
|
|
4
|
+
const { minimatch } = require('minimatch');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const chalk = require('chalk');
|
|
6
7
|
const { randomUUID } = require('crypto');
|
|
7
8
|
const upload = require('./fileUploader');
|
|
8
9
|
const { APP_PREFIX } = require('./constants');
|
|
9
10
|
const pipesFactory = require('./pipe');
|
|
11
|
+
const { glob } = require('glob');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
let listOfTestFilesToExcludeFromReport = null;
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* @typedef {import('../types').TestData} TestData
|
|
@@ -26,22 +31,76 @@ class Client {
|
|
|
26
31
|
this.pipes = pipesFactory(params, store);
|
|
27
32
|
this.queue = Promise.resolve();
|
|
28
33
|
this.totalUploaded = 0;
|
|
34
|
+
this.failedToUpload = 0;
|
|
29
35
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
36
|
+
this.executionList = Promise.resolve();
|
|
37
|
+
|
|
30
38
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
31
39
|
}
|
|
32
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Asynchronously prepares the execution list for running tests through various pipes.
|
|
43
|
+
* Each pipe in the client is checked for enablement,
|
|
44
|
+
* and if all pipes are disabled, the function returns a resolved Promise.
|
|
45
|
+
* Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
|
|
46
|
+
* The results are then filtered to remove any undefined values.
|
|
47
|
+
* If no valid results are found, the function returns undefined.
|
|
48
|
+
* Otherwise, it returns the first non-empty array from the filtered results.
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} params - The options for preparing the test execution list.
|
|
51
|
+
* @param {string} params.pipe - Name of the executed pipe.
|
|
52
|
+
* @param {string} params.pipeOptions - Filter option.
|
|
53
|
+
* @returns {Promise<any>} - A Promise that resolves to an
|
|
54
|
+
* array containing the prepared execution list,
|
|
55
|
+
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
56
|
+
*/
|
|
57
|
+
async prepareRun(params) {
|
|
58
|
+
const { pipe, pipeOptions } = params;
|
|
59
|
+
// all pipes disabled, skipping
|
|
60
|
+
if (!this.pipes.some(p => p.isEnabled)) {
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
|
|
66
|
+
|
|
67
|
+
if (!filterPipe.isEnabled) {
|
|
68
|
+
// TODO:for the future for the another pipes
|
|
69
|
+
console.warn(
|
|
70
|
+
APP_PREFIX,
|
|
71
|
+
`At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`,
|
|
72
|
+
);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const results = await Promise.all(
|
|
77
|
+
this.pipes.map(async p => ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
|
|
81
|
+
|
|
82
|
+
if (!result || result.length === 0) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
debug('Execution tests list', result);
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(APP_PREFIX, err);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
33
94
|
/**
|
|
34
95
|
* Used to create a new Test run
|
|
35
96
|
*
|
|
36
97
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
37
98
|
*/
|
|
38
99
|
createRun() {
|
|
100
|
+
debug('Creating run...');
|
|
39
101
|
// all pipes disabled, skipping
|
|
40
102
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
41
103
|
|
|
42
|
-
global.testomatioArtifacts = [];
|
|
43
|
-
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
44
|
-
|
|
45
104
|
this.queue = this.queue
|
|
46
105
|
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
47
106
|
.catch(err => console.log(APP_PREFIX, err))
|
|
@@ -55,13 +114,14 @@ class Client {
|
|
|
55
114
|
*
|
|
56
115
|
* @param {string|undefined} status
|
|
57
116
|
* @param {TestData} [testData]
|
|
58
|
-
* @param {string[]} [storeArtifacts]
|
|
59
117
|
* @returns {Promise<PipeResult[]>}
|
|
60
118
|
*/
|
|
61
|
-
async addTestRun(status, testData
|
|
119
|
+
async addTestRun(status, testData) {
|
|
62
120
|
// all pipes disabled, skipping
|
|
63
121
|
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
64
122
|
|
|
123
|
+
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
124
|
+
|
|
65
125
|
if (!testData)
|
|
66
126
|
testData = {
|
|
67
127
|
title: 'Unknown test',
|
|
@@ -77,46 +137,31 @@ class Client {
|
|
|
77
137
|
steps,
|
|
78
138
|
code = null,
|
|
79
139
|
title,
|
|
140
|
+
file,
|
|
80
141
|
suite_title,
|
|
81
142
|
suite_id,
|
|
82
143
|
test_id,
|
|
144
|
+
manuallyAttachedArtifacts,
|
|
145
|
+
meta,
|
|
83
146
|
} = testData;
|
|
84
147
|
let { message = '' } = testData;
|
|
85
148
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
let stack = '';
|
|
89
|
-
|
|
149
|
+
let errorFormatted = '';
|
|
90
150
|
if (error) {
|
|
91
|
-
|
|
151
|
+
errorFormatted += this.formatError(error) || '';
|
|
92
152
|
message = error?.message;
|
|
93
153
|
}
|
|
94
|
-
if (steps) {
|
|
95
|
-
stack += this.formatSteps(stack, steps);
|
|
96
|
-
}
|
|
97
154
|
|
|
98
|
-
|
|
155
|
+
// Attach logs
|
|
156
|
+
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
99
157
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
const testLogs = logger.getLogs(test_id);
|
|
103
|
-
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
104
|
-
if (stack) stack += '\n\n';
|
|
105
|
-
stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
|
|
158
|
+
// add artifacts
|
|
159
|
+
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
106
160
|
|
|
107
|
-
|
|
108
|
-
debug('CLIENT storeArtifact', storeArtifacts);
|
|
109
|
-
files.push(...storeArtifacts);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (Array.isArray(global.testomatioArtifacts)) {
|
|
113
|
-
debug('CLIENT global[testomatioArtifacts]', global.testomatioArtifacts);
|
|
114
|
-
files.push(...global.testomatioArtifacts);
|
|
115
|
-
global.testomatioArtifacts = [];
|
|
116
|
-
}
|
|
161
|
+
const uploadedFiles = [];
|
|
117
162
|
|
|
118
|
-
for (const
|
|
119
|
-
uploadedFiles.push(upload.uploadFileByPath(
|
|
163
|
+
for (const f of files) {
|
|
164
|
+
uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
|
|
120
165
|
}
|
|
121
166
|
|
|
122
167
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
@@ -124,18 +169,22 @@ class Client {
|
|
|
124
169
|
uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
|
|
125
170
|
}
|
|
126
171
|
|
|
127
|
-
const artifacts = await Promise.all(uploadedFiles);
|
|
172
|
+
const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
|
|
128
173
|
|
|
129
|
-
|
|
174
|
+
if (artifacts.length < uploadedFiles.length) {
|
|
175
|
+
const failedUploading = uploadedFiles.length - artifacts.length;
|
|
176
|
+
this.failedToUpload += failedUploading;
|
|
177
|
+
}
|
|
130
178
|
|
|
131
|
-
this.totalUploaded +=
|
|
179
|
+
this.totalUploaded += artifacts.length;
|
|
132
180
|
|
|
133
181
|
const data = {
|
|
134
182
|
files,
|
|
135
183
|
steps,
|
|
136
184
|
status,
|
|
137
|
-
stack,
|
|
185
|
+
stack: fullLogs,
|
|
138
186
|
example,
|
|
187
|
+
file,
|
|
139
188
|
code,
|
|
140
189
|
title,
|
|
141
190
|
suite_title,
|
|
@@ -144,16 +193,19 @@ class Client {
|
|
|
144
193
|
message,
|
|
145
194
|
run_time: parseFloat(time),
|
|
146
195
|
artifacts,
|
|
196
|
+
meta,
|
|
147
197
|
};
|
|
148
198
|
|
|
199
|
+
// debug('Adding test run...', data);
|
|
200
|
+
|
|
149
201
|
this.queue = this.queue.then(() =>
|
|
150
202
|
Promise.all(
|
|
151
|
-
this.pipes.map(async
|
|
203
|
+
this.pipes.map(async pipe => {
|
|
152
204
|
try {
|
|
153
|
-
const result = await
|
|
154
|
-
return { pipe:
|
|
205
|
+
const result = await pipe.addTest(data);
|
|
206
|
+
return { pipe: pipe.toString(), result };
|
|
155
207
|
} catch (err) {
|
|
156
|
-
console.log(APP_PREFIX,
|
|
208
|
+
console.log(APP_PREFIX, pipe.toString(), err);
|
|
157
209
|
}
|
|
158
210
|
}),
|
|
159
211
|
),
|
|
@@ -170,6 +222,7 @@ class Client {
|
|
|
170
222
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
171
223
|
*/
|
|
172
224
|
updateRunStatus(status, isParallel = false) {
|
|
225
|
+
debug('Updating run status...');
|
|
173
226
|
// all pipes disabled, skipping
|
|
174
227
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
175
228
|
|
|
@@ -178,15 +231,27 @@ class Client {
|
|
|
178
231
|
this.queue = this.queue
|
|
179
232
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
180
233
|
.then(() => {
|
|
181
|
-
debug('TOTAL
|
|
234
|
+
debug('TOTAL artifacts', this.totalUploaded);
|
|
235
|
+
if (this.totalUploaded && !upload.isArtifactsEnabled())
|
|
236
|
+
debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
|
|
182
237
|
|
|
183
|
-
if (
|
|
238
|
+
if (this.totalUploaded && upload.isArtifactsEnabled()) {
|
|
184
239
|
console.log(
|
|
185
240
|
APP_PREFIX,
|
|
186
|
-
`🗄️
|
|
241
|
+
`🗄️ ${this.totalUploaded} artifacts ${
|
|
187
242
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
188
|
-
} uploaded to S3 bucket
|
|
243
|
+
} uploaded to S3 bucket`,
|
|
189
244
|
);
|
|
245
|
+
|
|
246
|
+
if (this.failedToUpload > 0) {
|
|
247
|
+
console.log(
|
|
248
|
+
APP_PREFIX,
|
|
249
|
+
chalk.yellow(
|
|
250
|
+
`Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded.
|
|
251
|
+
Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
190
255
|
}
|
|
191
256
|
})
|
|
192
257
|
.catch(err => console.log(APP_PREFIX, err));
|
|
@@ -194,15 +259,27 @@ class Client {
|
|
|
194
259
|
return this.queue;
|
|
195
260
|
}
|
|
196
261
|
|
|
197
|
-
|
|
198
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
264
|
+
* @returns {string}
|
|
265
|
+
*/
|
|
266
|
+
formatLogs({ error, steps, logs }) {
|
|
267
|
+
error = error?.trim();
|
|
268
|
+
steps = steps?.trim();
|
|
269
|
+
logs = logs?.trim();
|
|
270
|
+
|
|
271
|
+
let testLogs = '';
|
|
272
|
+
if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}\n\n`;
|
|
273
|
+
if (logs) testLogs += `${chalk.bold.gray('################[ Logs ]################')}\n${logs}\n\n`;
|
|
274
|
+
if (error) testLogs += `${chalk.bold.red('################[ Failure ]################')}\n${error}`;
|
|
275
|
+
return testLogs;
|
|
199
276
|
}
|
|
200
277
|
|
|
201
278
|
formatError(error, message) {
|
|
202
279
|
if (!message) message = error.message;
|
|
203
280
|
if (error.inspect) message = error.inspect() || '';
|
|
204
281
|
|
|
205
|
-
let stack =
|
|
282
|
+
let stack = `${message}\n`;
|
|
206
283
|
|
|
207
284
|
// diffs for mocha, cypress, codeceptjs style
|
|
208
285
|
if (error.actual && error.expected) {
|
|
@@ -212,17 +289,21 @@ class Client {
|
|
|
212
289
|
stack += '\n\n';
|
|
213
290
|
}
|
|
214
291
|
|
|
292
|
+
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
293
|
+
|
|
215
294
|
try {
|
|
295
|
+
let hasFrame = false;
|
|
216
296
|
const record = createCallsiteRecord({
|
|
217
297
|
forError: error,
|
|
298
|
+
isCallsiteFrame: frame => {
|
|
299
|
+
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
300
|
+
if (hasFrame) return false;
|
|
301
|
+
if (isNotInternalFrame(frame)) hasFrame = true;
|
|
302
|
+
return hasFrame;
|
|
303
|
+
},
|
|
218
304
|
});
|
|
219
305
|
if (record && !record.filename.startsWith('http')) {
|
|
220
|
-
stack += record.renderSync({
|
|
221
|
-
stackFilter: frame =>
|
|
222
|
-
frame.getFileName().indexOf(sep) > -1 &&
|
|
223
|
-
frame.getFileName().indexOf('node_modules') < 0 &&
|
|
224
|
-
frame.getFileName().indexOf('internal') < 0,
|
|
225
|
-
});
|
|
306
|
+
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
226
307
|
}
|
|
227
308
|
return stack;
|
|
228
309
|
} catch (e) {
|
|
@@ -231,4 +312,49 @@ class Client {
|
|
|
231
312
|
}
|
|
232
313
|
}
|
|
233
314
|
|
|
315
|
+
function isNotInternalFrame(frame) {
|
|
316
|
+
return (
|
|
317
|
+
frame.getFileName() &&
|
|
318
|
+
frame.getFileName().includes(sep) &&
|
|
319
|
+
!frame.getFileName().includes('node_modules') &&
|
|
320
|
+
!frame.getFileName().includes('internal')
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
*
|
|
326
|
+
* @param {TestData} testData
|
|
327
|
+
* @returns boolean
|
|
328
|
+
*/
|
|
329
|
+
function isTestShouldBeExculedFromReport(testData) {
|
|
330
|
+
// const fileName = path.basename(test.location?.file || '');
|
|
331
|
+
const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
|
|
332
|
+
if (!globExcludeFilesPattern) return false;
|
|
333
|
+
|
|
334
|
+
if (!testData.file) {
|
|
335
|
+
debug('No "file" property found for test ', testData.title);
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const excludeParretnsList = globExcludeFilesPattern.split(';');
|
|
340
|
+
|
|
341
|
+
// as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
|
|
342
|
+
if (!listOfTestFilesToExcludeFromReport) {
|
|
343
|
+
// list of files with relative paths
|
|
344
|
+
listOfTestFilesToExcludeFromReport = glob.sync(excludeParretnsList, { ignore: '**/node_modules/**' });
|
|
345
|
+
debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const testFileRelativePath = path.relative(process.cwd(), testData.file);
|
|
349
|
+
|
|
350
|
+
// no files found matching the exclusion pattern
|
|
351
|
+
if (!listOfTestFilesToExcludeFromReport.length) return false;
|
|
352
|
+
|
|
353
|
+
if (listOfTestFilesToExcludeFromReport.includes(testFileRelativePath)) {
|
|
354
|
+
debug(`Excluding test '${testData.title}' <${testFileRelativePath}> from reporting`);
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
234
360
|
module.exports = Client;
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// This file is used to read environment variables from .env file
|
|
2
|
+
|
|
3
|
+
// require('dotenv').config({ path: process.env.TESTOMATIO_ENV_FILE_PATH });
|
|
4
|
+
|
|
5
|
+
const debug = require('debug')('@testomatio/reporter:config');
|
|
6
|
+
|
|
7
|
+
/* for possibility to use multiple env files (reading different paths)
|
|
8
|
+
const dotenv = require('dotenv');
|
|
9
|
+
const envFileVars = dotenv.config({ path: '.env' }).parsed; */
|
|
10
|
+
|
|
11
|
+
if (process.env.TESTOMATIO_API_KEY) {
|
|
12
|
+
process.env.TESTOMATIO = process.env.TESTOMATIO_API_KEY;
|
|
13
|
+
}
|
|
14
|
+
if (process.env.TESTOMATIO_TOKEN) {
|
|
15
|
+
process.env.TESTOMATIO = process.env.TESTOMATIO_TOKEN;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (process.env.TESTOMATIO === 'undefined')
|
|
19
|
+
console.error('TESTOMATIO is "undefined". Something went wrong. Contact dev team.');
|
|
20
|
+
|
|
21
|
+
// select only TESTOMATIO related variables (only to print them in debug)
|
|
22
|
+
const testomatioEnvVars =
|
|
23
|
+
Object.keys(process.env)
|
|
24
|
+
.filter(key => key.startsWith('TESTOMATIO') || key.startsWith('S3_'))
|
|
25
|
+
.reduce((obj, key) => {
|
|
26
|
+
obj[key] = process.env[key];
|
|
27
|
+
return obj;
|
|
28
|
+
}, {}) || {};
|
|
29
|
+
debug('TESTOMATIO variables:', testomatioEnvVars);
|
|
30
|
+
|
|
31
|
+
// includes variables from .env file and process.env
|
|
32
|
+
const config = process.env;
|
|
33
|
+
|
|
34
|
+
module.exports = config;
|
package/lib/constants.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
2
4
|
|
|
3
5
|
const APP_PREFIX = chalk.gray('[TESTOMATIO]');
|
|
4
|
-
const
|
|
6
|
+
const AXIOS_TIMEOUT = 20 * 1000; // sum = 20sec
|
|
5
7
|
|
|
6
|
-
const
|
|
7
|
-
mainDir: "testomatio_tmp",
|
|
8
|
-
}
|
|
8
|
+
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
9
9
|
|
|
10
10
|
const CSV_HEADERS = [
|
|
11
11
|
{ id: 'suite_title', title: 'Suite_title' },
|
|
@@ -21,11 +21,36 @@ const STATUS = {
|
|
|
21
21
|
SKIPPED: 'skipped',
|
|
22
22
|
FINISHED: 'finished',
|
|
23
23
|
};
|
|
24
|
+
// html pipe var
|
|
25
|
+
const HTML_REPORT = {
|
|
26
|
+
FOLDER: 'html-report',
|
|
27
|
+
REPORT_DEFAULT_NAME: 'testomatio-report.html',
|
|
28
|
+
TEMPLATE_NAME: 'testomatio.hbs',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
|
|
32
|
+
|
|
33
|
+
const REPORTER_REQUEST_RETRIES = {
|
|
34
|
+
retryTimeout: 5 * 1000, // sum = 5sec
|
|
35
|
+
retriesPerRequest: 2,
|
|
36
|
+
maxTotalRetries: Number(process.env.TESTOMATIO_MAX_REQUEST_FAILURES_COUNT) || 10,
|
|
37
|
+
withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const RunStatus = {
|
|
41
|
+
Passed: 'passed',
|
|
42
|
+
Failed: 'failed',
|
|
43
|
+
Finished: 'finished',
|
|
44
|
+
};
|
|
24
45
|
|
|
25
46
|
module.exports = {
|
|
26
47
|
APP_PREFIX,
|
|
27
|
-
|
|
28
|
-
TESTOMAT_TMP_STORAGE,
|
|
48
|
+
TESTOMAT_TMP_STORAGE_DIR,
|
|
29
49
|
CSV_HEADERS,
|
|
30
50
|
STATUS,
|
|
31
|
-
|
|
51
|
+
HTML_REPORT,
|
|
52
|
+
AXIOS_TIMEOUT,
|
|
53
|
+
testomatLogoURL,
|
|
54
|
+
REPORTER_REQUEST_RETRIES,
|
|
55
|
+
RunStatus,
|
|
56
|
+
};
|