@testomatio/reporter 1.3.1-beta → 1.3.1-beta.1-exclude-skipped-tests
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 +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 +47 -2
- package/lib/bin/reportXml.js +22 -16
- package/lib/bin/startTest.js +27 -6
- package/lib/client.js +185 -62
- 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 +27 -39
- 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 +337 -54
- 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 +218 -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/adapter/webdriver.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
2
|
const WDIOReporter = require('@wdio/reporter').default;
|
|
3
3
|
const TestomatClient = require('../client');
|
|
4
|
-
const {
|
|
4
|
+
const { getTestomatIdFromTestTitle } = require('../utils/utils');
|
|
5
5
|
|
|
6
6
|
class WebdriverReporter extends WDIOReporter {
|
|
7
7
|
constructor(options) {
|
|
@@ -31,12 +31,24 @@ class WebdriverReporter extends WDIOReporter {
|
|
|
31
31
|
this._addTestPromises.push(this.addTest(test));
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// wdio-cucumber does not trigger onTestEnd hook, thus, using this one
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param {} scerario
|
|
38
|
+
* @returns
|
|
39
|
+
*/
|
|
40
|
+
onSuiteEnd(scerario) {
|
|
41
|
+
if (scerario.type === 'scenario') {
|
|
42
|
+
this._addTestPromises.push(this.addBddScenario(scerario));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
async addTest(test) {
|
|
35
47
|
if (!this.client) return;
|
|
36
48
|
|
|
37
49
|
const { title, _duration: duration, state, error, output } = test;
|
|
38
50
|
|
|
39
|
-
const testId =
|
|
51
|
+
const testId = getTestomatIdFromTestTitle(title);
|
|
40
52
|
|
|
41
53
|
const screenshotEndpoint = '/session/:sessionId/screenshot';
|
|
42
54
|
const screenshotsBuffers = output
|
|
@@ -51,6 +63,39 @@ class WebdriverReporter extends WDIOReporter {
|
|
|
51
63
|
filesBuffers: screenshotsBuffers,
|
|
52
64
|
});
|
|
53
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {import('../../types').WebdriverIOScenario} scenario
|
|
69
|
+
*/
|
|
70
|
+
addBddScenario(scenario) {
|
|
71
|
+
if (!this.client) return;
|
|
72
|
+
|
|
73
|
+
const { title, _duration: duration } = scenario;
|
|
74
|
+
|
|
75
|
+
const testId = getTestomatIdFromTestTitle(title || scenario.tags.map(tag => tag.name).join(' '));
|
|
76
|
+
|
|
77
|
+
let scenarioState = scenario.tests.every(test => test.state === 'passed') ? 'passed' : 'failed';
|
|
78
|
+
if (scenario.tests.every(test => test.state === 'skipped')) {
|
|
79
|
+
scenarioState = 'skipped';
|
|
80
|
+
}
|
|
81
|
+
const errors = scenario.tests
|
|
82
|
+
.filter(test => test.state === 'failed')
|
|
83
|
+
.map(test => test.error?.stack)
|
|
84
|
+
.filter(Boolean);
|
|
85
|
+
const error = errors.join('\n');
|
|
86
|
+
|
|
87
|
+
const tags = scenario.tags.map(tag => tag.name);
|
|
88
|
+
|
|
89
|
+
return this.client.addTestRun(scenarioState, {
|
|
90
|
+
error: error ? Error(error) : null,
|
|
91
|
+
title,
|
|
92
|
+
test_id: testId,
|
|
93
|
+
time: duration,
|
|
94
|
+
tags,
|
|
95
|
+
file: scenario.file,
|
|
96
|
+
// filesBuffers: screenshotsBuffers,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
54
99
|
}
|
|
55
100
|
|
|
56
101
|
module.exports = WebdriverReporter;
|
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
|
-
const { APP_PREFIX } = require('./constants');
|
|
9
|
+
const { APP_PREFIX, STATUS } = 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,18 @@ 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
|
+
|
|
125
|
+
if (status === STATUS.SKIPPED && process.env.TESTOMATIO_IGNORE_SKIPPED) {
|
|
126
|
+
return; // do not log skipped tests
|
|
127
|
+
}
|
|
128
|
+
|
|
72
129
|
if (!testData)
|
|
73
130
|
testData = {
|
|
74
131
|
title: 'Unknown test',
|
|
@@ -84,46 +141,31 @@ class Client {
|
|
|
84
141
|
steps,
|
|
85
142
|
code = null,
|
|
86
143
|
title,
|
|
144
|
+
file,
|
|
87
145
|
suite_title,
|
|
88
146
|
suite_id,
|
|
89
147
|
test_id,
|
|
148
|
+
manuallyAttachedArtifacts,
|
|
149
|
+
meta,
|
|
90
150
|
} = testData;
|
|
91
151
|
let { message = '' } = testData;
|
|
92
152
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
let stack = '';
|
|
96
|
-
|
|
153
|
+
let errorFormatted = '';
|
|
97
154
|
if (error) {
|
|
98
|
-
|
|
155
|
+
errorFormatted += this.formatError(error) || '';
|
|
99
156
|
message = error?.message;
|
|
100
157
|
}
|
|
101
|
-
if (steps) {
|
|
102
|
-
stack += this.formatSteps(stack, steps);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
stack += testData.stack || '';
|
|
106
158
|
|
|
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}` : '';
|
|
159
|
+
// Attach logs
|
|
160
|
+
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
113
161
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
files.push(...storeArtifacts);
|
|
117
|
-
}
|
|
162
|
+
// add artifacts
|
|
163
|
+
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
118
164
|
|
|
119
|
-
|
|
120
|
-
debug('CLIENT global[testomatioArtifacts]', global.testomatioArtifacts);
|
|
121
|
-
files.push(...global.testomatioArtifacts);
|
|
122
|
-
global.testomatioArtifacts = [];
|
|
123
|
-
}
|
|
165
|
+
const uploadedFiles = [];
|
|
124
166
|
|
|
125
|
-
for (const
|
|
126
|
-
uploadedFiles.push(upload.uploadFileByPath(
|
|
167
|
+
for (const f of files) {
|
|
168
|
+
uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
|
|
127
169
|
}
|
|
128
170
|
|
|
129
171
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
@@ -131,18 +173,22 @@ class Client {
|
|
|
131
173
|
uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
|
|
132
174
|
}
|
|
133
175
|
|
|
134
|
-
const artifacts = await Promise.all(uploadedFiles);
|
|
176
|
+
const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
|
|
135
177
|
|
|
136
|
-
|
|
178
|
+
if (artifacts.length < uploadedFiles.length) {
|
|
179
|
+
const failedUploading = uploadedFiles.length - artifacts.length;
|
|
180
|
+
this.failedToUpload += failedUploading;
|
|
181
|
+
}
|
|
137
182
|
|
|
138
|
-
this.totalUploaded +=
|
|
183
|
+
this.totalUploaded += artifacts.length;
|
|
139
184
|
|
|
140
185
|
const data = {
|
|
141
186
|
files,
|
|
142
187
|
steps,
|
|
143
188
|
status,
|
|
144
|
-
stack,
|
|
189
|
+
stack: fullLogs,
|
|
145
190
|
example,
|
|
191
|
+
file,
|
|
146
192
|
code,
|
|
147
193
|
title,
|
|
148
194
|
suite_title,
|
|
@@ -151,16 +197,19 @@ class Client {
|
|
|
151
197
|
message,
|
|
152
198
|
run_time: parseFloat(time),
|
|
153
199
|
artifacts,
|
|
200
|
+
meta,
|
|
154
201
|
};
|
|
155
202
|
|
|
203
|
+
// debug('Adding test run...', data);
|
|
204
|
+
|
|
156
205
|
this.queue = this.queue.then(() =>
|
|
157
206
|
Promise.all(
|
|
158
|
-
this.pipes.map(async
|
|
207
|
+
this.pipes.map(async pipe => {
|
|
159
208
|
try {
|
|
160
|
-
const result = await
|
|
161
|
-
return { pipe:
|
|
209
|
+
const result = await pipe.addTest(data);
|
|
210
|
+
return { pipe: pipe.toString(), result };
|
|
162
211
|
} catch (err) {
|
|
163
|
-
console.log(APP_PREFIX,
|
|
212
|
+
console.log(APP_PREFIX, pipe.toString(), err);
|
|
164
213
|
}
|
|
165
214
|
}),
|
|
166
215
|
),
|
|
@@ -177,6 +226,7 @@ class Client {
|
|
|
177
226
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
178
227
|
*/
|
|
179
228
|
updateRunStatus(status, isParallel = false) {
|
|
229
|
+
debug('Updating run status...');
|
|
180
230
|
// all pipes disabled, skipping
|
|
181
231
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
182
232
|
|
|
@@ -185,15 +235,27 @@ class Client {
|
|
|
185
235
|
this.queue = this.queue
|
|
186
236
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
187
237
|
.then(() => {
|
|
188
|
-
debug('TOTAL
|
|
238
|
+
debug('TOTAL artifacts', this.totalUploaded);
|
|
239
|
+
if (this.totalUploaded && !upload.isArtifactsEnabled())
|
|
240
|
+
debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
|
|
189
241
|
|
|
190
|
-
if (
|
|
242
|
+
if (this.totalUploaded && upload.isArtifactsEnabled()) {
|
|
191
243
|
console.log(
|
|
192
244
|
APP_PREFIX,
|
|
193
|
-
`🗄️
|
|
245
|
+
`🗄️ ${this.totalUploaded} artifacts ${
|
|
194
246
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
195
|
-
} uploaded to S3 bucket
|
|
247
|
+
} uploaded to S3 bucket`,
|
|
196
248
|
);
|
|
249
|
+
|
|
250
|
+
if (this.failedToUpload > 0) {
|
|
251
|
+
console.log(
|
|
252
|
+
APP_PREFIX,
|
|
253
|
+
chalk.yellow(
|
|
254
|
+
`Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded.
|
|
255
|
+
Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
|
|
256
|
+
),
|
|
257
|
+
);
|
|
258
|
+
}
|
|
197
259
|
}
|
|
198
260
|
})
|
|
199
261
|
.catch(err => console.log(APP_PREFIX, err));
|
|
@@ -201,15 +263,27 @@ class Client {
|
|
|
201
263
|
return this.queue;
|
|
202
264
|
}
|
|
203
265
|
|
|
204
|
-
|
|
205
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
268
|
+
* @returns {string}
|
|
269
|
+
*/
|
|
270
|
+
formatLogs({ error, steps, logs }) {
|
|
271
|
+
error = error?.trim();
|
|
272
|
+
steps = steps?.trim();
|
|
273
|
+
logs = logs?.trim();
|
|
274
|
+
|
|
275
|
+
let testLogs = '';
|
|
276
|
+
if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}\n\n`;
|
|
277
|
+
if (logs) testLogs += `${chalk.bold.gray('################[ Logs ]################')}\n${logs}\n\n`;
|
|
278
|
+
if (error) testLogs += `${chalk.bold.red('################[ Failure ]################')}\n${error}`;
|
|
279
|
+
return testLogs;
|
|
206
280
|
}
|
|
207
281
|
|
|
208
282
|
formatError(error, message) {
|
|
209
283
|
if (!message) message = error.message;
|
|
210
284
|
if (error.inspect) message = error.inspect() || '';
|
|
211
285
|
|
|
212
|
-
let stack =
|
|
286
|
+
let stack = `${message}\n`;
|
|
213
287
|
|
|
214
288
|
// diffs for mocha, cypress, codeceptjs style
|
|
215
289
|
if (error.actual && error.expected) {
|
|
@@ -219,17 +293,21 @@ class Client {
|
|
|
219
293
|
stack += '\n\n';
|
|
220
294
|
}
|
|
221
295
|
|
|
296
|
+
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
297
|
+
|
|
222
298
|
try {
|
|
299
|
+
let hasFrame = false;
|
|
223
300
|
const record = createCallsiteRecord({
|
|
224
301
|
forError: error,
|
|
302
|
+
isCallsiteFrame: frame => {
|
|
303
|
+
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
304
|
+
if (hasFrame) return false;
|
|
305
|
+
if (isNotInternalFrame(frame)) hasFrame = true;
|
|
306
|
+
return hasFrame;
|
|
307
|
+
},
|
|
225
308
|
});
|
|
226
309
|
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
|
-
});
|
|
310
|
+
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
233
311
|
}
|
|
234
312
|
return stack;
|
|
235
313
|
} catch (e) {
|
|
@@ -238,4 +316,49 @@ class Client {
|
|
|
238
316
|
}
|
|
239
317
|
}
|
|
240
318
|
|
|
319
|
+
function isNotInternalFrame(frame) {
|
|
320
|
+
return (
|
|
321
|
+
frame.getFileName() &&
|
|
322
|
+
frame.getFileName().includes(sep) &&
|
|
323
|
+
!frame.getFileName().includes('node_modules') &&
|
|
324
|
+
!frame.getFileName().includes('internal')
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
*
|
|
330
|
+
* @param {TestData} testData
|
|
331
|
+
* @returns boolean
|
|
332
|
+
*/
|
|
333
|
+
function isTestShouldBeExculedFromReport(testData) {
|
|
334
|
+
// const fileName = path.basename(test.location?.file || '');
|
|
335
|
+
const globExcludeFilesPattern = process.env.TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN;
|
|
336
|
+
if (!globExcludeFilesPattern) return false;
|
|
337
|
+
|
|
338
|
+
if (!testData.file) {
|
|
339
|
+
debug('No "file" property found for test ', testData.title);
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const excludeParretnsList = globExcludeFilesPattern.split(';');
|
|
344
|
+
|
|
345
|
+
// as scanning files is time consuming operation, just save the result in variable to avoid multiple scans
|
|
346
|
+
if (!listOfTestFilesToExcludeFromReport) {
|
|
347
|
+
// list of files with relative paths
|
|
348
|
+
listOfTestFilesToExcludeFromReport = glob.sync(excludeParretnsList, { ignore: '**/node_modules/**' });
|
|
349
|
+
debug('Tests from next files will not be reported:', listOfTestFilesToExcludeFromReport);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const testFileRelativePath = path.relative(process.cwd(), testData.file);
|
|
353
|
+
|
|
354
|
+
// no files found matching the exclusion pattern
|
|
355
|
+
if (!listOfTestFilesToExcludeFromReport.length) return false;
|
|
356
|
+
|
|
357
|
+
if (listOfTestFilesToExcludeFromReport.includes(testFileRelativePath)) {
|
|
358
|
+
debug(`Excluding test '${testData.title}' <${testFileRelativePath}> from reporting`);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
241
364
|
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;
|