@testomatio/reporter 1.2.0-beta β 1.2.0-beta-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 +13 -9
- package/lib/_ArtifactStorageOld.js +1 -1
- package/lib/adapter/codecept.js +59 -39
- package/lib/adapter/cucumber/current.js +95 -59
- package/lib/adapter/cucumber/legacy.js +25 -11
- package/lib/adapter/cypress-plugin/index.js +1 -1
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +18 -3
- package/lib/adapter/mocha.js +23 -23
- package/lib/adapter/playwright.js +18 -11
- package/lib/adapter/webdriver.js +1 -1
- package/lib/artifactStorage.js +2 -2
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +102 -34
- package/lib/constants.js +7 -0
- package/lib/dataStorage.js +132 -72
- package/lib/junit-adapter/java.js +27 -9
- package/lib/logger.js +182 -164
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +24 -37
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/html.js +187 -0
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.js +138 -34
- package/lib/reporter.js +2 -1
- package/lib/template/real-data-example.js +47 -0
- package/lib/template/template.hbs +249 -0
- package/lib/template/testomatio.hbs +720 -0
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/{util.js β utils/utils.js} +116 -10
- package/lib/xmlReader.js +132 -44
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -11,12 +11,12 @@ Testomat.io Reporter (this npm package) supports:
|
|
|
11
11
|
|
|
12
12
|
* π Integarion with all popular [JavaScript/TypeScript frameworks](./docs/frameworks.md)
|
|
13
13
|
* ποΈ Screenshots, videos, traces [uploaded into S3 bucket](./docs/artifacts.md)
|
|
14
|
-
* π Stack traces and error messages
|
|
15
|
-
* π [GitHub](./docs/pipes.md
|
|
14
|
+
* π [Stack traces](./docs/stacktrace.md) and error messages
|
|
15
|
+
* π [GitHub](./docs/pipes/github.md) & [GitLab](./docs/pipes/gitlab.md) integration
|
|
16
16
|
* π
Realtime reports
|
|
17
17
|
* ποΈ Other test frameworks supported via [JUNit XML](./docs/junit.md)
|
|
18
18
|
* πΆββοΈ Steps *(work in progress)*
|
|
19
|
-
* π Logger *(work in progress, supports Jest for now)*
|
|
19
|
+
* π [Logger](./docs/logger.md) *(work in progress, supports Jest for now)*
|
|
20
20
|
* βοΈ Custom properties and metadata *(work in progress)*
|
|
21
21
|
* π― Free & open-source.
|
|
22
22
|
* π Public and private Run reports on cloud via [Testomat.io App](https://testomat.io) π
|
|
@@ -85,13 +85,13 @@ or any [other via JUnit](./docs/junit.md) report....
|
|
|
85
85
|
|
|
86
86
|
### 2οΈβ£ Configure Reports
|
|
87
87
|
|
|
88
|
-
* [Create report on Testomat.io](./docs/pipes.md
|
|
89
|
-
* [Create brief summary report for GitHub Pull Request](./docs/pipes.md
|
|
90
|
-
* [Create brief summary report for GitLab Merge Request](./docs/pipes.md
|
|
91
|
-
* [Configure other pipes](./docs/pipes
|
|
88
|
+
* [Create report on Testomat.io](./docs/pipes/testomatio.md).
|
|
89
|
+
* [Create brief summary report for GitHub Pull Request](./docs/pipes/github.md) π
|
|
90
|
+
* [Create brief summary report for GitLab Merge Request](./docs/pipes/gitlab.md).
|
|
91
|
+
* [Configure other pipes](./docs/pipes/md) for other ways to process test results output.
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-

|
|
94
|
+

|
|
95
95
|
|
|
96
96
|
GitHub report published as a comment to Pull Request:
|
|
97
97
|
|
|
@@ -121,10 +121,14 @@ Bring this reporter on CI and never lose test results again!
|
|
|
121
121
|
|
|
122
122
|
* π οΈ [Frameworks](./docs/frameworks.md)
|
|
123
123
|
* β² [Pipes](./docs/pipes.md)
|
|
124
|
+
* [Testomat.io](./docs/pipes/testomatio.md)
|
|
125
|
+
* [GitHub](./docs/pipes/github.md)
|
|
126
|
+
* [Gitlab](./docs/pipes/gitlab.md)
|
|
127
|
+
* [CSV](./docs/pipes/csv.md)
|
|
124
128
|
* π [JUnit](./docs/junit.md)
|
|
125
129
|
* ποΈ [Artifacts](./docs/artifacts.md)
|
|
126
130
|
* π [Workflows](./docs/workflows.md)
|
|
127
|
-
*
|
|
131
|
+
* ποΈ [Logger](./docs/logger.md)
|
|
128
132
|
|
|
129
133
|
## Development
|
|
130
134
|
|
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const uuid = require('uuid');
|
|
6
6
|
const { TESTOMAT_ARTIFACT_SUFFIX } = require('./constants');
|
|
7
|
-
const { specificTestInfo } = require('./
|
|
7
|
+
const { specificTestInfo } = require('./utils/utils');
|
|
8
8
|
|
|
9
9
|
class ArtifactStorage {
|
|
10
10
|
|
package/lib/adapter/codecept.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const debug = require('debug')('@testomatio/reporter:adapter:codeceptjs');
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const TestomatClient = require('../client');
|
|
4
|
-
const { STATUS, APP_PREFIX } = require('../constants');
|
|
4
|
+
const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE } = require('../constants');
|
|
5
5
|
const upload = require('../fileUploader');
|
|
6
|
-
const
|
|
6
|
+
const { parseTest: getIdFromTestTitle, fileSystem } = require('../utils/utils');
|
|
7
7
|
|
|
8
8
|
if (!global.codeceptjs) {
|
|
9
9
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
@@ -16,9 +16,9 @@ let currentMetaStep = [];
|
|
|
16
16
|
let error;
|
|
17
17
|
let stepShift = 0;
|
|
18
18
|
|
|
19
|
-
const output = new Output({
|
|
20
|
-
|
|
21
|
-
});
|
|
19
|
+
// const output = new Output({
|
|
20
|
+
// filterFn: stack => !stack.includes('codeceptjs/lib/output'), // output from codeceptjs
|
|
21
|
+
// });
|
|
22
22
|
|
|
23
23
|
let stepStart = new Date();
|
|
24
24
|
|
|
@@ -51,24 +51,36 @@ function CodeceptReporter(config) {
|
|
|
51
51
|
|
|
52
52
|
recorder.startUnlessRunning();
|
|
53
53
|
|
|
54
|
+
global.testomatioRunningEnvironment = 'codeceptjs';
|
|
55
|
+
|
|
54
56
|
// Listening to events
|
|
55
57
|
event.dispatcher.on(event.all.before, () => {
|
|
58
|
+
// clear tmp dir
|
|
59
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
60
|
+
|
|
56
61
|
recorder.add('Creating new run', () => client.createRun());
|
|
57
62
|
videos = [];
|
|
58
63
|
traces = [];
|
|
64
|
+
|
|
65
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
59
66
|
});
|
|
60
67
|
|
|
61
68
|
event.dispatcher.on(event.test.before, () => {
|
|
62
69
|
recorder.add(() => {
|
|
63
70
|
currentMetaStep = [];
|
|
64
|
-
output.reset();
|
|
65
|
-
output.start();
|
|
71
|
+
// output.reset();
|
|
72
|
+
// output.start();
|
|
66
73
|
stepShift = 0;
|
|
67
74
|
});
|
|
75
|
+
|
|
76
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
77
|
+
// reset steps
|
|
78
|
+
global.testomatioDataStore.steps = [];
|
|
68
79
|
});
|
|
69
80
|
|
|
70
81
|
event.dispatcher.on(event.test.started, test => {
|
|
71
82
|
testTimeMap[test.id] = Date.now();
|
|
83
|
+
if (global.testomatioDataStore) global.testomatioDataStore.currentlyRunningTestId = getIdFromTestTitle(test.title);
|
|
72
84
|
});
|
|
73
85
|
|
|
74
86
|
event.dispatcher.on(event.all.result, async () => {
|
|
@@ -97,10 +109,10 @@ function CodeceptReporter(config) {
|
|
|
97
109
|
suite_title: test.parent && test.parent.title,
|
|
98
110
|
message: testObj.message,
|
|
99
111
|
time: getDuration(test),
|
|
100
|
-
steps:
|
|
112
|
+
steps: global.testomatioDataStore.steps.join('\n') || null,
|
|
101
113
|
test_id: testId,
|
|
102
114
|
});
|
|
103
|
-
output.stop();
|
|
115
|
+
// output.stop();
|
|
104
116
|
});
|
|
105
117
|
|
|
106
118
|
event.dispatcher.on(event.test.failed, (test, err) => {
|
|
@@ -125,7 +137,7 @@ function CodeceptReporter(config) {
|
|
|
125
137
|
time: 0,
|
|
126
138
|
});
|
|
127
139
|
}
|
|
128
|
-
output.stop();
|
|
140
|
+
// output.stop();
|
|
129
141
|
});
|
|
130
142
|
|
|
131
143
|
event.dispatcher.on(event.test.after, test => {
|
|
@@ -141,32 +153,33 @@ function CodeceptReporter(config) {
|
|
|
141
153
|
|
|
142
154
|
const files = [];
|
|
143
155
|
if (artifacts.screenshot) files.push({ path: artifacts.screenshot, type: 'image/png' });
|
|
144
|
-
// todo: video must be uploaded later....
|
|
156
|
+
// todo: video must be uploaded later....
|
|
145
157
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
const reportTestPromise = client
|
|
159
|
+
.addTestRun(STATUS.FAILED, {
|
|
160
|
+
...stripExampleFromTitle(title),
|
|
161
|
+
test_id: testId,
|
|
162
|
+
suite_title: test.parent && test.parent.title,
|
|
163
|
+
error,
|
|
164
|
+
message: testObj.message,
|
|
165
|
+
time: getDuration(test),
|
|
166
|
+
files,
|
|
167
|
+
steps: global.testomatioDataStore?.steps?.join('\n') || null,
|
|
168
|
+
})
|
|
169
|
+
.then(pipes => {
|
|
170
|
+
testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
171
|
+
|
|
172
|
+
debug('artifacts', artifacts);
|
|
173
|
+
|
|
174
|
+
for (const aid in artifacts) {
|
|
175
|
+
if (aid.startsWith('video')) videos.push({ testId, title, path: artifacts[aid], type: 'video/webm' });
|
|
176
|
+
if (aid.startsWith('trace')) traces.push({ testId, title, path: artifacts[aid], type: 'application/zip' });
|
|
177
|
+
}
|
|
178
|
+
});
|
|
166
179
|
|
|
167
180
|
reportTestPromises.push(reportTestPromise);
|
|
168
181
|
|
|
169
|
-
output.stop();
|
|
182
|
+
// output.stop();
|
|
170
183
|
});
|
|
171
184
|
|
|
172
185
|
event.dispatcher.on(event.test.skipped, test => {
|
|
@@ -182,7 +195,7 @@ function CodeceptReporter(config) {
|
|
|
182
195
|
message: testObj.message,
|
|
183
196
|
time: getDuration(test),
|
|
184
197
|
});
|
|
185
|
-
output.stop();
|
|
198
|
+
// output.stop();
|
|
186
199
|
});
|
|
187
200
|
|
|
188
201
|
event.dispatcher.on(event.step.started, step => {
|
|
@@ -207,30 +220,37 @@ function CodeceptReporter(config) {
|
|
|
207
220
|
// eslint-disable-next-line no-continue
|
|
208
221
|
if (!metaSteps[i]) continue;
|
|
209
222
|
if (metaSteps[i].isBDD()) {
|
|
210
|
-
output.push(repeat(stepShift) + chalk.bold(metaSteps[i].toString()) + metaSteps[i].comment);
|
|
223
|
+
// output.push(repeat(stepShift) + chalk.bold(metaSteps[i].toString()) + metaSteps[i].comment);
|
|
224
|
+
global.testomatioDataStore?.steps?.push(
|
|
225
|
+
repeat(stepShift) + chalk.bold(metaSteps[i].toString()) + metaSteps[i].comment,
|
|
226
|
+
);
|
|
211
227
|
} else {
|
|
212
|
-
output.push(repeat(stepShift) + chalk.green.bold(metaSteps[i].toString()));
|
|
228
|
+
// output.push(repeat(stepShift) + chalk.green.bold(metaSteps[i].toString()));
|
|
229
|
+
global.testomatioDataStore?.steps?.push(repeat(stepShift) + chalk.green.bold(metaSteps[i].toString()));
|
|
213
230
|
}
|
|
214
231
|
}
|
|
215
232
|
}
|
|
216
233
|
currentMetaStep = metaSteps;
|
|
217
234
|
stepShift = 2 * shift;
|
|
218
235
|
|
|
219
|
-
const durationMs =
|
|
236
|
+
const durationMs = +new Date() - (+stepStart);
|
|
220
237
|
let duration = '';
|
|
221
238
|
if (durationMs) {
|
|
222
239
|
duration = repeat(1) + chalk.grey(`(${durationMs}ms)`);
|
|
223
240
|
}
|
|
224
241
|
|
|
225
242
|
if (step.status === STATUS.FAILED) {
|
|
226
|
-
output.push(repeat(stepShift) + chalk.red(step.toString()) + duration);
|
|
243
|
+
// output.push(repeat(stepShift) + chalk.red(step.toString()) + duration);
|
|
244
|
+
global.testomatioDataStore?.steps?.push(repeat(stepShift) + chalk.red(step.toString()) + duration);
|
|
227
245
|
} else {
|
|
228
|
-
output.push(repeat(stepShift) + step.toString() + duration);
|
|
246
|
+
// output.push(repeat(stepShift) + step.toString() + duration);
|
|
247
|
+
global.testomatioDataStore?.steps?.push(repeat(stepShift) + step.toString() + duration);
|
|
229
248
|
}
|
|
230
249
|
});
|
|
231
250
|
|
|
232
251
|
event.dispatcher.on(event.step.comment, step => {
|
|
233
|
-
output.push(chalk.cyan.bold(step.toString()));
|
|
252
|
+
// output.push(chalk.cyan.bold(step.toString()));
|
|
253
|
+
global.testomatioDataStore?.steps?.push(chalk.cyan.bold(step.toString()));
|
|
234
254
|
});
|
|
235
255
|
}
|
|
236
256
|
|
|
@@ -1,35 +1,59 @@
|
|
|
1
|
-
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
|
-
const { Formatter, formatterHelpers } = require(
|
|
1
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
|
|
2
|
+
const { Formatter, formatterHelpers } = require('@cucumber/cucumber');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
-
const { STATUS } = require('../../constants');
|
|
5
|
+
const { STATUS, TESTOMAT_TMP_STORAGE } = require('../../constants');
|
|
6
6
|
const TestomatClient = require('../../client');
|
|
7
|
+
const logger = require('../../logger');
|
|
8
|
+
const { parseTest, fileSystem } = require('../../utils/utils');
|
|
9
|
+
|
|
10
|
+
const { GherkinDocumentParser, PickleParser } = formatterHelpers;
|
|
11
|
+
const { getGherkinScenarioLocationMap, getGherkinStepMap } = GherkinDocumentParser;
|
|
12
|
+
const { getPickleStepMap } = PickleParser;
|
|
13
|
+
|
|
14
|
+
function getTestId(scenario) {
|
|
15
|
+
if (scenario) {
|
|
16
|
+
for (const tag of scenario.tags) {
|
|
17
|
+
const testId = parseTest(tag.name);
|
|
18
|
+
if (testId) return testId;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
7
21
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
getGherkinScenarioLocationMap,
|
|
11
|
-
getGherkinStepMap,
|
|
12
|
-
} = GherkinDocumentParser
|
|
13
|
-
const { getPickleStepMap } = PickleParser
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
14
24
|
|
|
15
25
|
class CucumberReporter extends Formatter {
|
|
16
26
|
constructor(options) {
|
|
17
27
|
super(options);
|
|
18
|
-
options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this))
|
|
28
|
+
options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this));
|
|
19
29
|
this.failures = [];
|
|
20
30
|
this.cases = [];
|
|
21
31
|
|
|
22
|
-
this.client = new TestomatClient();
|
|
32
|
+
this.client = new TestomatClient({ apiKey: options.apiKey || process.env.TESTOMATIO });
|
|
23
33
|
this.status = STATUS.PASSED;
|
|
24
34
|
|
|
35
|
+
global.testomatioRunningEnvironment = 'cucumber:current';
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
parseEnvelope(envelope) {
|
|
28
|
-
if (envelope.
|
|
39
|
+
if (envelope.testRunStarted) {
|
|
40
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
41
|
+
}
|
|
42
|
+
if (envelope.testCaseStarted && this.client) {
|
|
43
|
+
this.client.createRun();
|
|
44
|
+
this.onTestCaseStarted(envelope.testCaseStarted);
|
|
45
|
+
}
|
|
29
46
|
if (envelope.testCaseFinished) this.onTestCaseFinished(envelope.testCaseFinished);
|
|
30
47
|
if (envelope.testRunFinished) this.onTestRunFinished(envelope);
|
|
31
48
|
}
|
|
32
49
|
|
|
50
|
+
onTestCaseStarted(testCaseStarted) {
|
|
51
|
+
const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseStarted.id);
|
|
52
|
+
const testId = getTestId(testCaseAttempt.pickle);
|
|
53
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
54
|
+
global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
onTestCaseFinished(testCaseFinished) {
|
|
34
58
|
const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseFinished.testCaseStartedId);
|
|
35
59
|
|
|
@@ -45,10 +69,10 @@ class CucumberReporter extends Formatter {
|
|
|
45
69
|
if (testCaseAttempt.worstTestStepResult.status === 'SKIPPED') {
|
|
46
70
|
status = STATUS.SKIPPED;
|
|
47
71
|
}
|
|
48
|
-
if (testCaseAttempt.worstTestStepResult.status === 'UNDEFINED') {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
72
|
+
// if (testCaseAttempt.worstTestStepResult.status === 'UNDEFINED') {
|
|
73
|
+
// status = STATUS.SKIPPED;
|
|
74
|
+
// message = 'Undefined steps. Implement missing steps and rerun this scenario';
|
|
75
|
+
// }
|
|
52
76
|
if (testCaseAttempt.worstTestStepResult.status === 'FAILED') {
|
|
53
77
|
message = testCaseAttempt?.worstTestStepResult?.message;
|
|
54
78
|
status = STATUS.FAILED;
|
|
@@ -64,10 +88,12 @@ class CucumberReporter extends Formatter {
|
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
const scenario = testCaseAttempt.pickle.name;
|
|
67
|
-
|
|
91
|
+
// this may broke something (it is supposed to work, but I am sure it did not)
|
|
92
|
+
// const testId = testCaseAttempt.pickle.id;
|
|
93
|
+
const testId = getTestId(testCaseAttempt.pickle);
|
|
68
94
|
|
|
69
95
|
let exampleString = '';
|
|
70
|
-
if (example) exampleString = ` ${example.join(' | ')}
|
|
96
|
+
if (example) exampleString = ` ${example.join(' | ')}`;
|
|
71
97
|
let cliMessage = `${chalk.bold(scenario)}${exampleString}: ${chalk[color].bold(status.toUpperCase())} `;
|
|
72
98
|
|
|
73
99
|
if (message) cliMessage += chalk.gray(message.split('\n')[0]);
|
|
@@ -79,15 +105,21 @@ class CucumberReporter extends Formatter {
|
|
|
79
105
|
|
|
80
106
|
const time = Object.values(testCaseAttempt.stepResults)
|
|
81
107
|
.map(t => t.duration)
|
|
82
|
-
.reduce((sum, duration) => sum +
|
|
108
|
+
.reduce((sum, duration) => sum + duration.seconds * 1000 + duration.nanos / 1000000, 0);
|
|
83
109
|
|
|
84
110
|
if (!this.client) return;
|
|
85
111
|
|
|
112
|
+
const logs = logger.getLogs(testId);
|
|
113
|
+
|
|
86
114
|
this.client.addTestRun(status, {
|
|
87
115
|
// error: testCaseAttempt.worstTestStepResult.message,
|
|
88
116
|
message,
|
|
89
|
-
steps: getSteps(testCaseAttempt)
|
|
90
|
-
|
|
117
|
+
steps: getSteps(testCaseAttempt)
|
|
118
|
+
.map(s => s.toString())
|
|
119
|
+
.join('\n')
|
|
120
|
+
.trim(),
|
|
121
|
+
example: { ...example },
|
|
122
|
+
stack: logs,
|
|
91
123
|
title: scenario,
|
|
92
124
|
test_id: testId,
|
|
93
125
|
time,
|
|
@@ -99,21 +131,20 @@ class CucumberReporter extends Formatter {
|
|
|
99
131
|
console.log(chalk.bold('\nSUMMARY:\n\n'));
|
|
100
132
|
|
|
101
133
|
this.failures.forEach((tc, i) => {
|
|
102
|
-
let message = ` ${i+1}) ${tc.pickle.name}\n`;
|
|
134
|
+
let message = ` ${i + 1}) ${tc.pickle.name}\n`;
|
|
103
135
|
|
|
104
136
|
const steps = getSteps(tc);
|
|
105
137
|
|
|
106
138
|
steps.forEach(s => {
|
|
107
|
-
message += ` ${s.toString()}\n
|
|
108
|
-
})
|
|
139
|
+
message += ` ${s.toString()}\n`;
|
|
140
|
+
});
|
|
109
141
|
|
|
110
142
|
console.log(message);
|
|
111
143
|
if (tc?.worstTestStepResult?.message) {
|
|
112
144
|
console.log(chalk.red(tc?.worstTestStepResult?.message));
|
|
113
145
|
}
|
|
114
146
|
console.log();
|
|
115
|
-
|
|
116
|
-
})
|
|
147
|
+
});
|
|
117
148
|
}
|
|
118
149
|
|
|
119
150
|
const { testRunFinished } = envelope;
|
|
@@ -125,46 +156,48 @@ class CucumberReporter extends Formatter {
|
|
|
125
156
|
|
|
126
157
|
if (!this.client) return;
|
|
127
158
|
|
|
128
|
-
this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED)
|
|
159
|
+
this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED);
|
|
129
160
|
}
|
|
130
161
|
}
|
|
131
162
|
|
|
132
163
|
function getSteps(tc) {
|
|
133
164
|
const stepIds = Object.keys(tc.stepResults);
|
|
134
|
-
const pickleSteps = getPickleStepMap(tc.pickle)
|
|
135
|
-
return stepIds
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
const pickleSteps = getPickleStepMap(tc.pickle);
|
|
166
|
+
return stepIds
|
|
167
|
+
.map(stepId => {
|
|
168
|
+
const ts = tc.testCase.testSteps.find(t => t.id === stepId);
|
|
169
|
+
if (!ts) return;
|
|
170
|
+
if (!ts.pickleStepId) return;
|
|
171
|
+
const result = tc.stepResults[stepId];
|
|
172
|
+
const pickleStep = pickleSteps[ts.pickleStepId];
|
|
173
|
+
const sourceStepId = pickleStep.astNodeIds[0];
|
|
174
|
+
const step = {
|
|
175
|
+
text: pickleStep.text,
|
|
176
|
+
duration: result.duration,
|
|
177
|
+
status: result.status,
|
|
178
|
+
};
|
|
179
|
+
const color = getStatusColor(result.status);
|
|
180
|
+
if (sourceStepId && getGherkinStepMap(tc.gherkinDocument)[sourceStepId]) {
|
|
181
|
+
step.keyword = getGherkinStepMap(tc.gherkinDocument)[sourceStepId].keyword;
|
|
182
|
+
}
|
|
183
|
+
step.toString = function toString() {
|
|
184
|
+
const duration = step.duration.seconds * 1000 + step.duration.nanos / 1000000;
|
|
185
|
+
const durationString = ` ${chalk.gray(`(${Number(duration).toFixed(2)}ms)`)}`;
|
|
186
|
+
const stepString = `${chalk.bold(this.keyword)}${this.text}`.trim();
|
|
187
|
+
if (color === 'red') return chalk.red(stepString) + durationString;
|
|
188
|
+
if (color === 'yellow') return chalk.yellow(stepString) + durationString;
|
|
189
|
+
return stepString + durationString;
|
|
190
|
+
};
|
|
191
|
+
return step;
|
|
192
|
+
})
|
|
193
|
+
.filter(s => !!s);
|
|
161
194
|
}
|
|
162
195
|
|
|
163
196
|
function getStatusColor(status) {
|
|
164
197
|
if (status === 'UNDEFINED') return 'yellow';
|
|
165
198
|
if (status === 'SKIPPED') return 'yellow';
|
|
166
199
|
if (status === 'FAILED') return 'red';
|
|
167
|
-
return 'green'
|
|
200
|
+
return 'green';
|
|
168
201
|
}
|
|
169
202
|
|
|
170
203
|
function getExample(testCaseAttempt) {
|
|
@@ -173,11 +206,14 @@ function getExample(testCaseAttempt) {
|
|
|
173
206
|
if (!nodesMap[exampleNodeId]) return;
|
|
174
207
|
const featureDoc = fs.readFileSync(testCaseAttempt.gherkinDocument.uri).toString();
|
|
175
208
|
const { line } = nodesMap[exampleNodeId];
|
|
176
|
-
const example = featureDoc.split('\n')[line-1];
|
|
209
|
+
const example = featureDoc.split('\n')[line - 1];
|
|
177
210
|
if (example) {
|
|
178
|
-
return example
|
|
179
|
-
|
|
180
|
-
|
|
211
|
+
return example
|
|
212
|
+
.trim()
|
|
213
|
+
.split('|')
|
|
214
|
+
.filter(r => !!r)
|
|
215
|
+
.map(r => r.trim());
|
|
216
|
+
}
|
|
181
217
|
}
|
|
182
218
|
|
|
183
219
|
module.exports = CucumberReporter;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
|
|
2
2
|
const { Formatter } = require('cucumber');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
-
const { parseTest } = require('../../
|
|
5
|
-
const { STATUS } = require('../../constants');
|
|
4
|
+
const { parseTest, fileSystem } = require('../../utils/utils');
|
|
5
|
+
const { STATUS, TESTOMAT_TMP_STORAGE } = require('../../constants');
|
|
6
6
|
const TestomatClient = require('../../client');
|
|
7
7
|
|
|
8
8
|
const createTestomatFormatter = apiKey => {
|
|
@@ -96,9 +96,23 @@ const createTestomatFormatter = apiKey => {
|
|
|
96
96
|
this.status = STATUS.PASSED.toString();
|
|
97
97
|
|
|
98
98
|
options.eventBroadcaster.on('gherkin-document', addDocument);
|
|
99
|
-
options.eventBroadcaster.on('test-run-started', () =>
|
|
99
|
+
options.eventBroadcaster.on('test-run-started', () => {
|
|
100
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
101
|
+
this?.client?.createRun();
|
|
102
|
+
});
|
|
100
103
|
options.eventBroadcaster.on('test-case-finished', this.onTestCaseFinished.bind(this));
|
|
104
|
+
options.eventBroadcaster.on('test-case-started', this.onTestCaseStarted.bind(this));
|
|
101
105
|
options.eventBroadcaster.on('test-run-finished', () => this?.client?.updateRunStatus(this.status));
|
|
106
|
+
|
|
107
|
+
global.testomatioRunningEnvironment = 'cucumber:legacy';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onTestCaseStarted(event) {
|
|
111
|
+
const scenario = getScenario(event.sourceLocation);
|
|
112
|
+
const testId = getTestId(scenario);
|
|
113
|
+
|
|
114
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
115
|
+
global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
onTestCaseFinished(event) {
|
|
@@ -111,15 +125,15 @@ const createTestomatFormatter = apiKey => {
|
|
|
111
125
|
|
|
112
126
|
if (!scenario.name) return;
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
const message = '';
|
|
129
|
+
const cliMessage = `- ${scenario.name}: ${chalk.bold(status.toUpperCase())}`;
|
|
116
130
|
|
|
117
|
-
if (event.result.status === 'undefined') {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
131
|
+
// if (event.result.status === 'undefined') {
|
|
132
|
+
// cliMessage += chalk.yellow(
|
|
133
|
+
// ' (undefined steps. Run Cucumber without this formatter and implement missing steps)',
|
|
134
|
+
// );
|
|
135
|
+
// message = 'Undefined steps. Implement missing steps and rerun this scenario';
|
|
136
|
+
// }
|
|
123
137
|
console.log(cliMessage);
|
|
124
138
|
if (status !== STATUS.PASSED && status !== STATUS.SKIPPED) {
|
|
125
139
|
this.status = STATUS.FAILED;
|
package/lib/adapter/jasmine.js
CHANGED
package/lib/adapter/jest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const TestomatClient = require('../client');
|
|
2
2
|
const { STATUS, TESTOMAT_TMP_STORAGE } = require('../constants');
|
|
3
|
-
const { parseTest, ansiRegExp, fileSystem } = require('../
|
|
3
|
+
const { parseTest, ansiRegExp, fileSystem } = require('../utils/utils');
|
|
4
4
|
|
|
5
5
|
class JestReporter {
|
|
6
6
|
constructor(globalConfig, options) {
|
|
@@ -12,8 +12,14 @@ class JestReporter {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
static getIdOfCurrentlyRunningTest() {
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
}
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
onRunStart() {
|
|
@@ -36,12 +42,21 @@ class JestReporter {
|
|
|
36
42
|
steps = failureMessages[0];
|
|
37
43
|
}
|
|
38
44
|
const testId = parseTest(title);
|
|
45
|
+
let suite_title;
|
|
46
|
+
// this is test without a suite
|
|
47
|
+
if (result.fullName === result.title) {
|
|
48
|
+
suite_title = testResult.testFilePath.split('/').pop();
|
|
49
|
+
} else {
|
|
50
|
+
suite_title = result.fullName.replace(result.title, '');
|
|
51
|
+
}
|
|
52
|
+
|
|
39
53
|
const deducedStatus = status === 'pending' ? 'skipped' : status;
|
|
40
54
|
// In jest if test is not matched with test name pattern it is considered as skipped.
|
|
41
55
|
// So adding a check if it is skipped for real or because of test pattern
|
|
42
56
|
if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
|
|
43
57
|
this.client.addTestRun(deducedStatus, {
|
|
44
58
|
test_id: testId,
|
|
59
|
+
suite_title,
|
|
45
60
|
error,
|
|
46
61
|
steps,
|
|
47
62
|
title,
|