@testomatio/reporter 1.0.0-beta.4 → 1.0.1-6.1
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/Changelog.md +100 -2
- package/README.md +92 -540
- package/lib/{ArtifactStorage.js → _ArtifactStorageOld.js} +16 -5
- package/lib/adapter/codecept.js +83 -53
- package/lib/adapter/cucumber/current.js +95 -59
- package/lib/adapter/cucumber/legacy.js +25 -11
- package/lib/adapter/cypress-plugin/index.js +1 -1
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +30 -2
- package/lib/adapter/mocha.js +24 -24
- package/lib/adapter/playwright.js +18 -11
- package/lib/adapter/webdriver.js +1 -1
- package/lib/artifactStorage.js +25 -0
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +117 -41
- package/lib/constants.js +5 -0
- package/lib/dataStorage.js +232 -0
- package/lib/junit-adapter/java.js +27 -9
- package/lib/logger.js +319 -0
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +26 -39
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/testomatio.js +138 -33
- package/lib/reporter.js +11 -2
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/utils/utils.js +310 -0
- package/lib/xmlReader.js +132 -44
- package/package.json +9 -6
- package/lib/util.js +0 -184
package/lib/adapter/jest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const TestomatClient = require('../client');
|
|
2
|
-
const { STATUS } = require('../constants');
|
|
3
|
-
const { parseTest, ansiRegExp } = require('../
|
|
2
|
+
const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
|
|
3
|
+
const { parseTest, ansiRegExp, fileSystem } = require('../utils/utils');
|
|
4
4
|
|
|
5
5
|
class JestReporter {
|
|
6
6
|
constructor(globalConfig, options) {
|
|
@@ -11,6 +11,22 @@ class JestReporter {
|
|
|
11
11
|
this.client.createRun();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
static getIdOfCurrentlyRunningTest() {
|
|
15
|
+
if (!process.env.JEST_WORKER_ID) return null;
|
|
16
|
+
try {
|
|
17
|
+
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
18
|
+
// eslint-disable-next-line no-undef
|
|
19
|
+
if (expect && expect?.getState()?.currentTestName) return parseTest(expect?.getState()?.currentTestName);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
onRunStart() {
|
|
26
|
+
// clear tmp dir
|
|
27
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
onTestResult(test, testResult) {
|
|
15
31
|
if (!this.client) return;
|
|
16
32
|
|
|
@@ -26,12 +42,21 @@ class JestReporter {
|
|
|
26
42
|
steps = failureMessages[0];
|
|
27
43
|
}
|
|
28
44
|
const testId = parseTest(title);
|
|
45
|
+
let suite_title;
|
|
46
|
+
// this is test without a suite
|
|
47
|
+
if (result.fullName === result.title) {
|
|
48
|
+
suite_title = testResult.testFilePath.split('/').pop();
|
|
49
|
+
} else {
|
|
50
|
+
suite_title = result.fullName.replace(result.title, '');
|
|
51
|
+
}
|
|
52
|
+
|
|
29
53
|
const deducedStatus = status === 'pending' ? 'skipped' : status;
|
|
30
54
|
// In jest if test is not matched with test name pattern it is considered as skipped.
|
|
31
55
|
// So adding a check if it is skipped for real or because of test pattern
|
|
32
56
|
if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
|
|
33
57
|
this.client.addTestRun(deducedStatus, {
|
|
34
58
|
test_id: testId,
|
|
59
|
+
suite_title,
|
|
35
60
|
error,
|
|
36
61
|
steps,
|
|
37
62
|
title,
|
|
@@ -47,6 +72,9 @@ class JestReporter {
|
|
|
47
72
|
const { numFailedTests } = results;
|
|
48
73
|
const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
49
74
|
this.client.updateRunStatus(status);
|
|
75
|
+
|
|
76
|
+
// clear tmp dir
|
|
77
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
50
78
|
}
|
|
51
79
|
}
|
|
52
80
|
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -3,55 +3,54 @@ const Mocha = require('mocha');
|
|
|
3
3
|
const debug = require('debug')('@testomatio/reporter:adapter:mocha');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const TestomatClient = require('../client');
|
|
6
|
-
const { STATUS } = require('../constants');
|
|
7
|
-
const { parseTest, specificTestInfo } = require('../
|
|
8
|
-
const ArtifactStorage = require('../
|
|
6
|
+
const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
|
|
7
|
+
const { parseTest, specificTestInfo, fileSystem } = require('../utils/utils');
|
|
8
|
+
const ArtifactStorage = require('../_ArtifactStorageOld');
|
|
9
9
|
|
|
10
|
-
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } =
|
|
10
|
+
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } =
|
|
11
|
+
Mocha.Runner.constants;
|
|
11
12
|
|
|
12
13
|
function MochaReporter(runner, opts) {
|
|
13
14
|
Mocha.reporters.Base.call(this, runner);
|
|
14
|
-
let passes = 0;
|
|
15
|
+
let passes = 0;
|
|
16
|
+
let failures = 0;
|
|
17
|
+
let skipped = 0;
|
|
15
18
|
let artifactStore;
|
|
16
19
|
|
|
17
20
|
const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
|
|
18
21
|
|
|
19
|
-
if (!apiKey) {
|
|
20
|
-
debug('TESTOMATIO key is empty, ignoring reports');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
22
|
const client = new TestomatClient({ apiKey });
|
|
24
23
|
|
|
25
24
|
runner.on(EVENT_RUN_BEGIN, () => {
|
|
26
25
|
client.createRun();
|
|
27
26
|
|
|
28
|
-
const params = {
|
|
29
|
-
toFile: true
|
|
27
|
+
const params = {
|
|
28
|
+
toFile: true,
|
|
30
29
|
};
|
|
31
|
-
|
|
32
|
-
artifactStore = runner._workerReporter !== undefined
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
|
|
31
|
+
artifactStore = runner._workerReporter !== undefined ? new ArtifactStorage(params) : new ArtifactStorage();
|
|
32
|
+
|
|
33
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
35
34
|
});
|
|
36
35
|
|
|
37
|
-
runner.on(EVENT_TEST_PASS, async
|
|
36
|
+
runner.on(EVENT_TEST_PASS, async test => {
|
|
38
37
|
passes += 1;
|
|
39
38
|
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
40
39
|
const testId = parseTest(test.title);
|
|
41
40
|
|
|
42
|
-
const specificTest =
|
|
41
|
+
const specificTest = specificTestInfo(test);
|
|
43
42
|
const content = await artifactStore.artifactByTestName(specificTest);
|
|
44
43
|
|
|
45
44
|
debug(`test=${specificTest} content = `, content);
|
|
46
45
|
|
|
47
46
|
client.addTestRun(
|
|
48
|
-
STATUS.PASSED,
|
|
47
|
+
STATUS.PASSED,
|
|
49
48
|
{
|
|
50
49
|
test_id: testId,
|
|
51
50
|
title: test.title,
|
|
52
51
|
time: test.duration,
|
|
53
52
|
},
|
|
54
|
-
content
|
|
53
|
+
content,
|
|
55
54
|
);
|
|
56
55
|
});
|
|
57
56
|
|
|
@@ -66,24 +65,25 @@ function MochaReporter(runner, opts) {
|
|
|
66
65
|
});
|
|
67
66
|
});
|
|
68
67
|
|
|
69
|
-
runner.on(EVENT_TEST_FAIL, async(test, err) => {
|
|
68
|
+
runner.on(EVENT_TEST_FAIL, async (test, err) => {
|
|
70
69
|
failures += 1;
|
|
71
70
|
console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
|
|
72
71
|
const testId = parseTest(test.title);
|
|
73
72
|
|
|
74
|
-
const specificTest =
|
|
73
|
+
const specificTest = specificTestInfo(test);
|
|
75
74
|
const content = await artifactStore.artifactByTestName(specificTest);
|
|
76
75
|
|
|
77
76
|
debug(`fail test=${specificTest} content = `, content);
|
|
78
77
|
|
|
79
78
|
client.addTestRun(
|
|
80
|
-
STATUS.FAILED,
|
|
79
|
+
STATUS.FAILED,
|
|
80
|
+
{
|
|
81
81
|
error: err,
|
|
82
82
|
test_id: testId,
|
|
83
83
|
title: test.title,
|
|
84
84
|
time: test.duration,
|
|
85
|
-
},
|
|
86
|
-
content
|
|
85
|
+
},
|
|
86
|
+
content,
|
|
87
87
|
);
|
|
88
88
|
});
|
|
89
89
|
|
|
@@ -3,14 +3,14 @@ const crypto = require('crypto');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs');
|
|
6
|
-
const { APP_PREFIX, STATUS: Status } = require('../constants');
|
|
6
|
+
const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE } = require('../constants');
|
|
7
7
|
const TestomatioClient = require('../client');
|
|
8
8
|
const { isArtifactsEnabled } = require('../fileUploader');
|
|
9
|
-
const { parseTest } = require('../
|
|
9
|
+
const { parseTest, fileSystem } = require('../utils/utils');
|
|
10
10
|
|
|
11
11
|
const reportTestPromises = [];
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class PlaywrightReporter {
|
|
14
14
|
constructor(config = {}) {
|
|
15
15
|
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
16
16
|
|
|
@@ -18,11 +18,17 @@ class TestomatioReporter {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
onBegin(_config, suite) {
|
|
21
|
+
// clean data storage
|
|
22
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
21
23
|
if (!this.client) return;
|
|
22
24
|
this.suite = suite;
|
|
23
25
|
this.client.createRun();
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
// onTestBegin(test) {
|
|
29
|
+
// const testId = parseTest(test.title);
|
|
30
|
+
// }
|
|
31
|
+
|
|
26
32
|
onTestEnd(test, result) {
|
|
27
33
|
if (!this.client) return;
|
|
28
34
|
|
|
@@ -37,7 +43,9 @@ class TestomatioReporter {
|
|
|
37
43
|
for (const step of result.steps) {
|
|
38
44
|
appendStep(step, steps);
|
|
39
45
|
}
|
|
40
|
-
|
|
46
|
+
|
|
47
|
+
const logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
|
|
48
|
+
|
|
41
49
|
const reportTestPromise = this.client.addTestRun(checkStatus(result.status), {
|
|
42
50
|
error,
|
|
43
51
|
test_id: testId,
|
|
@@ -45,13 +53,14 @@ class TestomatioReporter {
|
|
|
45
53
|
title,
|
|
46
54
|
steps: steps.join('\n'),
|
|
47
55
|
time: duration,
|
|
56
|
+
stack: logs,
|
|
48
57
|
}).then(pipes => {
|
|
49
|
-
testId = pipes
|
|
58
|
+
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
50
59
|
|
|
51
60
|
this.uploads.push({
|
|
52
61
|
testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
|
|
53
|
-
})
|
|
54
|
-
})
|
|
62
|
+
});
|
|
63
|
+
});
|
|
55
64
|
|
|
56
65
|
reportTestPromises.push(reportTestPromise);
|
|
57
66
|
}
|
|
@@ -67,10 +76,9 @@ class TestomatioReporter {
|
|
|
67
76
|
const promises = [];
|
|
68
77
|
|
|
69
78
|
for (const upload of this.uploads) {
|
|
70
|
-
|
|
71
79
|
const { title, testId, suite_title } = upload;
|
|
72
80
|
|
|
73
|
-
const files = upload.files.map(
|
|
81
|
+
const files = upload.files.map(attachment => {
|
|
74
82
|
if (attachment.body) {
|
|
75
83
|
const fileName = tmpFile();
|
|
76
84
|
fs.writeFileSync(fileName, attachment.body);
|
|
@@ -78,7 +86,6 @@ class TestomatioReporter {
|
|
|
78
86
|
return { path: attachment.path, title, type: attachment.contentType };
|
|
79
87
|
});
|
|
80
88
|
|
|
81
|
-
|
|
82
89
|
promises.push(
|
|
83
90
|
this.client.addTestRun(undefined, {
|
|
84
91
|
test_id: testId,
|
|
@@ -124,4 +131,4 @@ function tmpFile(prefix = 'tmp.') {
|
|
|
124
131
|
return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
|
|
125
132
|
}
|
|
126
133
|
|
|
127
|
-
module.exports =
|
|
134
|
+
module.exports = PlaywrightReporter;
|
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 { parseTest } = require('../
|
|
4
|
+
const { parseTest } = require('../utils/utils');
|
|
5
5
|
|
|
6
6
|
class WebdriverReporter extends WDIOReporter {
|
|
7
7
|
constructor(options) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// const debug = require('debug')('@testomatio/reporter:logger');
|
|
2
|
+
const { DataStorage } = require('./dataStorage');
|
|
3
|
+
|
|
4
|
+
class ArtifactStorage {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.dataStorage = new DataStorage('artifact');
|
|
7
|
+
|
|
8
|
+
// singleton
|
|
9
|
+
if (!ArtifactStorage.instance) {
|
|
10
|
+
ArtifactStorage.instance = this;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
save(data, context = null) {
|
|
15
|
+
this.dataStorage.putData(data, context);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get(context) {
|
|
19
|
+
this.dataStorage.getData(context);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
ArtifactStorage.instance = null;
|
|
24
|
+
|
|
25
|
+
module.exports = new ArtifactStorage();
|
package/lib/bin/reportXml.js
CHANGED
package/lib/bin/startTest.js
CHANGED
|
@@ -13,9 +13,11 @@ program
|
|
|
13
13
|
.option('--launch', 'Start a new run and return its ID')
|
|
14
14
|
.option('--finish', 'Finish Run by its ID')
|
|
15
15
|
.option("--env-file <envfile>", "Load environment variables from env file")
|
|
16
|
-
.
|
|
16
|
+
.option("--filter <filter>", "Additional execution filter")
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
const { launch, finish, filter } = opts;
|
|
19
|
+
let { command } = opts;
|
|
17
20
|
|
|
18
|
-
const { command, launch, finish } = opts;
|
|
19
21
|
if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
|
|
20
22
|
|
|
21
23
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || process.env.TESTOMATIO;
|
|
@@ -51,6 +53,27 @@ program
|
|
|
51
53
|
|
|
52
54
|
let exitCode = 0;
|
|
53
55
|
|
|
56
|
+
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
57
|
+
|
|
58
|
+
if(filter) {
|
|
59
|
+
const [pipe, ...optsArray] = filter.split(":");
|
|
60
|
+
const pipeOptions = optsArray.join(":");
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const tests = await client.prepareRun({pipe, pipeOptions});
|
|
64
|
+
|
|
65
|
+
if(!tests || tests.length === 0) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const grep = ` --grep (${tests.join('|')})`;
|
|
70
|
+
command += grep;
|
|
71
|
+
}
|
|
72
|
+
catch(err) {
|
|
73
|
+
console.log(APP_PREFIX, err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
if (!command.split) {
|
|
55
78
|
process.exitCode = 255;
|
|
56
79
|
console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
|
|
@@ -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,6 +1,7 @@
|
|
|
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');
|
|
@@ -11,6 +12,7 @@ const pipesFactory = require('./pipe');
|
|
|
11
12
|
/**
|
|
12
13
|
* @typedef {import('../types').TestData} TestData
|
|
13
14
|
* @typedef {import('../types').RunStatus} RunStatus
|
|
15
|
+
* @typedef {import('../types').PipeResult} PipeResult
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
18
|
class Client {
|
|
@@ -20,57 +22,109 @@ class Client {
|
|
|
20
22
|
* @param {*} params
|
|
21
23
|
*/
|
|
22
24
|
constructor(params = {}) {
|
|
23
|
-
this.parallel = params.parallel;
|
|
24
25
|
const store = {};
|
|
25
26
|
this.uuid = randomUUID();
|
|
26
27
|
this.pipes = pipesFactory(params, store);
|
|
27
28
|
this.queue = Promise.resolve();
|
|
28
29
|
this.totalUploaded = 0;
|
|
29
30
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
31
|
+
this.executionList = Promise.resolve();
|
|
32
|
+
|
|
30
33
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
31
34
|
}
|
|
32
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Asynchronously prepares the execution list for running tests through various pipes.
|
|
38
|
+
* Each pipe in the client is checked for enablement,
|
|
39
|
+
* and if all pipes are disabled, the function returns a resolved Promise.
|
|
40
|
+
* Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
|
|
41
|
+
* The results are then filtered to remove any undefined values.
|
|
42
|
+
* If no valid results are found, the function returns undefined.
|
|
43
|
+
* Otherwise, it returns the first non-empty array from the filtered results.
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} params - The options for preparing the test execution list.
|
|
46
|
+
* @param {string} params.pipe - Name of the executed pipe.
|
|
47
|
+
* @param {string} params.pipeOptions - Filter option.
|
|
48
|
+
* @returns {Promise<any>} - A Promise that resolves to an
|
|
49
|
+
* array containing the prepared execution list,
|
|
50
|
+
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
51
|
+
*/
|
|
52
|
+
async prepareRun(params) {
|
|
53
|
+
const { pipe, pipeOptions } = params;
|
|
54
|
+
// all pipes disabled, skipping
|
|
55
|
+
if (!this.pipes.some(p => p.isEnabled)) {
|
|
56
|
+
return Promise.resolve();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
|
|
61
|
+
|
|
62
|
+
if (!filterPipe.isEnabled) {
|
|
63
|
+
// TODO:for the future for the another pipes
|
|
64
|
+
console.warn(
|
|
65
|
+
APP_PREFIX,
|
|
66
|
+
`At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const results = await Promise.all(this.pipes.map(async p =>
|
|
72
|
+
({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })
|
|
73
|
+
));
|
|
74
|
+
|
|
75
|
+
const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
|
|
76
|
+
|
|
77
|
+
if (!result || result.length === 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
debug('Execution tests list', result);
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error(APP_PREFIX, err);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
33
89
|
/**
|
|
34
90
|
* Used to create a new Test run
|
|
35
91
|
*
|
|
36
|
-
* @returns {Promise<
|
|
92
|
+
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
37
93
|
*/
|
|
38
94
|
createRun() {
|
|
95
|
+
debug('Creating run...');
|
|
39
96
|
// all pipes disabled, skipping
|
|
40
|
-
if (!this.pipes
|
|
41
|
-
|
|
42
|
-
const runParams = {
|
|
43
|
-
title: this.title,
|
|
44
|
-
parallel: this.parallel,
|
|
45
|
-
env: this.env,
|
|
46
|
-
};
|
|
97
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
47
98
|
|
|
48
99
|
global.testomatioArtifacts = [];
|
|
100
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
49
101
|
|
|
50
102
|
this.queue = this.queue
|
|
51
|
-
.then(() => Promise.all(this.pipes.map(p => p.createRun(
|
|
103
|
+
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
52
104
|
.catch(err => console.log(APP_PREFIX, err))
|
|
53
105
|
.then(() => undefined); // fixes return type
|
|
54
|
-
debug('Run', this.queue);
|
|
106
|
+
// debug('Run', this.queue);
|
|
55
107
|
return this.queue;
|
|
56
108
|
}
|
|
57
109
|
|
|
58
110
|
/**
|
|
59
111
|
* Updates test status and its data
|
|
60
|
-
*
|
|
112
|
+
*
|
|
61
113
|
* @param {string|undefined} status
|
|
62
114
|
* @param {TestData} [testData]
|
|
63
115
|
* @param {string[]} [storeArtifacts]
|
|
64
|
-
* @returns {Promise<
|
|
116
|
+
* @returns {Promise<PipeResult[]>}
|
|
65
117
|
*/
|
|
66
118
|
async addTestRun(status, testData, storeArtifacts = []) {
|
|
119
|
+
debug('Adding test run for test', testData?.test_id || 'unknown test');
|
|
67
120
|
// all pipes disabled, skipping
|
|
68
|
-
if (!this.pipes
|
|
121
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
69
122
|
|
|
70
|
-
if (!testData)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
123
|
+
if (!testData)
|
|
124
|
+
testData = {
|
|
125
|
+
title: 'Unknown test',
|
|
126
|
+
suite_title: 'Unknown suite',
|
|
127
|
+
};
|
|
74
128
|
|
|
75
129
|
const {
|
|
76
130
|
error = null,
|
|
@@ -99,13 +153,22 @@ class Client {
|
|
|
99
153
|
stack = this.formatSteps(stack, steps);
|
|
100
154
|
}
|
|
101
155
|
|
|
156
|
+
stack += testData.stack || '';
|
|
157
|
+
|
|
158
|
+
// in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
|
|
159
|
+
const logger = require('./logger');
|
|
160
|
+
const testLogs = logger.getLogs(test_id);
|
|
161
|
+
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
162
|
+
if (stack) stack += '\n\n';
|
|
163
|
+
stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
|
|
164
|
+
|
|
102
165
|
if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
|
|
103
|
-
debug(
|
|
166
|
+
debug('CLIENT storeArtifact', storeArtifacts);
|
|
104
167
|
files.push(...storeArtifacts);
|
|
105
168
|
}
|
|
106
169
|
|
|
107
170
|
if (Array.isArray(global.testomatioArtifacts)) {
|
|
108
|
-
debug(
|
|
171
|
+
debug('CLIENT global[testomatioArtifacts]', global.testomatioArtifacts);
|
|
109
172
|
files.push(...global.testomatioArtifacts);
|
|
110
173
|
global.testomatioArtifacts = [];
|
|
111
174
|
}
|
|
@@ -141,17 +204,19 @@ class Client {
|
|
|
141
204
|
artifacts,
|
|
142
205
|
};
|
|
143
206
|
|
|
144
|
-
this.queue = this.queue
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
207
|
+
this.queue = this.queue.then(() =>
|
|
208
|
+
Promise.all(
|
|
209
|
+
this.pipes.map(async p => {
|
|
210
|
+
try {
|
|
211
|
+
const result = await p.addTest(data);
|
|
212
|
+
return { pipe: p.toString(), result };
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.log(APP_PREFIX, p.toString(), err);
|
|
215
|
+
}
|
|
216
|
+
}),
|
|
217
|
+
),
|
|
218
|
+
);
|
|
219
|
+
|
|
155
220
|
return this.queue;
|
|
156
221
|
}
|
|
157
222
|
|
|
@@ -160,19 +225,20 @@ class Client {
|
|
|
160
225
|
* Updates the status of the current test run and finishes the run.
|
|
161
226
|
* @param {RunStatus} status - The status of the current test run. Must be one of "passed", "failed", or "finished"
|
|
162
227
|
* @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
|
|
163
|
-
* @returns {Promise<
|
|
228
|
+
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
164
229
|
*/
|
|
165
230
|
updateRunStatus(status, isParallel = false) {
|
|
231
|
+
debug('Updating run status...');
|
|
166
232
|
// all pipes disabled, skipping
|
|
167
|
-
if (!this.pipes
|
|
233
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
168
234
|
|
|
169
235
|
const runParams = { status, parallel: isParallel };
|
|
170
236
|
|
|
171
237
|
this.queue = this.queue
|
|
172
238
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
173
239
|
.then(() => {
|
|
174
|
-
debug(
|
|
175
|
-
|
|
240
|
+
debug('TOTAL uploaded files', this.totalUploaded);
|
|
241
|
+
|
|
176
242
|
if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
|
|
177
243
|
console.log(
|
|
178
244
|
APP_PREFIX,
|
|
@@ -205,17 +271,21 @@ class Client {
|
|
|
205
271
|
stack += '\n\n';
|
|
206
272
|
}
|
|
207
273
|
|
|
274
|
+
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
275
|
+
|
|
208
276
|
try {
|
|
277
|
+
let hasFrame = false;
|
|
209
278
|
const record = createCallsiteRecord({
|
|
210
279
|
forError: error,
|
|
280
|
+
isCallsiteFrame: frame => {
|
|
281
|
+
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
282
|
+
if (hasFrame) return false;
|
|
283
|
+
if (isNotInternalFrame(frame)) hasFrame = true;
|
|
284
|
+
return hasFrame;
|
|
285
|
+
}
|
|
211
286
|
});
|
|
212
287
|
if (record && !record.filename.startsWith('http')) {
|
|
213
|
-
stack += record.renderSync({
|
|
214
|
-
stackFilter: frame =>
|
|
215
|
-
frame.getFileName().indexOf(sep) > -1 &&
|
|
216
|
-
frame.getFileName().indexOf('node_modules') < 0 &&
|
|
217
|
-
frame.getFileName().indexOf('internal') < 0,
|
|
218
|
-
});
|
|
288
|
+
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
219
289
|
}
|
|
220
290
|
return stack;
|
|
221
291
|
} catch (e) {
|
|
@@ -224,4 +294,10 @@ class Client {
|
|
|
224
294
|
}
|
|
225
295
|
}
|
|
226
296
|
|
|
297
|
+
function isNotInternalFrame(frame) {
|
|
298
|
+
return frame.getFileName().includes(sep) &&
|
|
299
|
+
!frame.getFileName().includes('node_modules') &&
|
|
300
|
+
!frame.getFileName().includes('internal')
|
|
301
|
+
}
|
|
302
|
+
|
|
227
303
|
module.exports = Client;
|
package/lib/constants.js
CHANGED
|
@@ -3,6 +3,10 @@ const chalk = require('chalk');
|
|
|
3
3
|
const APP_PREFIX = chalk.gray('[TESTOMATIO]');
|
|
4
4
|
const TESTOMAT_ARTIFACT_SUFFIX = "testomatio_artifact_";
|
|
5
5
|
|
|
6
|
+
const TESTOMAT_TMP_STORAGE = {
|
|
7
|
+
mainDir: "testomatio_tmp",
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
const CSV_HEADERS = [
|
|
7
11
|
{ id: 'suite_title', title: 'Suite_title' },
|
|
8
12
|
{ id: 'title', title: 'Title' },
|
|
@@ -21,6 +25,7 @@ const STATUS = {
|
|
|
21
25
|
module.exports = {
|
|
22
26
|
APP_PREFIX,
|
|
23
27
|
TESTOMAT_ARTIFACT_SUFFIX,
|
|
28
|
+
TESTOMAT_TMP_STORAGE,
|
|
24
29
|
CSV_HEADERS,
|
|
25
30
|
STATUS,
|
|
26
31
|
}
|