@testomatio/reporter 1.3.0-beta → 1.3.0
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 +96 -35
- package/lib/adapter/cucumber/current.js +18 -11
- package/lib/adapter/cucumber/legacy.js +6 -5
- 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 -16
- package/lib/adapter/mocha.js +103 -51
- package/lib/adapter/playwright.js +95 -33
- package/lib/adapter/webdriver.js +1 -1
- package/lib/bin/reportXml.js +22 -16
- package/lib/bin/startTest.js +27 -6
- package/lib/client.js +180 -61
- 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 +5 -3
- package/lib/pipe/github.js +27 -39
- package/lib/pipe/gitlab.js +22 -26
- package/lib/pipe/html.js +354 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +259 -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/testomatio.hbs +1236 -0
- package/lib/utils/pipe_utils.js +129 -0
- package/lib/{util.js → utils/utils.js} +144 -14
- package/lib/xmlReader.js +211 -122
- package/package.json +17 -8
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -244
- package/lib/logger.js +0 -294
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
|
|
@@ -21,36 +26,83 @@ class Client {
|
|
|
21
26
|
* @param {*} params
|
|
22
27
|
*/
|
|
23
28
|
constructor(params = {}) {
|
|
24
|
-
this.parallel = params.parallel;
|
|
25
29
|
const store = {};
|
|
26
30
|
this.uuid = randomUUID();
|
|
27
31
|
this.pipes = pipesFactory(params, store);
|
|
28
32
|
this.queue = Promise.resolve();
|
|
29
33
|
this.totalUploaded = 0;
|
|
34
|
+
this.failedToUpload = 0;
|
|
30
35
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
36
|
+
this.executionList = Promise.resolve();
|
|
37
|
+
|
|
31
38
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
32
39
|
}
|
|
33
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
|
+
|
|
34
94
|
/**
|
|
35
95
|
* Used to create a new Test run
|
|
36
96
|
*
|
|
37
97
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
38
98
|
*/
|
|
39
99
|
createRun() {
|
|
100
|
+
debug('Creating run...');
|
|
40
101
|
// all pipes disabled, skipping
|
|
41
102
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
42
103
|
|
|
43
|
-
const runParams = {
|
|
44
|
-
title: this.title,
|
|
45
|
-
parallel: this.parallel,
|
|
46
|
-
env: this.env,
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
global.testomatioArtifacts = [];
|
|
50
|
-
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
51
|
-
|
|
52
104
|
this.queue = this.queue
|
|
53
|
-
.then(() => Promise.all(this.pipes.map(p => p.createRun(
|
|
105
|
+
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
54
106
|
.catch(err => console.log(APP_PREFIX, err))
|
|
55
107
|
.then(() => undefined); // fixes return type
|
|
56
108
|
// debug('Run', this.queue);
|
|
@@ -62,13 +114,14 @@ class Client {
|
|
|
62
114
|
*
|
|
63
115
|
* @param {string|undefined} status
|
|
64
116
|
* @param {TestData} [testData]
|
|
65
|
-
* @param {string[]} [storeArtifacts]
|
|
66
117
|
* @returns {Promise<PipeResult[]>}
|
|
67
118
|
*/
|
|
68
|
-
async addTestRun(status, testData
|
|
119
|
+
async addTestRun(status, testData) {
|
|
69
120
|
// all pipes disabled, skipping
|
|
70
121
|
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
71
122
|
|
|
123
|
+
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
124
|
+
|
|
72
125
|
if (!testData)
|
|
73
126
|
testData = {
|
|
74
127
|
title: 'Unknown test',
|
|
@@ -84,46 +137,31 @@ class Client {
|
|
|
84
137
|
steps,
|
|
85
138
|
code = null,
|
|
86
139
|
title,
|
|
140
|
+
file,
|
|
87
141
|
suite_title,
|
|
88
142
|
suite_id,
|
|
89
143
|
test_id,
|
|
144
|
+
manuallyAttachedArtifacts,
|
|
145
|
+
meta,
|
|
90
146
|
} = testData;
|
|
91
147
|
let { message = '' } = testData;
|
|
92
148
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
let stack = '';
|
|
96
|
-
|
|
149
|
+
let errorFormatted = '';
|
|
97
150
|
if (error) {
|
|
98
|
-
|
|
151
|
+
errorFormatted += this.formatError(error) || '';
|
|
99
152
|
message = error?.message;
|
|
100
153
|
}
|
|
101
|
-
if (steps) {
|
|
102
|
-
stack += this.formatSteps(stack, steps);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
stack += testData.stack || '';
|
|
106
154
|
|
|
107
|
-
//
|
|
108
|
-
const
|
|
109
|
-
const testLogs = logger.getLogs(test_id);
|
|
110
|
-
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
111
|
-
if (stack) stack += '\n\n';
|
|
112
|
-
stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
|
|
155
|
+
// Attach logs
|
|
156
|
+
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
113
157
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
files.push(...storeArtifacts);
|
|
117
|
-
}
|
|
158
|
+
// add artifacts
|
|
159
|
+
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
118
160
|
|
|
119
|
-
|
|
120
|
-
debug('CLIENT global[testomatioArtifacts]', global.testomatioArtifacts);
|
|
121
|
-
files.push(...global.testomatioArtifacts);
|
|
122
|
-
global.testomatioArtifacts = [];
|
|
123
|
-
}
|
|
161
|
+
const uploadedFiles = [];
|
|
124
162
|
|
|
125
|
-
for (const
|
|
126
|
-
uploadedFiles.push(upload.uploadFileByPath(
|
|
163
|
+
for (const f of files) {
|
|
164
|
+
uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
|
|
127
165
|
}
|
|
128
166
|
|
|
129
167
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
@@ -131,18 +169,22 @@ class Client {
|
|
|
131
169
|
uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
|
|
132
170
|
}
|
|
133
171
|
|
|
134
|
-
const artifacts = await Promise.all(uploadedFiles);
|
|
172
|
+
const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
|
|
135
173
|
|
|
136
|
-
|
|
174
|
+
if (artifacts.length < uploadedFiles.length) {
|
|
175
|
+
const failedUploading = uploadedFiles.length - artifacts.length;
|
|
176
|
+
this.failedToUpload += failedUploading;
|
|
177
|
+
}
|
|
137
178
|
|
|
138
|
-
this.totalUploaded +=
|
|
179
|
+
this.totalUploaded += artifacts.length;
|
|
139
180
|
|
|
140
181
|
const data = {
|
|
141
182
|
files,
|
|
142
183
|
steps,
|
|
143
184
|
status,
|
|
144
|
-
stack,
|
|
185
|
+
stack: fullLogs,
|
|
145
186
|
example,
|
|
187
|
+
file,
|
|
146
188
|
code,
|
|
147
189
|
title,
|
|
148
190
|
suite_title,
|
|
@@ -151,16 +193,19 @@ class Client {
|
|
|
151
193
|
message,
|
|
152
194
|
run_time: parseFloat(time),
|
|
153
195
|
artifacts,
|
|
196
|
+
meta,
|
|
154
197
|
};
|
|
155
198
|
|
|
199
|
+
// debug('Adding test run...', data);
|
|
200
|
+
|
|
156
201
|
this.queue = this.queue.then(() =>
|
|
157
202
|
Promise.all(
|
|
158
|
-
this.pipes.map(async
|
|
203
|
+
this.pipes.map(async pipe => {
|
|
159
204
|
try {
|
|
160
|
-
const result = await
|
|
161
|
-
return { pipe:
|
|
205
|
+
const result = await pipe.addTest(data);
|
|
206
|
+
return { pipe: pipe.toString(), result };
|
|
162
207
|
} catch (err) {
|
|
163
|
-
console.log(APP_PREFIX,
|
|
208
|
+
console.log(APP_PREFIX, pipe.toString(), err);
|
|
164
209
|
}
|
|
165
210
|
}),
|
|
166
211
|
),
|
|
@@ -177,6 +222,7 @@ class Client {
|
|
|
177
222
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
178
223
|
*/
|
|
179
224
|
updateRunStatus(status, isParallel = false) {
|
|
225
|
+
debug('Updating run status...');
|
|
180
226
|
// all pipes disabled, skipping
|
|
181
227
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
182
228
|
|
|
@@ -185,15 +231,27 @@ class Client {
|
|
|
185
231
|
this.queue = this.queue
|
|
186
232
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
187
233
|
.then(() => {
|
|
188
|
-
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`);
|
|
189
237
|
|
|
190
|
-
if (
|
|
238
|
+
if (this.totalUploaded && upload.isArtifactsEnabled()) {
|
|
191
239
|
console.log(
|
|
192
240
|
APP_PREFIX,
|
|
193
|
-
`🗄️
|
|
241
|
+
`🗄️ ${this.totalUploaded} artifacts ${
|
|
194
242
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
195
|
-
} uploaded to S3 bucket
|
|
243
|
+
} uploaded to S3 bucket`,
|
|
196
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
|
+
}
|
|
197
255
|
}
|
|
198
256
|
})
|
|
199
257
|
.catch(err => console.log(APP_PREFIX, err));
|
|
@@ -201,15 +259,27 @@ class Client {
|
|
|
201
259
|
return this.queue;
|
|
202
260
|
}
|
|
203
261
|
|
|
204
|
-
|
|
205
|
-
|
|
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;
|
|
206
276
|
}
|
|
207
277
|
|
|
208
278
|
formatError(error, message) {
|
|
209
279
|
if (!message) message = error.message;
|
|
210
280
|
if (error.inspect) message = error.inspect() || '';
|
|
211
281
|
|
|
212
|
-
let stack =
|
|
282
|
+
let stack = `${message}\n`;
|
|
213
283
|
|
|
214
284
|
// diffs for mocha, cypress, codeceptjs style
|
|
215
285
|
if (error.actual && error.expected) {
|
|
@@ -219,17 +289,21 @@ class Client {
|
|
|
219
289
|
stack += '\n\n';
|
|
220
290
|
}
|
|
221
291
|
|
|
292
|
+
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
293
|
+
|
|
222
294
|
try {
|
|
295
|
+
let hasFrame = false;
|
|
223
296
|
const record = createCallsiteRecord({
|
|
224
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
|
+
},
|
|
225
304
|
});
|
|
226
305
|
if (record && !record.filename.startsWith('http')) {
|
|
227
|
-
stack += record.renderSync({
|
|
228
|
-
stackFilter: frame =>
|
|
229
|
-
frame.getFileName().indexOf(sep) > -1 &&
|
|
230
|
-
frame.getFileName().indexOf('node_modules') < 0 &&
|
|
231
|
-
frame.getFileName().indexOf('internal') < 0,
|
|
232
|
-
});
|
|
306
|
+
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
233
307
|
}
|
|
234
308
|
return stack;
|
|
235
309
|
} catch (e) {
|
|
@@ -238,4 +312,49 @@ class Client {
|
|
|
238
312
|
}
|
|
239
313
|
}
|
|
240
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
|
+
|
|
241
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
|
+
};
|