@testomatio/reporter 1.2.3-beta → 1.2.3-beta.upload-s3
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 +23 -16
- package/lib/adapter/cucumber/legacy.js +14 -13
- 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 +80 -27
- 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 +192 -69
- package/lib/config.js +34 -0
- package/lib/constants.js +19 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +135 -55
- 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 +354 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +185 -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 +1236 -0
- package/lib/utils/pipe_utils.js +129 -0
- package/lib/{util.js → utils/utils.js} +144 -12
- package/lib/xmlReader.js +211 -122
- package/package.json +18 -7
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -241
- package/lib/helpers.js +0 -34
- package/lib/logger.js +0 -293
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,7 +38,7 @@ 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
|
|
|
@@ -46,7 +47,7 @@ program
|
|
|
46
47
|
timeoutTimer = setTimeout(() => {
|
|
47
48
|
console.log(`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
|
|
48
49
|
process.exit(0);
|
|
49
|
-
}, parseInt(opts.timelimit, 10) * 1000)
|
|
50
|
+
}, parseInt(opts.timelimit, 10) * 1000);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
try {
|
|
@@ -56,7 +57,7 @@ program
|
|
|
56
57
|
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
if (timeoutTimer) clearTimeout(timeoutTimer)
|
|
60
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
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
|
-
if (!this.pipes
|
|
42
|
-
|
|
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 = {};
|
|
102
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
51
103
|
|
|
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,12 +114,13 @@ 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
|
-
if (!this.pipes
|
|
121
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
122
|
+
|
|
123
|
+
if (isTestShouldBeExculedFromReport(testData)) return [];
|
|
71
124
|
|
|
72
125
|
if (!testData)
|
|
73
126
|
testData = {
|
|
@@ -84,44 +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 = testData.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
154
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
107
|
-
const testLogs = logger.getLogs(test_id);
|
|
108
|
-
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
109
|
-
if (stack) stack += '\n\n';
|
|
110
|
-
stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
|
|
155
|
+
// Attach logs
|
|
156
|
+
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
111
157
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
files.push(...storeArtifacts);
|
|
115
|
-
}
|
|
158
|
+
// add artifacts
|
|
159
|
+
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
116
160
|
|
|
117
|
-
|
|
118
|
-
debug('CLIENT global[testomatioArtifacts]', global.testomatioArtifacts);
|
|
119
|
-
files.push(...global.testomatioArtifacts);
|
|
120
|
-
global.testomatioArtifacts = [];
|
|
121
|
-
}
|
|
161
|
+
const uploadedFiles = [];
|
|
122
162
|
|
|
123
|
-
for (const
|
|
124
|
-
uploadedFiles.push(upload.uploadFileByPath(
|
|
163
|
+
for (const f of files) {
|
|
164
|
+
uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
|
|
125
165
|
}
|
|
126
166
|
|
|
127
167
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
@@ -129,18 +169,22 @@ class Client {
|
|
|
129
169
|
uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
|
|
130
170
|
}
|
|
131
171
|
|
|
132
|
-
const artifacts = await Promise.all(uploadedFiles);
|
|
172
|
+
const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
|
|
133
173
|
|
|
134
|
-
|
|
174
|
+
if (artifacts.length < uploadedFiles.length) {
|
|
175
|
+
const failedUploading = uploadedFiles.length - artifacts.length;
|
|
176
|
+
this.failedToUpload += failedUploading;
|
|
177
|
+
}
|
|
135
178
|
|
|
136
|
-
this.totalUploaded +=
|
|
179
|
+
this.totalUploaded += artifacts.length;
|
|
137
180
|
|
|
138
181
|
const data = {
|
|
139
182
|
files,
|
|
140
183
|
steps,
|
|
141
184
|
status,
|
|
142
|
-
stack,
|
|
185
|
+
stack: fullLogs,
|
|
143
186
|
example,
|
|
187
|
+
file,
|
|
144
188
|
code,
|
|
145
189
|
title,
|
|
146
190
|
suite_title,
|
|
@@ -149,19 +193,24 @@ class Client {
|
|
|
149
193
|
message,
|
|
150
194
|
run_time: parseFloat(time),
|
|
151
195
|
artifacts,
|
|
196
|
+
meta,
|
|
152
197
|
};
|
|
153
198
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
199
|
+
// debug('Adding test run...', data);
|
|
200
|
+
|
|
201
|
+
this.queue = this.queue.then(() =>
|
|
202
|
+
Promise.all(
|
|
203
|
+
this.pipes.map(async pipe => {
|
|
204
|
+
try {
|
|
205
|
+
const result = await pipe.addTest(data);
|
|
206
|
+
return { pipe: pipe.toString(), result };
|
|
207
|
+
} catch (err) {
|
|
208
|
+
console.log(APP_PREFIX, pipe.toString(), err);
|
|
209
|
+
}
|
|
210
|
+
}),
|
|
211
|
+
),
|
|
212
|
+
);
|
|
213
|
+
|
|
165
214
|
return this.queue;
|
|
166
215
|
}
|
|
167
216
|
|
|
@@ -173,23 +222,36 @@ class Client {
|
|
|
173
222
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
174
223
|
*/
|
|
175
224
|
updateRunStatus(status, isParallel = false) {
|
|
225
|
+
debug('Updating run status...');
|
|
176
226
|
// all pipes disabled, skipping
|
|
177
|
-
if (!this.pipes
|
|
227
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
178
228
|
|
|
179
229
|
const runParams = { status, parallel: isParallel };
|
|
180
230
|
|
|
181
231
|
this.queue = this.queue
|
|
182
232
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
183
233
|
.then(() => {
|
|
184
|
-
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`);
|
|
185
237
|
|
|
186
|
-
if (
|
|
238
|
+
if (this.totalUploaded && upload.isArtifactsEnabled()) {
|
|
187
239
|
console.log(
|
|
188
240
|
APP_PREFIX,
|
|
189
|
-
`🗄️
|
|
241
|
+
`🗄️ ${this.totalUploaded} artifacts ${
|
|
190
242
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
191
|
-
} uploaded to S3 bucket
|
|
243
|
+
} uploaded to S3 bucket`,
|
|
192
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
|
+
}
|
|
193
255
|
}
|
|
194
256
|
})
|
|
195
257
|
.catch(err => console.log(APP_PREFIX, err));
|
|
@@ -197,15 +259,27 @@ class Client {
|
|
|
197
259
|
return this.queue;
|
|
198
260
|
}
|
|
199
261
|
|
|
200
|
-
|
|
201
|
-
|
|
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;
|
|
202
276
|
}
|
|
203
277
|
|
|
204
278
|
formatError(error, message) {
|
|
205
279
|
if (!message) message = error.message;
|
|
206
280
|
if (error.inspect) message = error.inspect() || '';
|
|
207
281
|
|
|
208
|
-
let stack =
|
|
282
|
+
let stack = `${message}\n`;
|
|
209
283
|
|
|
210
284
|
// diffs for mocha, cypress, codeceptjs style
|
|
211
285
|
if (error.actual && error.expected) {
|
|
@@ -215,17 +289,21 @@ class Client {
|
|
|
215
289
|
stack += '\n\n';
|
|
216
290
|
}
|
|
217
291
|
|
|
292
|
+
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
293
|
+
|
|
218
294
|
try {
|
|
295
|
+
let hasFrame = false;
|
|
219
296
|
const record = createCallsiteRecord({
|
|
220
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
|
+
},
|
|
221
304
|
});
|
|
222
305
|
if (record && !record.filename.startsWith('http')) {
|
|
223
|
-
stack += record.renderSync({
|
|
224
|
-
stackFilter: frame =>
|
|
225
|
-
frame.getFileName().indexOf(sep) > -1 &&
|
|
226
|
-
frame.getFileName().indexOf('node_modules') < 0 &&
|
|
227
|
-
frame.getFileName().indexOf('internal') < 0,
|
|
228
|
-
});
|
|
306
|
+
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
229
307
|
}
|
|
230
308
|
return stack;
|
|
231
309
|
} catch (e) {
|
|
@@ -234,4 +312,49 @@ class Client {
|
|
|
234
312
|
}
|
|
235
313
|
}
|
|
236
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
|
+
|
|
237
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,12 @@
|
|
|
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
|
|
7
|
+
const AXIOS_RETRY_TIMEOUT = 5 * 1000; // sum = 5sec
|
|
5
8
|
|
|
6
|
-
const
|
|
7
|
-
mainDir: "testomatio_tmp",
|
|
8
|
-
}
|
|
9
|
+
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
9
10
|
|
|
10
11
|
const CSV_HEADERS = [
|
|
11
12
|
{ id: 'suite_title', title: 'Suite_title' },
|
|
@@ -21,11 +22,22 @@ const STATUS = {
|
|
|
21
22
|
SKIPPED: 'skipped',
|
|
22
23
|
FINISHED: 'finished',
|
|
23
24
|
};
|
|
25
|
+
// html pipe var
|
|
26
|
+
const HTML_REPORT = {
|
|
27
|
+
FOLDER: 'html-report',
|
|
28
|
+
REPORT_DEFAULT_NAME: 'testomatio-report.html',
|
|
29
|
+
TEMPLATE_NAME: 'testomatio.hbs',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
|
|
24
33
|
|
|
25
34
|
module.exports = {
|
|
26
35
|
APP_PREFIX,
|
|
27
|
-
|
|
28
|
-
TESTOMAT_TMP_STORAGE,
|
|
36
|
+
TESTOMAT_TMP_STORAGE_DIR,
|
|
29
37
|
CSV_HEADERS,
|
|
30
38
|
STATUS,
|
|
31
|
-
|
|
39
|
+
HTML_REPORT,
|
|
40
|
+
AXIOS_TIMEOUT,
|
|
41
|
+
AXIOS_RETRY_TIMEOUT,
|
|
42
|
+
testomatLogoURL,
|
|
43
|
+
};
|