@testomatio/reporter 1.1.1-beta-codecept-logger → 1.1.1-file-upload.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 +11 -17
- package/lib/adapter/codecept.js +26 -14
- package/lib/adapter/cucumber/current.js +15 -8
- package/lib/adapter/cucumber/legacy.js +2 -1
- package/lib/adapter/cypress-plugin/index.js +46 -26
- package/lib/adapter/jest.js +46 -8
- package/lib/adapter/mocha.js +45 -11
- package/lib/adapter/playwright.js +42 -16
- package/lib/bin/startTest.js +2 -1
- package/lib/client.js +30 -16
- package/lib/config.js +5 -0
- package/lib/{storages/data-storage.js → data-storage.js} +76 -43
- package/lib/fileUploader.js +9 -7
- package/lib/junit-adapter/index.js +3 -2
- package/lib/pipe/testomatio.js +4 -2
- package/lib/reporter-functions.js +10 -18
- package/lib/reporter.js +8 -13
- package/lib/{storages/artifact-storage.js → services/artifacts.js} +9 -31
- package/lib/services/index.js +13 -0
- package/lib/{storages/key-value-storage.js → services/key-values.js} +10 -25
- package/lib/{storages → services}/logger.js +13 -30
- package/lib/utils/utils.js +25 -2
- package/lib/xmlReader.js +106 -104
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -65,23 +65,16 @@ yarn add @testomatio/reporter --dev
|
|
|
65
65
|
|
|
66
66
|
### 1️⃣ Attach Reporter to the Test Runner
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
* #### [JUnit](./docs/junit.md#junit)
|
|
79
|
-
* #### [NUnit](./docs/junit.md#nunit)
|
|
80
|
-
* #### [PyTest](./docs/junit.md#pytest)
|
|
81
|
-
* #### [PHPUnit](./docs/junit.md#phpunit)
|
|
82
|
-
* #### [Protractor](./docs/frameworks.md#protractor)
|
|
83
|
-
|
|
84
|
-
or any [other via JUnit](./docs/junit.md) report....
|
|
68
|
+
| | | |
|
|
69
|
+
|--|--|--|
|
|
70
|
+
| [Playwright](./docs/frameworks.md#playwright) | [CodeceptJS](./docs/frameworks.md#CodeceptJS) | [Cypress](./docs/frameworks.md#Cypress) |
|
|
71
|
+
| [Jest](./docs/frameworks.md#Jest) | [Mocha](./docs/frameworks.md#Mocha) | [WebDriverIO](./docs/frameworks.md#WebDriverIO) |
|
|
72
|
+
| [TestCafe](./docs/frameworks.md#TestCafe) | [Detox](./docs/frameworks.md#Detox) | [Codeception](https://github.com/testomatio/php-reporter) |
|
|
73
|
+
| [Newman (Postman)](./docs/frameworks.md#Newman) | [JUnit](./docs/junit.md#junit) | [NUnit](./docs/junit.md#nunit) |
|
|
74
|
+
| [PyTest](./docs/junit.md#pytest) | [PHPUnit](./docs/junit.md#phpunit) | [Protractor](./docs/frameworks.md#protractor) |
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
or **any [other via JUnit](./docs/junit.md)** report....
|
|
85
78
|
|
|
86
79
|
### 2️⃣ Configure Reports
|
|
87
80
|
|
|
@@ -153,3 +146,4 @@ To print all reporter logs of a specific pipe:
|
|
|
153
146
|
```
|
|
154
147
|
DEBUG=@testomatio/reporter:pipe:github
|
|
155
148
|
```
|
|
149
|
+
|
package/lib/adapter/codecept.js
CHANGED
|
@@ -4,7 +4,7 @@ const TestomatClient = require('../client');
|
|
|
4
4
|
const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
5
5
|
const upload = require('../fileUploader');
|
|
6
6
|
const { parseTest: getIdFromTestTitle, fileSystem } = require('../utils/utils');
|
|
7
|
-
const {
|
|
7
|
+
const { services } = require('../services');
|
|
8
8
|
|
|
9
9
|
if (!global.codeceptjs) {
|
|
10
10
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
@@ -52,8 +52,6 @@ function CodeceptReporter(config) {
|
|
|
52
52
|
|
|
53
53
|
recorder.startUnlessRunning();
|
|
54
54
|
|
|
55
|
-
global.testomatioRunningEnvironment = 'codeceptjs';
|
|
56
|
-
|
|
57
55
|
// Listening to events
|
|
58
56
|
event.dispatcher.on(event.all.before, () => {
|
|
59
57
|
// clear tmp dir
|
|
@@ -75,11 +73,11 @@ function CodeceptReporter(config) {
|
|
|
75
73
|
hookSteps = [];
|
|
76
74
|
global.testomatioDataStore.steps = [];
|
|
77
75
|
|
|
78
|
-
|
|
76
|
+
services.setContext(suite.fullTitle());
|
|
79
77
|
});
|
|
80
78
|
|
|
81
79
|
event.dispatcher.on(event.suite.after, () => {
|
|
82
|
-
|
|
80
|
+
services.setContext(null);
|
|
83
81
|
});
|
|
84
82
|
|
|
85
83
|
event.dispatcher.on(event.hook.started, () => {
|
|
@@ -87,13 +85,17 @@ function CodeceptReporter(config) {
|
|
|
87
85
|
});
|
|
88
86
|
|
|
89
87
|
event.dispatcher.on(event.hook.passed, () => {
|
|
90
|
-
if (suiteHookRunning)
|
|
91
|
-
|
|
88
|
+
if (suiteHookRunning) {
|
|
89
|
+
hookSteps.push(...global.testomatioDataStore.steps);
|
|
90
|
+
services.setContext(null);
|
|
91
|
+
}
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
event.dispatcher.on(event.hook.failed, () => {
|
|
95
|
-
if (suiteHookRunning)
|
|
96
|
-
|
|
95
|
+
if (suiteHookRunning) {
|
|
96
|
+
hookSteps.push(...global.testomatioDataStore.steps);
|
|
97
|
+
services.setContext(null);
|
|
98
|
+
}
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
event.dispatcher.on(event.test.before, test => {
|
|
@@ -111,11 +113,12 @@ function CodeceptReporter(config) {
|
|
|
111
113
|
// reset steps
|
|
112
114
|
global.testomatioDataStore.steps = [];
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
services.setContext(test.fullTitle());
|
|
115
117
|
});
|
|
116
118
|
|
|
117
119
|
event.dispatcher.on(event.test.started, test => {
|
|
118
|
-
|
|
120
|
+
services.setContext(test.fullTitle());
|
|
121
|
+
|
|
119
122
|
testTimeMap[test.id] = Date.now();
|
|
120
123
|
if (global.testomatioDataStore) global.testomatioDataStore.currentlyRunningTestId = getIdFromTestTitle(test.title);
|
|
121
124
|
// start logging
|
|
@@ -144,6 +147,9 @@ function CodeceptReporter(config) {
|
|
|
144
147
|
const testObj = getTestAndMessage(title);
|
|
145
148
|
|
|
146
149
|
const logs = getTestLogs(test);
|
|
150
|
+
const manuallyAttachedArtifacts = services.artifacts.get(test.fullTitle());
|
|
151
|
+
const keyValues = services.keyValues.get(test.fullTitle());
|
|
152
|
+
services.setContext(null);
|
|
147
153
|
|
|
148
154
|
client.addTestRun(STATUS.PASSED, {
|
|
149
155
|
...stripExampleFromTitle(title),
|
|
@@ -153,6 +159,8 @@ function CodeceptReporter(config) {
|
|
|
153
159
|
steps: global.testomatioDataStore.steps.join('\n') || null,
|
|
154
160
|
test_id: testId,
|
|
155
161
|
logs,
|
|
162
|
+
manuallyAttachedArtifacts,
|
|
163
|
+
meta: keyValues,
|
|
156
164
|
});
|
|
157
165
|
// output.stop();
|
|
158
166
|
});
|
|
@@ -195,7 +203,9 @@ function CodeceptReporter(config) {
|
|
|
195
203
|
// todo: video must be uploaded later....
|
|
196
204
|
|
|
197
205
|
const logs = getTestLogs(test);
|
|
198
|
-
|
|
206
|
+
const manuallyAttachedArtifacts = services.artifacts.get(test.fullTitle());
|
|
207
|
+
const keyValues = services.keyValues.get(test.fullTitle());
|
|
208
|
+
services.setContext(null);
|
|
199
209
|
|
|
200
210
|
const reportTestPromise = client
|
|
201
211
|
.addTestRun(STATUS.FAILED, {
|
|
@@ -208,6 +218,8 @@ function CodeceptReporter(config) {
|
|
|
208
218
|
files,
|
|
209
219
|
steps: global.testomatioDataStore?.steps?.join('\n') || null,
|
|
210
220
|
logs,
|
|
221
|
+
manuallyAttachedArtifacts,
|
|
222
|
+
meta: keyValues,
|
|
211
223
|
})
|
|
212
224
|
.then(pipes => {
|
|
213
225
|
testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
@@ -351,9 +363,9 @@ function repeat(num) {
|
|
|
351
363
|
|
|
352
364
|
// TODO: think about moving to some common utils
|
|
353
365
|
function getTestLogs(test) {
|
|
354
|
-
const suiteLogsArr = logger.getLogs(test.parent.fullTitle());
|
|
366
|
+
const suiteLogsArr = services.logger.getLogs(test.parent.fullTitle());
|
|
355
367
|
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
356
|
-
const testLogsArr = logger.getLogs(test.fullTitle());
|
|
368
|
+
const testLogsArr = services.logger.getLogs(test.fullTitle());
|
|
357
369
|
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
358
370
|
|
|
359
371
|
let logs = '';
|
|
@@ -4,8 +4,9 @@ const chalk = require('chalk');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../../constants');
|
|
6
6
|
const TestomatClient = require('../../client');
|
|
7
|
-
const logger = require('../../storages/logger');
|
|
8
7
|
const { parseTest, fileSystem } = require('../../utils/utils');
|
|
8
|
+
const { TESTOMATIO } = require('../../config');
|
|
9
|
+
const { services } = require('../../services');
|
|
9
10
|
|
|
10
11
|
const { GherkinDocumentParser, PickleParser } = formatterHelpers;
|
|
11
12
|
const { getGherkinScenarioLocationMap, getGherkinStepMap } = GherkinDocumentParser;
|
|
@@ -29,10 +30,8 @@ class CucumberReporter extends Formatter {
|
|
|
29
30
|
this.failures = [];
|
|
30
31
|
this.cases = [];
|
|
31
32
|
|
|
32
|
-
this.client = new TestomatClient({ apiKey: options.apiKey ||
|
|
33
|
+
this.client = new TestomatClient({ apiKey: options.apiKey || TESTOMATIO });
|
|
33
34
|
this.status = STATUS.PASSED;
|
|
34
|
-
|
|
35
|
-
global.testomatioRunningEnvironment = 'cucumber:current';
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
parseEnvelope(envelope) {
|
|
@@ -49,9 +48,10 @@ class CucumberReporter extends Formatter {
|
|
|
49
48
|
|
|
50
49
|
onTestCaseStarted(testCaseStarted) {
|
|
51
50
|
const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseStarted.id);
|
|
52
|
-
const testId = getTestId(testCaseAttempt.pickle);
|
|
53
51
|
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
54
|
-
|
|
52
|
+
|
|
53
|
+
const testTitle = testCaseAttempt.pickle.name + testCaseAttempt.pickle.uri;
|
|
54
|
+
services.setContext(testTitle);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
onTestCaseFinished(testCaseFinished) {
|
|
@@ -109,7 +109,10 @@ class CucumberReporter extends Formatter {
|
|
|
109
109
|
|
|
110
110
|
if (!this.client) return;
|
|
111
111
|
|
|
112
|
-
const
|
|
112
|
+
const testTitle = testCaseAttempt.pickle.name + testCaseAttempt.pickle.uri;
|
|
113
|
+
const logs = services.logger.getLogs(testTitle).join('\n');
|
|
114
|
+
const artifacts = services.artifacts.get(testTitle);
|
|
115
|
+
const keyValues = services.keyValues.get(testTitle);
|
|
113
116
|
|
|
114
117
|
this.client.addTestRun(status, {
|
|
115
118
|
// error: testCaseAttempt.worstTestStepResult.message,
|
|
@@ -119,11 +122,15 @@ class CucumberReporter extends Formatter {
|
|
|
119
122
|
.join('\n')
|
|
120
123
|
.trim(),
|
|
121
124
|
example: { ...example },
|
|
122
|
-
|
|
125
|
+
logs,
|
|
126
|
+
manuallyAttachedArtifacts: artifacts,
|
|
127
|
+
meta: keyValues,
|
|
123
128
|
title: scenario,
|
|
124
129
|
test_id: testId,
|
|
125
130
|
time,
|
|
126
131
|
});
|
|
132
|
+
|
|
133
|
+
services.setContext(null);
|
|
127
134
|
}
|
|
128
135
|
|
|
129
136
|
onTestRunFinished(envelope) {
|
|
@@ -4,6 +4,7 @@ const chalk = require('chalk');
|
|
|
4
4
|
const { parseTest, fileSystem } = require('../../utils/utils');
|
|
5
5
|
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../../constants');
|
|
6
6
|
const TestomatClient = require('../../client');
|
|
7
|
+
const { TESTOMATIO } = require('../../config');
|
|
7
8
|
|
|
8
9
|
const createTestomatFormatter = apiKey => {
|
|
9
10
|
if (!apiKey || apiKey === '') {
|
|
@@ -151,4 +152,4 @@ const createTestomatFormatter = apiKey => {
|
|
|
151
152
|
};
|
|
152
153
|
};
|
|
153
154
|
|
|
154
|
-
module.exports = createTestomatFormatter(
|
|
155
|
+
module.exports = createTestomatFormatter(TESTOMATIO);
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
const { STATUS } = require('../../constants');
|
|
2
2
|
const { parseTest, parseSuite } = require('../../utils/utils');
|
|
3
3
|
const TestomatClient = require('../../client');
|
|
4
|
+
const { TESTOMATIO } = require('../../config');
|
|
4
5
|
|
|
5
6
|
const testomatioReporter = on => {
|
|
6
|
-
if (!
|
|
7
|
+
if (!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: TESTOMATIO });
|
|
11
12
|
|
|
12
|
-
on('before:run', async
|
|
13
|
+
on('before:run', async run => {
|
|
13
14
|
// TODO: looks like client.env does not exist
|
|
14
15
|
if (!client.env) {
|
|
15
|
-
client.env = `${run.browser.displayName},${run.system.osName}
|
|
16
|
+
client.env = `${run.browser.displayName},${run.system.osName}`;
|
|
16
17
|
}
|
|
17
18
|
await client.createRun();
|
|
18
19
|
});
|
|
@@ -26,8 +27,9 @@ const testomatioReporter = on => {
|
|
|
26
27
|
const lastAttemptIndex = test.attempts.length - 1;
|
|
27
28
|
const latestAttempt = test.attempts[lastAttemptIndex];
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
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;
|
|
31
33
|
|
|
32
34
|
let title = test.title.pop();
|
|
33
35
|
const examples = title.match(/\(example (#\d+)\)/);
|
|
@@ -40,23 +42,28 @@ const testomatioReporter = on => {
|
|
|
40
42
|
const testId = parseTest(title);
|
|
41
43
|
const suiteId = parseSuite(suiteTitle);
|
|
42
44
|
|
|
43
|
-
if (error) {
|
|
44
|
-
error
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
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
|
+
};
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
const formattedError = error
|
|
54
|
+
? {
|
|
55
|
+
message: error.message,
|
|
56
|
+
inspect:
|
|
57
|
+
error.inspect ||
|
|
58
|
+
function () {
|
|
59
|
+
return this.message;
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
: '';
|
|
63
|
+
|
|
52
64
|
const screenshots = Array.isArray(results.screenshots)
|
|
53
65
|
? results.screenshots
|
|
54
|
-
.filter(
|
|
55
|
-
screenshot =>
|
|
56
|
-
screenshot?.path &&
|
|
57
|
-
screenshot?.path.includes(title) &&
|
|
58
|
-
screenshot?.takenAt
|
|
59
|
-
)
|
|
66
|
+
.filter(screenshot => screenshot?.path && screenshot?.path.includes(title) && screenshot?.takenAt)
|
|
60
67
|
.map(screenshot => screenshot.path)
|
|
61
68
|
: [];
|
|
62
69
|
|
|
@@ -64,17 +71,30 @@ const testomatioReporter = on => {
|
|
|
64
71
|
|
|
65
72
|
let state;
|
|
66
73
|
switch (test.state) {
|
|
67
|
-
case 'passed':
|
|
68
|
-
|
|
74
|
+
case 'passed':
|
|
75
|
+
state = STATUS.PASSED;
|
|
76
|
+
break;
|
|
77
|
+
case 'failed':
|
|
78
|
+
state = STATUS.FAILED;
|
|
79
|
+
break;
|
|
69
80
|
case 'skipped':
|
|
70
|
-
case 'pending':
|
|
81
|
+
case 'pending':
|
|
71
82
|
default:
|
|
72
83
|
state = STATUS.SKIPPED;
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
addSpecTestsPromises.push(
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
);
|
|
78
98
|
}
|
|
79
99
|
|
|
80
100
|
await Promise.all(addSpecTestsPromises);
|
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
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) {
|
|
@@ -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
|
|
|
@@ -31,13 +48,15 @@ class JestReporter {
|
|
|
31
48
|
steps = failureMessages[0];
|
|
32
49
|
}
|
|
33
50
|
const testId = parseTest(title);
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(testResult.fullName);
|
|
59
|
+
const keyValues = services.keyValues.get(testResult.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
|
@@ -4,7 +4,8 @@ const chalk = require('chalk');
|
|
|
4
4
|
const TestomatClient = require('../client');
|
|
5
5
|
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
6
6
|
const { parseTest, fileSystem } = require('../utils/utils');
|
|
7
|
-
const {
|
|
7
|
+
const { TESTOMATIO } = require('../config');
|
|
8
|
+
const { services } = require('../services');
|
|
8
9
|
|
|
9
10
|
const {
|
|
10
11
|
EVENT_RUN_BEGIN,
|
|
@@ -25,7 +26,7 @@ function MochaReporter(runner, opts) {
|
|
|
25
26
|
let skipped = 0;
|
|
26
27
|
// let artifactStore;
|
|
27
28
|
|
|
28
|
-
const apiKey = opts?.reporterOptions?.apiKey ||
|
|
29
|
+
const apiKey = opts?.reporterOptions?.apiKey || TESTOMATIO;
|
|
29
30
|
|
|
30
31
|
const client = new TestomatClient({ apiKey });
|
|
31
32
|
|
|
@@ -36,33 +37,41 @@ function MochaReporter(runner, opts) {
|
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
runner.on(EVENT_SUITE_BEGIN, async suite => {
|
|
39
|
-
|
|
40
|
+
services.setContext(suite.fullTitle());
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
runner.on(EVENT_SUITE_END, async () => {
|
|
43
|
-
|
|
44
|
+
services.setContext(null);
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
runner.on(EVENT_TEST_BEGIN, async test => {
|
|
47
|
-
|
|
48
|
+
services.setContext(test.fullTitle());
|
|
48
49
|
});
|
|
49
50
|
|
|
50
51
|
runner.on(EVENT_TEST_END, async () => {
|
|
51
|
-
|
|
52
|
+
services.setContext(null);
|
|
52
53
|
});
|
|
53
54
|
|
|
54
55
|
runner.on(EVENT_TEST_PASS, async test => {
|
|
55
56
|
passes += 1;
|
|
57
|
+
|
|
56
58
|
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
57
59
|
const testId = parseTest(test.title);
|
|
58
60
|
|
|
59
61
|
const logs = getTestLogs(test);
|
|
62
|
+
const artifacts = services.artifacts.get(test.fullTitle());
|
|
63
|
+
const keyValues = services.keyValues.get(test.fullTitle());
|
|
60
64
|
|
|
61
65
|
client.addTestRun(STATUS.PASSED, {
|
|
62
66
|
test_id: testId,
|
|
63
|
-
|
|
67
|
+
suite_title: getSuiteTitle(test),
|
|
68
|
+
title: getTestName(test),
|
|
69
|
+
code: test.body.toString(),
|
|
70
|
+
file: getFile(test),
|
|
64
71
|
time: test.duration,
|
|
65
72
|
logs,
|
|
73
|
+
manuallyAttachedArtifacts: artifacts,
|
|
74
|
+
meta: keyValues,
|
|
66
75
|
});
|
|
67
76
|
});
|
|
68
77
|
|
|
@@ -71,7 +80,10 @@ function MochaReporter(runner, opts) {
|
|
|
71
80
|
console.log('skip: %s', test.fullTitle());
|
|
72
81
|
const testId = parseTest(test.title);
|
|
73
82
|
client.addTestRun(STATUS.SKIPPED, {
|
|
74
|
-
title: test
|
|
83
|
+
title: getTestName(test),
|
|
84
|
+
suite_title: getSuiteTitle(test),
|
|
85
|
+
code: test.body.toString(),
|
|
86
|
+
file: getFile(test),
|
|
75
87
|
test_id: testId,
|
|
76
88
|
time: test.duration,
|
|
77
89
|
});
|
|
@@ -86,8 +98,11 @@ function MochaReporter(runner, opts) {
|
|
|
86
98
|
|
|
87
99
|
client.addTestRun(STATUS.FAILED, {
|
|
88
100
|
error: err,
|
|
101
|
+
suite_title: getSuiteTitle(test),
|
|
102
|
+
file: getFile(test),
|
|
89
103
|
test_id: testId,
|
|
90
|
-
title: test
|
|
104
|
+
title: getTestName(test),
|
|
105
|
+
code: test.body.toString(),
|
|
91
106
|
time: test.duration,
|
|
92
107
|
logs,
|
|
93
108
|
});
|
|
@@ -101,9 +116,10 @@ function MochaReporter(runner, opts) {
|
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
function getTestLogs(test) {
|
|
104
|
-
|
|
119
|
+
|
|
120
|
+
const suiteLogsArr = services.logger.getLogs(test.parent.fullTitle());
|
|
105
121
|
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
106
|
-
const testLogsArr = logger.getLogs(test.fullTitle());
|
|
122
|
+
const testLogsArr = services.logger.getLogs(test.fullTitle());
|
|
107
123
|
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
108
124
|
|
|
109
125
|
let logs = '';
|
|
@@ -120,3 +136,21 @@ function getTestLogs(test) {
|
|
|
120
136
|
Mocha.utils.inherits(MochaReporter, Mocha.reporters.Spec);
|
|
121
137
|
|
|
122
138
|
module.exports = MochaReporter;
|
|
139
|
+
|
|
140
|
+
function getSuiteTitle(test, pathArr = []) {
|
|
141
|
+
|
|
142
|
+
if (test.parent.parent) getSuiteTitle(test.parent, pathArr);
|
|
143
|
+
|
|
144
|
+
pathArr.push(test.parent.title);
|
|
145
|
+
|
|
146
|
+
return pathArr.filter(t => !!t)[0];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getFile(test) {
|
|
150
|
+
return test.parent.file?.replace(process.cwd(), '');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getTestName(test) {
|
|
154
|
+
if (process.env.TESTOMATIO_CREATE === 'fulltitle') return test.fullTitle();
|
|
155
|
+
return test.title;
|
|
156
|
+
}
|
|
@@ -17,26 +17,22 @@ class PlaywrightReporter {
|
|
|
17
17
|
this.uploads = [];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
onBegin(
|
|
20
|
+
onBegin(config, suite) {
|
|
21
21
|
// clean data storage
|
|
22
22
|
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
23
23
|
if (!this.client) return;
|
|
24
24
|
this.suite = suite;
|
|
25
|
+
this.config = config;
|
|
25
26
|
this.client.createRun();
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
/*
|
|
29
|
-
onTestBegin(test) {
|
|
30
|
-
// does not work; value is not to the storage context
|
|
31
|
-
global.testTitle = test.title;
|
|
32
|
-
} */
|
|
33
|
-
|
|
34
29
|
onTestEnd(test, result) {
|
|
35
30
|
if (!this.client) return;
|
|
36
31
|
|
|
37
|
-
let testId = parseTest(test.title);
|
|
38
|
-
|
|
39
32
|
const { title } = test;
|
|
33
|
+
|
|
34
|
+
let testId = parseTest(title);
|
|
35
|
+
|
|
40
36
|
const { error, duration } = result;
|
|
41
37
|
|
|
42
38
|
const suite_title = test.parent ? test.parent.title : null;
|
|
@@ -59,7 +55,8 @@ class PlaywrightReporter {
|
|
|
59
55
|
title,
|
|
60
56
|
steps: steps.join('\n'),
|
|
61
57
|
time: duration,
|
|
62
|
-
|
|
58
|
+
logs,
|
|
59
|
+
// TODO: attach artifacts
|
|
63
60
|
})
|
|
64
61
|
.then(pipes => {
|
|
65
62
|
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
@@ -77,7 +74,24 @@ class PlaywrightReporter {
|
|
|
77
74
|
reportTestPromises.push(reportTestPromise);
|
|
78
75
|
}
|
|
79
76
|
|
|
80
|
-
|
|
77
|
+
|
|
78
|
+
#getArtifactPath(artifact) {
|
|
79
|
+
if (artifact.path) {
|
|
80
|
+
if (path.isAbsolute(artifact.path)) return artifact.path;
|
|
81
|
+
|
|
82
|
+
return path.join(this.config.outputDir || this.config.projects[0].outputDir, artifact.path);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (artifact.body) {
|
|
86
|
+
const fileName = tmpFile();
|
|
87
|
+
fs.writeFileSync(fileName, artifact.body);
|
|
88
|
+
return fileName;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async onEnd(result, config) {
|
|
81
95
|
if (!this.client) return;
|
|
82
96
|
|
|
83
97
|
await Promise.all(reportTestPromises);
|
|
@@ -91,11 +105,7 @@ class PlaywrightReporter {
|
|
|
91
105
|
const { title, testId, suite_title } = upload;
|
|
92
106
|
|
|
93
107
|
const files = upload.files.map(attachment => {
|
|
94
|
-
|
|
95
|
-
const fileName = tmpFile();
|
|
96
|
-
fs.writeFileSync(fileName, attachment.body);
|
|
97
|
-
}
|
|
98
|
-
return { path: attachment.path, title, type: attachment.contentType };
|
|
108
|
+
return { path: this.#getArtifactPath(attachment), title, type: attachment.contentType };
|
|
99
109
|
});
|
|
100
110
|
|
|
101
111
|
promises.push(
|
|
@@ -143,4 +153,20 @@ function tmpFile(prefix = 'tmp.') {
|
|
|
143
153
|
return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
|
|
144
154
|
}
|
|
145
155
|
|
|
156
|
+
function initPlaywrightForStorage() {
|
|
157
|
+
try {
|
|
158
|
+
// @ts-ignore-next-line
|
|
159
|
+
// eslint-disable-next-line import/no-unresolved
|
|
160
|
+
const { test } = require('@playwright/test');
|
|
161
|
+
// eslint-disable-next-line no-empty-pattern
|
|
162
|
+
test.beforeEach(async ({}, testInfo) => {
|
|
163
|
+
const fullTestTitle = `${testInfo.file}_${testInfo.title}`;
|
|
164
|
+
global.testomatioTestTitle = fullTestTitle;
|
|
165
|
+
});
|
|
166
|
+
} catch (e) {
|
|
167
|
+
// ignore
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
146
171
|
module.exports = PlaywrightReporter;
|
|
172
|
+
module.exports.initPlaywrightForStorage = initPlaywrightForStorage;
|
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 { TESTOMATIO } = require('../config');
|
|
8
9
|
|
|
9
10
|
console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
|
|
10
11
|
|
|
@@ -20,7 +21,7 @@ program
|
|
|
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'] || TESTOMATIO;
|
|
24
25
|
const title = process.env.TESTOMATIO_TITLE;
|
|
25
26
|
|
|
26
27
|
if (launch) {
|