@testomatio/reporter 1.2.4-beta → 1.3.0-ignore-stack-for-passed.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 +61 -54
- package/lib/adapter/codecept.js +96 -35
- package/lib/adapter/cucumber/current.js +19 -12
- package/lib/adapter/cucumber/legacy.js +8 -7
- package/lib/adapter/cucumber.js +2 -2
- package/lib/adapter/cypress-plugin/index.js +52 -25
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +49 -16
- package/lib/adapter/mocha.js +103 -51
- package/lib/adapter/playwright.js +80 -27
- package/lib/adapter/webdriver.js +1 -1
- package/lib/bin/reportXml.js +14 -13
- package/lib/bin/startTest.js +27 -6
- package/lib/client.js +192 -69
- package/lib/config.js +34 -0
- package/lib/constants.js +19 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +128 -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 +35 -17
- package/lib/junit-adapter/javascript.js +1 -2
- package/lib/junit-adapter/python.js +12 -14
- package/lib/junit-adapter/ruby.js +1 -2
- package/lib/pipe/csv.js +5 -3
- package/lib/pipe/github.js +27 -39
- package/lib/pipe/gitlab.js +20 -24
- package/lib/pipe/html.js +317 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +189 -56
- package/lib/reporter-functions.js +46 -0
- package/lib/reporter.js +11 -9
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/services/key-values.js +58 -0
- package/lib/services/logger.js +311 -0
- package/lib/template/template-draft.hbs +249 -0
- package/lib/template/testomatio.hbs +388 -0
- package/lib/utils/pipe_utils.js +128 -0
- package/lib/{util.js → utils/utils.js} +144 -12
- package/lib/xmlReader.js +211 -122
- package/package.json +18 -7
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -241
- package/lib/helpers.js +0 -34
- package/lib/logger.js +0 -293
|
@@ -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,54 +3,61 @@ 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('../
|
|
10
|
-
const
|
|
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');
|
|
11
13
|
|
|
12
14
|
const reportTestPromises = [];
|
|
13
15
|
|
|
14
|
-
class
|
|
16
|
+
class PlaywrightReporter {
|
|
15
17
|
constructor(config = {}) {
|
|
16
18
|
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
17
19
|
|
|
18
20
|
this.uploads = [];
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
onBegin(
|
|
23
|
+
onBegin(config, suite) {
|
|
24
|
+
// clean data storage
|
|
25
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
22
26
|
if (!this.client) return;
|
|
23
27
|
this.suite = suite;
|
|
28
|
+
this.config = config;
|
|
24
29
|
this.client.createRun();
|
|
25
|
-
|
|
26
|
-
// for data storage
|
|
27
|
-
// fileSystem.createDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
onTestBegin(
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
// global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
34
|
-
// TestomatioReporter.currentlyRunningTestId = testId;
|
|
32
|
+
onTestBegin(testInfo) {
|
|
33
|
+
const fullTestTitle = getTestContextName(testInfo);
|
|
34
|
+
dataStorage.setContext(fullTestTitle);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
onTestEnd(test, result) {
|
|
38
|
-
// reset currently running test id
|
|
39
|
-
// setCurrentlyRunningTestId('');
|
|
40
38
|
if (!this.client) return;
|
|
41
39
|
|
|
42
|
-
let testId = parseTest(test.title);
|
|
43
|
-
|
|
44
40
|
const { title } = test;
|
|
41
|
+
|
|
42
|
+
let testId = parseTest(title);
|
|
43
|
+
|
|
45
44
|
const { error, duration } = result;
|
|
46
45
|
|
|
47
|
-
const suite_title = test.parent ? test.parent
|
|
46
|
+
const suite_title = test.parent ? test.parent?.title : path.basename(test?.location?.file);
|
|
48
47
|
|
|
49
48
|
const steps = [];
|
|
50
49
|
for (const step of result.steps) {
|
|
51
50
|
appendStep(step, steps);
|
|
52
51
|
}
|
|
53
52
|
|
|
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
|
+
|
|
54
61
|
const reportTestPromise = this.client
|
|
55
62
|
.addTestRun(checkStatus(result.status), {
|
|
56
63
|
error,
|
|
@@ -59,21 +66,44 @@ class TestomatioReporter {
|
|
|
59
66
|
title,
|
|
60
67
|
steps: steps.join('\n'),
|
|
61
68
|
time: duration,
|
|
69
|
+
logs,
|
|
70
|
+
manuallyAttachedArtifacts,
|
|
71
|
+
meta: keyValues,
|
|
72
|
+
file: test.location?.file,
|
|
62
73
|
})
|
|
63
74
|
.then(pipes => {
|
|
64
|
-
testId = pipes
|
|
75
|
+
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
65
76
|
|
|
66
77
|
this.uploads.push({
|
|
67
78
|
testId,
|
|
68
79
|
title,
|
|
69
80
|
suite_title,
|
|
70
81
|
files: result.attachments.filter(a => a.body || a.path),
|
|
82
|
+
file: test.location?.file,
|
|
71
83
|
});
|
|
84
|
+
// remove empty uploads
|
|
85
|
+
this.uploads = this.uploads.filter(upload => upload.files.length);
|
|
72
86
|
});
|
|
73
87
|
|
|
74
88
|
reportTestPromises.push(reportTestPromise);
|
|
75
89
|
}
|
|
76
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
|
+
|
|
77
107
|
async onEnd(result) {
|
|
78
108
|
if (!this.client) return;
|
|
79
109
|
|
|
@@ -87,13 +117,11 @@ class TestomatioReporter {
|
|
|
87
117
|
for (const upload of this.uploads) {
|
|
88
118
|
const { title, testId, suite_title } = upload;
|
|
89
119
|
|
|
90
|
-
const files = upload.files.map(attachment => {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return { path: attachment.path, title, type: attachment.contentType };
|
|
96
|
-
});
|
|
120
|
+
const files = upload.files.map(attachment => ({
|
|
121
|
+
path: this.#getArtifactPath(attachment),
|
|
122
|
+
title,
|
|
123
|
+
type: attachment.contentType,
|
|
124
|
+
}));
|
|
97
125
|
|
|
98
126
|
promises.push(
|
|
99
127
|
this.client.addTestRun(undefined, {
|
|
@@ -101,6 +129,7 @@ class TestomatioReporter {
|
|
|
101
129
|
title,
|
|
102
130
|
suite_title,
|
|
103
131
|
files,
|
|
132
|
+
file: upload.file,
|
|
104
133
|
}),
|
|
105
134
|
);
|
|
106
135
|
}
|
|
@@ -140,4 +169,28 @@ function tmpFile(prefix = 'tmp.') {
|
|
|
140
169
|
return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
|
|
141
170
|
}
|
|
142
171
|
|
|
143
|
-
|
|
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
|
+
|
|
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) {
|