@testomatio/reporter 1.4.1-beta-1 → 1.4.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/README.md +60 -57
- package/lib/adapter/codecept.js +86 -27
- package/lib/adapter/cucumber/current.js +17 -10
- package/lib/adapter/cucumber/legacy.js +5 -4
- 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 +48 -10
- package/lib/adapter/mocha.js +90 -10
- package/lib/adapter/playwright.js +68 -18
- package/lib/adapter/webdriver.js +47 -2
- package/lib/bin/reportXml.js +21 -16
- package/lib/bin/startTest.js +12 -12
- package/lib/client.js +112 -52
- package/lib/config.js +34 -0
- package/lib/constants.js +28 -1
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +66 -53
- 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 +11 -11
- 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 +35 -32
- package/lib/pipe/github.js +4 -3
- package/lib/pipe/gitlab.js +17 -10
- package/lib/pipe/html.js +361 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +230 -51
- package/lib/reporter-functions.js +12 -9
- package/lib/reporter.js +8 -12
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/{storages/key-value-storage.js → services/key-values.js} +19 -19
- package/lib/{storages → services}/logger.js +58 -37
- package/lib/template/emptyData.svg +23 -0
- package/lib/template/testomatio.hbs +1421 -0
- package/lib/utils/pipe_utils.js +73 -79
- package/lib/utils/utils.js +53 -40
- package/lib/xmlReader.js +113 -105
- package/package.json +13 -7
- package/lib/storages/artifact-storage.js +0 -70
- package/lib/storages/data-storage.js +0 -307
package/lib/adapter/jest.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
1
2
|
const TestomatClient = require('../client');
|
|
2
3
|
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
3
|
-
const {
|
|
4
|
+
const { getTestomatIdFromTestTitle, ansiRegExp, fileSystem } = require('../utils/utils');
|
|
5
|
+
const { services } = require('../services');
|
|
6
|
+
const debug = require('debug')('@testomatio/reporter:adapter-jest');
|
|
7
|
+
const path = require('path');
|
|
4
8
|
|
|
5
9
|
class JestReporter {
|
|
6
10
|
constructor(globalConfig, options) {
|
|
@@ -16,6 +20,19 @@ class JestReporter {
|
|
|
16
20
|
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
// start of test file (including beforeAll)
|
|
24
|
+
onTestStart(testFile) {
|
|
25
|
+
debug('Start running test file:', testFile.path);
|
|
26
|
+
services.setContext(testFile.path);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// start of the test (including beforeEach)
|
|
30
|
+
onTestCaseStart(test, testCase) {
|
|
31
|
+
debug('Start running test:', testCase.fullName);
|
|
32
|
+
services.setContext(testCase.fullName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// end of test file! (there is also onTestCaseResult listener)
|
|
19
36
|
onTestResult(test, testResult) {
|
|
20
37
|
if (!this.client) return;
|
|
21
38
|
|
|
@@ -30,14 +47,16 @@ class JestReporter {
|
|
|
30
47
|
error = new Error(errorMessage);
|
|
31
48
|
steps = failureMessages[0];
|
|
32
49
|
}
|
|
33
|
-
const testId =
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
const testId = getTestomatIdFromTestTitle(title);
|
|
51
|
+
|
|
52
|
+
// suite titles from most outer to most inner, separated by space
|
|
53
|
+
let fullSuiteTitle = testResult.ancestorTitles?.join(' ');
|
|
54
|
+
// if no suite titles, use file name
|
|
55
|
+
if (!fullSuiteTitle && testResult.testFilePath) fullSuiteTitle = path.basename(testResult.testFilePath);
|
|
56
|
+
|
|
57
|
+
const logs = getTestLogs(result);
|
|
58
|
+
const artifacts = services.artifacts.get(result.fullName);
|
|
59
|
+
const keyValues = services.keyValues.get(result.fullName);
|
|
41
60
|
|
|
42
61
|
const deducedStatus = status === 'pending' ? 'skipped' : status;
|
|
43
62
|
// In jest if test is not matched with test name pattern it is considered as skipped.
|
|
@@ -45,11 +64,14 @@ class JestReporter {
|
|
|
45
64
|
if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
|
|
46
65
|
this.client.addTestRun(deducedStatus, {
|
|
47
66
|
test_id: testId,
|
|
48
|
-
suite_title,
|
|
67
|
+
suite_title: fullSuiteTitle,
|
|
49
68
|
error,
|
|
50
69
|
steps,
|
|
51
70
|
title,
|
|
52
71
|
time: duration,
|
|
72
|
+
logs,
|
|
73
|
+
manuallyAttachedArtifacts: artifacts,
|
|
74
|
+
meta: keyValues,
|
|
53
75
|
});
|
|
54
76
|
}
|
|
55
77
|
}
|
|
@@ -64,4 +86,20 @@ class JestReporter {
|
|
|
64
86
|
}
|
|
65
87
|
}
|
|
66
88
|
|
|
89
|
+
function getTestLogs(testResult) {
|
|
90
|
+
const suiteLogsArr = services.logger.getLogs(testResult.testFilePath);
|
|
91
|
+
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
92
|
+
const testLogsArr = services.logger.getLogs(testResult.fullName);
|
|
93
|
+
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
94
|
+
|
|
95
|
+
let logs = '';
|
|
96
|
+
if (suiteLogs) {
|
|
97
|
+
logs += `${chalk.bold('\t--- Suite ---')}\n${suiteLogs}`;
|
|
98
|
+
}
|
|
99
|
+
if (testLogs) {
|
|
100
|
+
logs += `\n${chalk.bold('\t--- Test ---')}\n${testLogs}`;
|
|
101
|
+
}
|
|
102
|
+
return logs;
|
|
103
|
+
}
|
|
104
|
+
|
|
67
105
|
module.exports = JestReporter;
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -3,10 +3,21 @@ const Mocha = require('mocha');
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const TestomatClient = require('../client');
|
|
5
5
|
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
6
|
-
const {
|
|
6
|
+
const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
const { services } = require('../services');
|
|
7
9
|
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
+
const {
|
|
11
|
+
EVENT_RUN_BEGIN,
|
|
12
|
+
EVENT_RUN_END,
|
|
13
|
+
EVENT_TEST_FAIL,
|
|
14
|
+
EVENT_TEST_PASS,
|
|
15
|
+
EVENT_TEST_PENDING,
|
|
16
|
+
EVENT_SUITE_BEGIN,
|
|
17
|
+
EVENT_SUITE_END,
|
|
18
|
+
EVENT_TEST_BEGIN,
|
|
19
|
+
EVENT_TEST_END,
|
|
20
|
+
} = Mocha.Runner.constants;
|
|
10
21
|
|
|
11
22
|
function MochaReporter(runner, opts) {
|
|
12
23
|
Mocha.reporters.Base.call(this, runner);
|
|
@@ -15,7 +26,7 @@ function MochaReporter(runner, opts) {
|
|
|
15
26
|
let skipped = 0;
|
|
16
27
|
// let artifactStore;
|
|
17
28
|
|
|
18
|
-
const apiKey = opts?.reporterOptions?.apiKey ||
|
|
29
|
+
const apiKey = opts?.reporterOptions?.apiKey || config.TESTOMATIO;
|
|
19
30
|
|
|
20
31
|
const client = new TestomatClient({ apiKey });
|
|
21
32
|
|
|
@@ -25,24 +36,54 @@ function MochaReporter(runner, opts) {
|
|
|
25
36
|
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
26
37
|
});
|
|
27
38
|
|
|
39
|
+
runner.on(EVENT_SUITE_BEGIN, async suite => {
|
|
40
|
+
services.setContext(suite.fullTitle());
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
runner.on(EVENT_SUITE_END, async () => {
|
|
44
|
+
services.setContext(null);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
runner.on(EVENT_TEST_BEGIN, async test => {
|
|
48
|
+
services.setContext(test.fullTitle());
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
runner.on(EVENT_TEST_END, async () => {
|
|
52
|
+
services.setContext(null);
|
|
53
|
+
});
|
|
54
|
+
|
|
28
55
|
runner.on(EVENT_TEST_PASS, async test => {
|
|
29
56
|
passes += 1;
|
|
57
|
+
|
|
30
58
|
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
31
|
-
const testId =
|
|
59
|
+
const testId = getTestomatIdFromTestTitle(test.title);
|
|
60
|
+
|
|
61
|
+
const logs = getTestLogs(test);
|
|
62
|
+
const artifacts = services.artifacts.get(test.fullTitle());
|
|
63
|
+
const keyValues = services.keyValues.get(test.fullTitle());
|
|
32
64
|
|
|
33
65
|
client.addTestRun(STATUS.PASSED, {
|
|
34
66
|
test_id: testId,
|
|
35
|
-
|
|
67
|
+
suite_title: getSuiteTitle(test),
|
|
68
|
+
title: getTestName(test),
|
|
69
|
+
code: test.body.toString(),
|
|
70
|
+
file: getFile(test),
|
|
36
71
|
time: test.duration,
|
|
72
|
+
logs,
|
|
73
|
+
manuallyAttachedArtifacts: artifacts,
|
|
74
|
+
meta: keyValues,
|
|
37
75
|
});
|
|
38
76
|
});
|
|
39
77
|
|
|
40
78
|
runner.on(EVENT_TEST_PENDING, test => {
|
|
41
79
|
skipped += 1;
|
|
42
80
|
console.log('skip: %s', test.fullTitle());
|
|
43
|
-
const testId =
|
|
81
|
+
const testId = getTestomatIdFromTestTitle(test.title);
|
|
44
82
|
client.addTestRun(STATUS.SKIPPED, {
|
|
45
|
-
title: test
|
|
83
|
+
title: getTestName(test),
|
|
84
|
+
suite_title: getSuiteTitle(test),
|
|
85
|
+
code: test.body.toString(),
|
|
86
|
+
file: getFile(test),
|
|
46
87
|
test_id: testId,
|
|
47
88
|
time: test.duration,
|
|
48
89
|
});
|
|
@@ -51,13 +92,19 @@ function MochaReporter(runner, opts) {
|
|
|
51
92
|
runner.on(EVENT_TEST_FAIL, async (test, err) => {
|
|
52
93
|
failures += 1;
|
|
53
94
|
console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
|
|
54
|
-
const testId =
|
|
95
|
+
const testId = getTestomatIdFromTestTitle(test.title);
|
|
96
|
+
|
|
97
|
+
const logs = getTestLogs(test);
|
|
55
98
|
|
|
56
99
|
client.addTestRun(STATUS.FAILED, {
|
|
57
100
|
error: err,
|
|
101
|
+
suite_title: getSuiteTitle(test),
|
|
102
|
+
file: getFile(test),
|
|
58
103
|
test_id: testId,
|
|
59
|
-
title: test
|
|
104
|
+
title: getTestName(test),
|
|
105
|
+
code: test.body.toString(),
|
|
60
106
|
time: test.duration,
|
|
107
|
+
logs,
|
|
61
108
|
});
|
|
62
109
|
});
|
|
63
110
|
|
|
@@ -68,7 +115,40 @@ function MochaReporter(runner, opts) {
|
|
|
68
115
|
});
|
|
69
116
|
}
|
|
70
117
|
|
|
118
|
+
function getTestLogs(test) {
|
|
119
|
+
const suiteLogsArr = services.logger.getLogs(test.parent.fullTitle());
|
|
120
|
+
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
121
|
+
const testLogsArr = services.logger.getLogs(test.fullTitle());
|
|
122
|
+
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
123
|
+
|
|
124
|
+
let logs = '';
|
|
125
|
+
if (suiteLogs) {
|
|
126
|
+
logs += `${chalk.bold('\t--- BeforeSuite ---')}\n${suiteLogs}`;
|
|
127
|
+
}
|
|
128
|
+
if (testLogs) {
|
|
129
|
+
logs += `\n${chalk.bold('\t--- Test ---')}\n${testLogs}`;
|
|
130
|
+
}
|
|
131
|
+
return logs;
|
|
132
|
+
}
|
|
133
|
+
|
|
71
134
|
// To have this reporter "extend" a built-in reporter uncomment the following line:
|
|
72
135
|
Mocha.utils.inherits(MochaReporter, Mocha.reporters.Spec);
|
|
73
136
|
|
|
74
137
|
module.exports = MochaReporter;
|
|
138
|
+
|
|
139
|
+
function getSuiteTitle(test, pathArr = []) {
|
|
140
|
+
if (test.parent.parent) getSuiteTitle(test.parent, pathArr);
|
|
141
|
+
|
|
142
|
+
pathArr.push(test.parent.title);
|
|
143
|
+
|
|
144
|
+
return pathArr.filter(t => !!t)[0];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getFile(test) {
|
|
148
|
+
return test.parent.file?.replace(process.cwd(), '');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getTestName(test) {
|
|
152
|
+
if (process.env.TESTOMATIO_CREATE === 'fulltitle') return test.fullTitle();
|
|
153
|
+
return test.title;
|
|
154
|
+
}
|
|
@@ -6,7 +6,10 @@ const fs = require('fs');
|
|
|
6
6
|
const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
7
7
|
const TestomatioClient = require('../client');
|
|
8
8
|
const { isArtifactsEnabled } = require('../fileUploader');
|
|
9
|
-
const {
|
|
9
|
+
const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
|
|
10
|
+
// const debug = require('debug')('@testomatio/reporter:adapter:playwright');
|
|
11
|
+
const { services } = require('../services');
|
|
12
|
+
const { dataStorage } = require('../data-storage');
|
|
10
13
|
|
|
11
14
|
const reportTestPromises = [];
|
|
12
15
|
|
|
@@ -17,39 +20,43 @@ class PlaywrightReporter {
|
|
|
17
20
|
this.uploads = [];
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
onBegin(
|
|
23
|
+
onBegin(config, suite) {
|
|
21
24
|
// clean data storage
|
|
22
25
|
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
23
26
|
if (!this.client) return;
|
|
24
27
|
this.suite = suite;
|
|
28
|
+
this.config = config;
|
|
25
29
|
this.client.createRun();
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} */
|
|
32
|
+
onTestBegin(testInfo) {
|
|
33
|
+
const fullTestTitle = getTestContextName(testInfo);
|
|
34
|
+
dataStorage.setContext(fullTestTitle);
|
|
35
|
+
}
|
|
33
36
|
|
|
34
37
|
onTestEnd(test, result) {
|
|
35
38
|
if (!this.client) return;
|
|
36
39
|
|
|
37
|
-
let testId = parseTest(test.title);
|
|
38
|
-
|
|
39
40
|
const { title } = test;
|
|
41
|
+
|
|
42
|
+
let testId = getTestomatIdFromTestTitle(`${title} ${test.tags?.join(' ')}`);
|
|
43
|
+
|
|
40
44
|
const { error, duration } = result;
|
|
41
45
|
|
|
42
|
-
const suite_title = test.parent ? test.parent
|
|
46
|
+
const suite_title = test.parent ? test.parent?.title : path.basename(test?.location?.file);
|
|
43
47
|
|
|
44
48
|
const steps = [];
|
|
45
49
|
for (const step of result.steps) {
|
|
46
50
|
appendStep(step, steps);
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
const fullTestTitle = getTestContextName(test);
|
|
49
54
|
let logs = '';
|
|
50
55
|
if (result.stderr.length || result.stdout.length) {
|
|
51
56
|
logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
|
|
52
57
|
}
|
|
58
|
+
const manuallyAttachedArtifacts = services.artifacts.get(fullTestTitle);
|
|
59
|
+
const keyValues = services.keyValues.get(fullTestTitle);
|
|
53
60
|
|
|
54
61
|
const reportTestPromise = this.client
|
|
55
62
|
.addTestRun(checkStatus(result.status), {
|
|
@@ -59,7 +66,10 @@ class PlaywrightReporter {
|
|
|
59
66
|
title,
|
|
60
67
|
steps: steps.join('\n'),
|
|
61
68
|
time: duration,
|
|
62
|
-
|
|
69
|
+
logs,
|
|
70
|
+
manuallyAttachedArtifacts,
|
|
71
|
+
meta: keyValues,
|
|
72
|
+
file: test.location?.file,
|
|
63
73
|
})
|
|
64
74
|
.then(pipes => {
|
|
65
75
|
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
@@ -69,6 +79,7 @@ class PlaywrightReporter {
|
|
|
69
79
|
title,
|
|
70
80
|
suite_title,
|
|
71
81
|
files: result.attachments.filter(a => a.body || a.path),
|
|
82
|
+
file: test.location?.file,
|
|
72
83
|
});
|
|
73
84
|
// remove empty uploads
|
|
74
85
|
this.uploads = this.uploads.filter(upload => upload.files.length);
|
|
@@ -77,6 +88,22 @@ class PlaywrightReporter {
|
|
|
77
88
|
reportTestPromises.push(reportTestPromise);
|
|
78
89
|
}
|
|
79
90
|
|
|
91
|
+
#getArtifactPath(artifact) {
|
|
92
|
+
if (artifact.path) {
|
|
93
|
+
if (path.isAbsolute(artifact.path)) return artifact.path;
|
|
94
|
+
|
|
95
|
+
return path.join(this.config.outputDir || this.config.projects[0].outputDir, artifact.path);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (artifact.body) {
|
|
99
|
+
const fileName = tmpFile();
|
|
100
|
+
fs.writeFileSync(fileName, artifact.body);
|
|
101
|
+
return fileName;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
80
107
|
async onEnd(result) {
|
|
81
108
|
if (!this.client) return;
|
|
82
109
|
|
|
@@ -90,13 +117,11 @@ class PlaywrightReporter {
|
|
|
90
117
|
for (const upload of this.uploads) {
|
|
91
118
|
const { title, testId, suite_title } = upload;
|
|
92
119
|
|
|
93
|
-
const files = upload.files.map(attachment => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return { path: attachment.path, title, type: attachment.contentType };
|
|
99
|
-
});
|
|
120
|
+
const files = upload.files.map(attachment => ({
|
|
121
|
+
path: this.#getArtifactPath(attachment),
|
|
122
|
+
title,
|
|
123
|
+
type: attachment.contentType,
|
|
124
|
+
}));
|
|
100
125
|
|
|
101
126
|
promises.push(
|
|
102
127
|
this.client.addTestRun(undefined, {
|
|
@@ -104,6 +129,7 @@ class PlaywrightReporter {
|
|
|
104
129
|
title,
|
|
105
130
|
suite_title,
|
|
106
131
|
files,
|
|
132
|
+
file: upload.file,
|
|
107
133
|
}),
|
|
108
134
|
);
|
|
109
135
|
}
|
|
@@ -143,4 +169,28 @@ function tmpFile(prefix = 'tmp.') {
|
|
|
143
169
|
return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
|
|
144
170
|
}
|
|
145
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Returns filename + test title
|
|
174
|
+
* @param {*} test - testInfo object from Playwright
|
|
175
|
+
* @returns
|
|
176
|
+
*/
|
|
177
|
+
function getTestContextName(test) {
|
|
178
|
+
return `${test._requireFile || ''}_${test.title}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function initPlaywrightForStorage() {
|
|
182
|
+
try {
|
|
183
|
+
// @ts-ignore-next-line
|
|
184
|
+
// eslint-disable-next-line import/no-unresolved
|
|
185
|
+
const { test } = require('@playwright/test');
|
|
186
|
+
// eslint-disable-next-line no-empty-pattern
|
|
187
|
+
test.beforeEach(async ({}, testInfo) => {
|
|
188
|
+
global.testomatioTestTitle = `${testInfo.file || ''}_${testInfo.title}`;
|
|
189
|
+
});
|
|
190
|
+
} catch (e) {
|
|
191
|
+
// ignore
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
146
195
|
module.exports = PlaywrightReporter;
|
|
196
|
+
module.exports.initPlaywrightForStorage = initPlaywrightForStorage;
|
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,22 +1,22 @@
|
|
|
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';
|
|
@@ -24,7 +24,7 @@ program
|
|
|
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
|
+
debug('Loading env file: %s', opts.envFile);
|
|
28
28
|
require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
|
|
29
29
|
}
|
|
30
30
|
if (javaTests === true) javaTests = 'src/test/java';
|
|
@@ -38,16 +38,21 @@ program
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
for (const file of files) {
|
|
41
|
-
console.log(APP_PREFIX
|
|
41
|
+
console.log(APP_PREFIX, `Parsed ${file}`);
|
|
42
42
|
runReader.parse(file);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
let timeoutTimer;
|
|
46
46
|
if (opts.timelimit) {
|
|
47
|
-
timeoutTimer = setTimeout(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
);
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
try {
|
|
@@ -57,7 +62,7 @@ program
|
|
|
57
62
|
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
if (timeoutTimer) clearTimeout(timeoutTimer)
|
|
65
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
61
66
|
});
|
|
62
67
|
|
|
63
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,15 +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
|
-
.option(
|
|
17
|
-
.action(async
|
|
16
|
+
.option('--env-file <envfile>', 'Load environment variables from env file')
|
|
17
|
+
.option('--filter <filter>', 'Additional execution filter')
|
|
18
|
+
.action(async opts => {
|
|
18
19
|
const { launch, finish, filter } = opts;
|
|
19
20
|
let { command } = opts;
|
|
20
21
|
|
|
21
22
|
if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
|
|
22
23
|
|
|
23
|
-
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] ||
|
|
24
|
+
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
24
25
|
const title = process.env.TESTOMATIO_TITLE;
|
|
25
26
|
|
|
26
27
|
if (launch) {
|
|
@@ -61,21 +62,20 @@ program
|
|
|
61
62
|
|
|
62
63
|
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
63
64
|
|
|
64
|
-
if(filter) {
|
|
65
|
-
const [pipe, ...optsArray] = filter.split(
|
|
66
|
-
const pipeOptions = optsArray.join(
|
|
65
|
+
if (filter) {
|
|
66
|
+
const [pipe, ...optsArray] = filter.split(':');
|
|
67
|
+
const pipeOptions = optsArray.join(':');
|
|
67
68
|
|
|
68
|
-
try {
|
|
69
|
-
const tests = await client.prepareRun({pipe, pipeOptions});
|
|
69
|
+
try {
|
|
70
|
+
const tests = await client.prepareRun({ pipe, pipeOptions });
|
|
70
71
|
|
|
71
|
-
if(!tests || tests.length === 0) {
|
|
72
|
+
if (!tests || tests.length === 0) {
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
const grep = ` --grep (${tests.join('|')})`;
|
|
76
77
|
command += grep;
|
|
77
|
-
}
|
|
78
|
-
catch(err) {
|
|
78
|
+
} catch (err) {
|
|
79
79
|
console.log(APP_PREFIX, err);
|
|
80
80
|
}
|
|
81
81
|
}
|