@testomatio/reporter 1.3.5-beta → 1.4.0-beta-wdio-bdd
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 +96 -35
- package/lib/adapter/cucumber/current.js +18 -11
- package/lib/adapter/cucumber/legacy.js +6 -5
- package/lib/adapter/cucumber.js +2 -2
- package/lib/adapter/cypress-plugin/index.js +52 -25
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +49 -16
- package/lib/adapter/mocha.js +103 -51
- package/lib/adapter/playwright.js +95 -33
- package/lib/adapter/webdriver.js +46 -1
- package/lib/bin/reportXml.js +22 -16
- package/lib/bin/startTest.js +27 -6
- package/lib/client.js +179 -53
- package/lib/config.js +34 -0
- package/lib/constants.js +32 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +135 -54
- package/lib/junit-adapter/adapter.js +0 -2
- package/lib/junit-adapter/csharp.js +3 -4
- package/lib/junit-adapter/index.js +3 -3
- package/lib/junit-adapter/java.js +35 -17
- package/lib/junit-adapter/javascript.js +1 -2
- package/lib/junit-adapter/python.js +12 -14
- package/lib/junit-adapter/ruby.js +1 -2
- package/lib/pipe/csv.js +5 -3
- package/lib/pipe/github.js +9 -19
- package/lib/pipe/gitlab.js +22 -26
- package/lib/pipe/html.js +354 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +257 -53
- package/lib/reporter-functions.js +46 -0
- package/lib/reporter.js +11 -9
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/services/key-values.js +58 -0
- package/lib/services/logger.js +311 -0
- package/lib/template/testomatio.hbs +1236 -0
- package/lib/utils/pipe_utils.js +129 -0
- package/lib/{util.js → utils/utils.js} +145 -15
- package/lib/xmlReader.js +211 -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
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
const { STATUS } = require('../../constants');
|
|
2
|
-
const { parseTest, parseSuite } = require('../../
|
|
2
|
+
const { parseTest, parseSuite } = require('../../utils/utils');
|
|
3
3
|
const TestomatClient = require('../../client');
|
|
4
|
+
const config = require('../../config');
|
|
4
5
|
|
|
5
6
|
const testomatioReporter = on => {
|
|
6
|
-
if (!
|
|
7
|
+
if (!config.TESTOMATIO) {
|
|
7
8
|
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
8
|
-
return
|
|
9
|
+
return;
|
|
9
10
|
}
|
|
10
|
-
const client = new TestomatClient({ apiKey:
|
|
11
|
+
const client = new TestomatClient({ apiKey: config.TESTOMATIO });
|
|
11
12
|
|
|
12
|
-
on('before:run', async
|
|
13
|
+
on('before:run', async run => {
|
|
14
|
+
// TODO: looks like client.env does not exist
|
|
13
15
|
if (!client.env) {
|
|
14
|
-
client.env = `${run.browser.displayName},${run.system.osName}
|
|
16
|
+
client.env = `${run.browser.displayName},${run.system.osName}`;
|
|
15
17
|
}
|
|
16
18
|
await client.createRun();
|
|
17
19
|
});
|
|
@@ -25,8 +27,9 @@ const testomatioReporter = on => {
|
|
|
25
27
|
const lastAttemptIndex = test.attempts.length - 1;
|
|
26
28
|
const latestAttempt = test.attempts[lastAttemptIndex];
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
+
// latestAttempt.duration && latestAttempt.error were available in adapters version up to 13 JFYI
|
|
31
|
+
const time = latestAttempt.duration || latestAttempt.wallClockDuration || test.duration;
|
|
32
|
+
let error = latestAttempt.error;
|
|
30
33
|
|
|
31
34
|
let title = test.title.pop();
|
|
32
35
|
const examples = title.match(/\(example (#\d+)\)/);
|
|
@@ -39,35 +42,59 @@ const testomatioReporter = on => {
|
|
|
39
42
|
const testId = parseTest(title);
|
|
40
43
|
const suiteId = parseSuite(suiteTitle);
|
|
41
44
|
|
|
42
|
-
if (error) {
|
|
43
|
-
error
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
45
|
+
if (!error && test.displayError) {
|
|
46
|
+
error = { message: test.displayError };
|
|
47
|
+
error.inspect = function () {
|
|
48
|
+
// eslint-disable-line func-names
|
|
49
|
+
return this.message;
|
|
50
|
+
};
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const formattedError = error
|
|
54
|
+
? {
|
|
55
|
+
message: error.message,
|
|
56
|
+
inspect:
|
|
57
|
+
error.inspect ||
|
|
58
|
+
function () {
|
|
59
|
+
return this.message;
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
: '';
|
|
63
|
+
|
|
64
|
+
const screenshots = Array.isArray(results.screenshots)
|
|
65
|
+
? results.screenshots
|
|
66
|
+
.filter(screenshot => screenshot?.path && screenshot?.path.includes(title) && screenshot?.takenAt)
|
|
67
|
+
.map(screenshot => screenshot.path)
|
|
68
|
+
: [];
|
|
55
69
|
|
|
56
70
|
const files = [...videos, ...screenshots];
|
|
57
71
|
|
|
58
72
|
let state;
|
|
59
73
|
switch (test.state) {
|
|
60
|
-
case 'passed':
|
|
61
|
-
|
|
74
|
+
case 'passed':
|
|
75
|
+
state = STATUS.PASSED;
|
|
76
|
+
break;
|
|
77
|
+
case 'failed':
|
|
78
|
+
state = STATUS.FAILED;
|
|
79
|
+
break;
|
|
62
80
|
case 'skipped':
|
|
63
|
-
case 'pending':
|
|
81
|
+
case 'pending':
|
|
64
82
|
default:
|
|
65
83
|
state = STATUS.SKIPPED;
|
|
66
84
|
}
|
|
67
85
|
|
|
68
|
-
addSpecTestsPromises.push(
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
addSpecTestsPromises.push(
|
|
87
|
+
client.addTestRun(state, {
|
|
88
|
+
title,
|
|
89
|
+
time,
|
|
90
|
+
example,
|
|
91
|
+
error: formattedError,
|
|
92
|
+
files,
|
|
93
|
+
suite_title: suiteTitle,
|
|
94
|
+
test_id: testId,
|
|
95
|
+
suite_id: suiteId,
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
71
98
|
}
|
|
72
99
|
|
|
73
100
|
await Promise.all(addSpecTestsPromises);
|
package/lib/adapter/jasmine.js
CHANGED
package/lib/adapter/jest.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
1
2
|
const TestomatClient = require('../client');
|
|
2
|
-
const { STATUS,
|
|
3
|
-
const { parseTest, ansiRegExp, fileSystem } = require('../
|
|
3
|
+
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
4
|
+
const { parseTest, 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) {
|
|
@@ -11,22 +15,24 @@ class JestReporter {
|
|
|
11
15
|
this.client.createRun();
|
|
12
16
|
}
|
|
13
17
|
|
|
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
18
|
onRunStart() {
|
|
26
19
|
// clear tmp dir
|
|
27
|
-
fileSystem.clearDir(
|
|
20
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
21
|
+
}
|
|
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);
|
|
28
33
|
}
|
|
29
34
|
|
|
35
|
+
// end of test file! (there is also onTestCaseResult listener)
|
|
30
36
|
onTestResult(test, testResult) {
|
|
31
37
|
if (!this.client) return;
|
|
32
38
|
|
|
@@ -42,16 +48,30 @@ class JestReporter {
|
|
|
42
48
|
steps = failureMessages[0];
|
|
43
49
|
}
|
|
44
50
|
const testId = parseTest(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);
|
|
60
|
+
|
|
45
61
|
const deducedStatus = status === 'pending' ? 'skipped' : status;
|
|
46
62
|
// In jest if test is not matched with test name pattern it is considered as skipped.
|
|
47
63
|
// So adding a check if it is skipped for real or because of test pattern
|
|
48
64
|
if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
|
|
49
65
|
this.client.addTestRun(deducedStatus, {
|
|
50
66
|
test_id: testId,
|
|
67
|
+
suite_title: fullSuiteTitle,
|
|
51
68
|
error,
|
|
52
69
|
steps,
|
|
53
70
|
title,
|
|
54
71
|
time: duration,
|
|
72
|
+
logs,
|
|
73
|
+
manuallyAttachedArtifacts: artifacts,
|
|
74
|
+
meta: keyValues,
|
|
55
75
|
});
|
|
56
76
|
}
|
|
57
77
|
}
|
|
@@ -63,10 +83,23 @@ class JestReporter {
|
|
|
63
83
|
const { numFailedTests } = results;
|
|
64
84
|
const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
65
85
|
this.client.updateRunStatus(status);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
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}`;
|
|
69
101
|
}
|
|
102
|
+
return logs;
|
|
70
103
|
}
|
|
71
104
|
|
|
72
105
|
module.exports = JestReporter;
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -1,58 +1,78 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
2
|
const Mocha = require('mocha');
|
|
3
|
-
const debug = require('debug')('@testomatio/reporter:adapter:mocha');
|
|
4
3
|
const chalk = require('chalk');
|
|
5
4
|
const TestomatClient = require('../client');
|
|
6
|
-
const { STATUS } = require('../constants');
|
|
7
|
-
const { parseTest,
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
6
|
+
const { parseTest, fileSystem } = require('../utils/utils');
|
|
7
|
+
const config = require('../config');
|
|
8
|
+
const { services } = require('../services');
|
|
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;
|
|
11
21
|
|
|
12
22
|
function MochaReporter(runner, opts) {
|
|
13
23
|
Mocha.reporters.Base.call(this, runner);
|
|
14
|
-
let passes = 0;
|
|
15
|
-
let
|
|
24
|
+
let passes = 0;
|
|
25
|
+
let failures = 0;
|
|
26
|
+
let skipped = 0;
|
|
27
|
+
// let artifactStore;
|
|
16
28
|
|
|
17
|
-
const apiKey = opts?.reporterOptions?.apiKey ||
|
|
29
|
+
const apiKey = opts?.reporterOptions?.apiKey || config.TESTOMATIO;
|
|
18
30
|
|
|
19
|
-
if (!apiKey) {
|
|
20
|
-
debug('TESTOMATIO key is empty, ignoring reports');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
31
|
const client = new TestomatClient({ apiKey });
|
|
24
32
|
|
|
25
33
|
runner.on(EVENT_RUN_BEGIN, () => {
|
|
26
34
|
client.createRun();
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
toFile: true
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
artifactStore = runner._workerReporter !== undefined
|
|
33
|
-
? new ArtifactStorage(params)
|
|
34
|
-
: new ArtifactStorage();
|
|
36
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
35
37
|
});
|
|
36
38
|
|
|
37
|
-
runner.on(
|
|
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
|
+
|
|
55
|
+
runner.on(EVENT_TEST_PASS, async test => {
|
|
38
56
|
passes += 1;
|
|
57
|
+
|
|
39
58
|
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
40
59
|
const testId = parseTest(test.title);
|
|
41
60
|
|
|
42
|
-
const
|
|
43
|
-
const
|
|
61
|
+
const logs = getTestLogs(test);
|
|
62
|
+
const artifacts = services.artifacts.get(test.fullTitle());
|
|
63
|
+
const keyValues = services.keyValues.get(test.fullTitle());
|
|
44
64
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
65
|
+
client.addTestRun(STATUS.PASSED, {
|
|
66
|
+
test_id: testId,
|
|
67
|
+
suite_title: getSuiteTitle(test),
|
|
68
|
+
title: getTestName(test),
|
|
69
|
+
code: test.body.toString(),
|
|
70
|
+
file: getFile(test),
|
|
71
|
+
time: test.duration,
|
|
72
|
+
logs,
|
|
73
|
+
manuallyAttachedArtifacts: artifacts,
|
|
74
|
+
meta: keyValues,
|
|
75
|
+
});
|
|
56
76
|
});
|
|
57
77
|
|
|
58
78
|
runner.on(EVENT_TEST_PENDING, test => {
|
|
@@ -60,43 +80,75 @@ function MochaReporter(runner, opts) {
|
|
|
60
80
|
console.log('skip: %s', test.fullTitle());
|
|
61
81
|
const testId = parseTest(test.title);
|
|
62
82
|
client.addTestRun(STATUS.SKIPPED, {
|
|
63
|
-
title: test
|
|
83
|
+
title: getTestName(test),
|
|
84
|
+
suite_title: getSuiteTitle(test),
|
|
85
|
+
code: test.body.toString(),
|
|
86
|
+
file: getFile(test),
|
|
64
87
|
test_id: testId,
|
|
65
88
|
time: test.duration,
|
|
66
89
|
});
|
|
67
90
|
});
|
|
68
91
|
|
|
69
|
-
runner.on(EVENT_TEST_FAIL, async(test, err) => {
|
|
92
|
+
runner.on(EVENT_TEST_FAIL, async (test, err) => {
|
|
70
93
|
failures += 1;
|
|
71
94
|
console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
|
|
72
95
|
const testId = parseTest(test.title);
|
|
73
96
|
|
|
74
|
-
const
|
|
75
|
-
const content = await artifactStore.artifactByTestName(specificTest);
|
|
97
|
+
const logs = getTestLogs(test);
|
|
76
98
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
);
|
|
99
|
+
client.addTestRun(STATUS.FAILED, {
|
|
100
|
+
error: err,
|
|
101
|
+
suite_title: getSuiteTitle(test),
|
|
102
|
+
file: getFile(test),
|
|
103
|
+
test_id: testId,
|
|
104
|
+
title: getTestName(test),
|
|
105
|
+
code: test.body.toString(),
|
|
106
|
+
time: test.duration,
|
|
107
|
+
logs,
|
|
108
|
+
});
|
|
88
109
|
});
|
|
89
110
|
|
|
90
111
|
runner.on(EVENT_RUN_END, () => {
|
|
91
112
|
const status = failures === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
92
113
|
console.log(chalk.bold(status), `${passes} passed, ${failures} failed, ${skipped} skipped`);
|
|
93
114
|
client.updateRunStatus(status);
|
|
94
|
-
|
|
95
|
-
artifactStore.cleanup();
|
|
96
115
|
});
|
|
97
116
|
}
|
|
98
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
|
+
|
|
99
134
|
// To have this reporter "extend" a built-in reporter uncomment the following line:
|
|
100
135
|
Mocha.utils.inherits(MochaReporter, Mocha.reporters.Spec);
|
|
101
136
|
|
|
102
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
|
+
}
|
|
@@ -3,10 +3,13 @@ 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,
|
|
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 { parseTest, fileSystem } = require('../
|
|
9
|
+
const { parseTest, 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,54 +20,90 @@ class PlaywrightReporter {
|
|
|
17
20
|
this.uploads = [];
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
onBegin(
|
|
23
|
+
onBegin(config, suite) {
|
|
21
24
|
// clean data storage
|
|
22
|
-
fileSystem.clearDir(
|
|
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
|
-
|
|
32
|
+
onTestBegin(testInfo) {
|
|
33
|
+
const fullTestTitle = getTestContextName(testInfo);
|
|
34
|
+
dataStorage.setContext(fullTestTitle);
|
|
35
|
+
}
|
|
31
36
|
|
|
32
37
|
onTestEnd(test, result) {
|
|
33
38
|
if (!this.client) return;
|
|
34
39
|
|
|
35
|
-
let testId = parseTest(test.title);
|
|
36
|
-
|
|
37
40
|
const { title } = test;
|
|
41
|
+
|
|
42
|
+
let testId = parseTest(title);
|
|
43
|
+
|
|
38
44
|
const { error, duration } = result;
|
|
39
45
|
|
|
40
|
-
const suite_title = test.parent ? test.parent
|
|
46
|
+
const suite_title = test.parent ? test.parent?.title : path.basename(test?.location?.file);
|
|
41
47
|
|
|
42
48
|
const steps = [];
|
|
43
49
|
for (const step of result.steps) {
|
|
44
50
|
appendStep(step, steps);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
const fullTestTitle = getTestContextName(test);
|
|
54
|
+
let logs = '';
|
|
55
|
+
if (result.stderr.length || result.stdout.length) {
|
|
56
|
+
logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
|
|
57
|
+
}
|
|
58
|
+
const manuallyAttachedArtifacts = services.artifacts.get(fullTestTitle);
|
|
59
|
+
const keyValues = services.keyValues.get(fullTestTitle);
|
|
60
|
+
|
|
61
|
+
const reportTestPromise = this.client
|
|
62
|
+
.addTestRun(checkStatus(result.status), {
|
|
63
|
+
error,
|
|
64
|
+
test_id: testId,
|
|
65
|
+
suite_title,
|
|
66
|
+
title,
|
|
67
|
+
steps: steps.join('\n'),
|
|
68
|
+
time: duration,
|
|
69
|
+
logs,
|
|
70
|
+
manuallyAttachedArtifacts,
|
|
71
|
+
meta: keyValues,
|
|
72
|
+
file: test.location?.file,
|
|
73
|
+
})
|
|
74
|
+
.then(pipes => {
|
|
75
|
+
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
76
|
+
|
|
77
|
+
this.uploads.push({
|
|
78
|
+
testId,
|
|
79
|
+
title,
|
|
80
|
+
suite_title,
|
|
81
|
+
files: result.attachments.filter(a => a.body || a.path),
|
|
82
|
+
file: test.location?.file,
|
|
83
|
+
});
|
|
84
|
+
// remove empty uploads
|
|
85
|
+
this.uploads = this.uploads.filter(upload => upload.files.length);
|
|
62
86
|
});
|
|
63
|
-
});
|
|
64
87
|
|
|
65
88
|
reportTestPromises.push(reportTestPromise);
|
|
66
89
|
}
|
|
67
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
|
+
|
|
68
107
|
async onEnd(result) {
|
|
69
108
|
if (!this.client) return;
|
|
70
109
|
|
|
@@ -78,13 +117,11 @@ class PlaywrightReporter {
|
|
|
78
117
|
for (const upload of this.uploads) {
|
|
79
118
|
const { title, testId, suite_title } = upload;
|
|
80
119
|
|
|
81
|
-
const files = upload.files.map(attachment => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return { path: attachment.path, title, type: attachment.contentType };
|
|
87
|
-
});
|
|
120
|
+
const files = upload.files.map(attachment => ({
|
|
121
|
+
path: this.#getArtifactPath(attachment),
|
|
122
|
+
title,
|
|
123
|
+
type: attachment.contentType,
|
|
124
|
+
}));
|
|
88
125
|
|
|
89
126
|
promises.push(
|
|
90
127
|
this.client.addTestRun(undefined, {
|
|
@@ -92,6 +129,7 @@ class PlaywrightReporter {
|
|
|
92
129
|
title,
|
|
93
130
|
suite_title,
|
|
94
131
|
files,
|
|
132
|
+
file: upload.file,
|
|
95
133
|
}),
|
|
96
134
|
);
|
|
97
135
|
}
|
|
@@ -131,4 +169,28 @@ function tmpFile(prefix = 'tmp.') {
|
|
|
131
169
|
return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
|
|
132
170
|
}
|
|
133
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
|
+
|
|
134
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 { parseTest } = require('../
|
|
4
|
+
const { parseTest } = require('../utils/utils');
|
|
5
5
|
|
|
6
6
|
class WebdriverReporter extends WDIOReporter {
|
|
7
7
|
constructor(options) {
|
|
@@ -31,6 +31,18 @@ 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
|
|
|
@@ -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 = parseTest(title) || parseTest(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;
|