@testomatio/reporter 1.0.0-beta.4 → 1.0.1-6.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/Changelog.md +100 -2
- package/README.md +92 -540
- package/lib/{ArtifactStorage.js → _ArtifactStorageOld.js} +16 -5
- package/lib/adapter/codecept.js +83 -53
- 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 +30 -2
- package/lib/adapter/mocha.js +24 -24
- package/lib/adapter/playwright.js +18 -11
- package/lib/adapter/webdriver.js +1 -1
- package/lib/artifactStorage.js +25 -0
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +117 -41
- package/lib/constants.js +5 -0
- package/lib/dataStorage.js +232 -0
- package/lib/junit-adapter/java.js +27 -9
- package/lib/logger.js +319 -0
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +26 -39
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/testomatio.js +138 -33
- package/lib/reporter.js +11 -2
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/utils/utils.js +310 -0
- package/lib/xmlReader.js +132 -44
- package/package.json +9 -6
- package/lib/util.js +0 -184
|
@@ -4,27 +4,32 @@ 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
|
+
|
|
11
11
|
constructor(params) {
|
|
12
|
-
this._tmpPrefix = "tsmt_reporter";
|
|
13
12
|
this.isFile = false;
|
|
14
13
|
this.isMemory = true;
|
|
15
14
|
this.storage = params?.toFile;
|
|
16
15
|
|
|
17
16
|
if (this.storage) {
|
|
17
|
+
// this is fine to use when you start saving artifacts;
|
|
18
|
+
// but when you need to retrieve them, you will not know if there is "isFile" param,
|
|
19
|
+
// because you need to create a new instance of ArtifactStorage inside client
|
|
20
|
+
// so the solution is make it opposite to isMemory (which will be retrieved from global)
|
|
18
21
|
this.isFile = true;
|
|
19
22
|
this.isMemory = false;
|
|
20
|
-
this.tmpDirFullpath = this.createTestomatTmpDir(
|
|
23
|
+
this.tmpDirFullpath = this.createTestomatTmpDir(ArtifactStorage._tmpPrefix);
|
|
21
24
|
debug('SAVE to tmp folder mode enabled!');
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
static async storeToFile(tmpDirName, artifact, testSuffix = "test") {
|
|
29
|
+
// this assumes to use multiple files for each test. do we really want this?
|
|
26
30
|
const suffix = TESTOMAT_ARTIFACT_SUFFIX + uuid.v4() + testSuffix;
|
|
27
31
|
const dirpath = join(os.tmpdir(), tmpDirName);
|
|
32
|
+
// why json?
|
|
28
33
|
const filepath = resolve(dirpath, `${suffix}.json`);
|
|
29
34
|
|
|
30
35
|
return fs.promises.appendFile(filepath, JSON.stringify(artifact));
|
|
@@ -34,6 +39,7 @@ class ArtifactStorage {
|
|
|
34
39
|
// TODO: mode for different pre-hooks. As variant - "all-tests" || "only_fail"
|
|
35
40
|
// const mode = process.env.TESTOMAT_ARTIFACTS || "only_fail";
|
|
36
41
|
|
|
42
|
+
// you save the artifact without specifying its source - test id
|
|
37
43
|
if (Array.isArray(global.testomatioArtifacts)) {
|
|
38
44
|
debug("Saving artifacts to global storage");
|
|
39
45
|
|
|
@@ -71,6 +77,7 @@ class ArtifactStorage {
|
|
|
71
77
|
return list;
|
|
72
78
|
}
|
|
73
79
|
|
|
80
|
+
// returns all the content from all files; do we really need it?
|
|
74
81
|
async tmpContents() {
|
|
75
82
|
const contents = [];
|
|
76
83
|
|
|
@@ -103,8 +110,9 @@ class ArtifactStorage {
|
|
|
103
110
|
}
|
|
104
111
|
}
|
|
105
112
|
|
|
113
|
+
// no need to use multiple dirs; we can implement it later if required
|
|
106
114
|
static tmpTestomatDirNames() {
|
|
107
|
-
const subname =
|
|
115
|
+
const subname = ArtifactStorage._tmpPrefix || "tsmt_reporter";
|
|
108
116
|
|
|
109
117
|
return fs.readdirSync(os.tmpdir(), { withFileTypes: true })
|
|
110
118
|
.filter((item) => item.isDirectory())
|
|
@@ -113,6 +121,7 @@ class ArtifactStorage {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
cleanup() {
|
|
124
|
+
// when you do a cleanup, I know nothing about isFile param
|
|
116
125
|
if (this.isFile && this.tmpDirFullpath) {
|
|
117
126
|
const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
|
|
118
127
|
|
|
@@ -128,4 +137,6 @@ class ArtifactStorage {
|
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
|
|
140
|
+
ArtifactStorage._tmpPrefix = "tsmt_reporter";
|
|
141
|
+
|
|
131
142
|
module.exports = ArtifactStorage;
|
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
|
|
|
@@ -33,6 +33,7 @@ if (MAJOR_VERSION < 3) {
|
|
|
33
33
|
function CodeceptReporter(config) {
|
|
34
34
|
let failedTests = [];
|
|
35
35
|
let videos = [];
|
|
36
|
+
let traces = [];
|
|
36
37
|
const reportTestPromises = [];
|
|
37
38
|
|
|
38
39
|
const testTimeMap = {};
|
|
@@ -50,45 +51,46 @@ function CodeceptReporter(config) {
|
|
|
50
51
|
|
|
51
52
|
recorder.startUnlessRunning();
|
|
52
53
|
|
|
54
|
+
global.testomatioRunningEnvironment = 'codeceptjs';
|
|
55
|
+
|
|
53
56
|
// Listening to events
|
|
54
57
|
event.dispatcher.on(event.all.before, () => {
|
|
58
|
+
// clear tmp dir
|
|
59
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
60
|
+
|
|
55
61
|
recorder.add('Creating new run', () => client.createRun());
|
|
56
62
|
videos = [];
|
|
63
|
+
traces = [];
|
|
64
|
+
|
|
65
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
57
66
|
});
|
|
58
67
|
|
|
59
68
|
event.dispatcher.on(event.test.before, () => {
|
|
60
69
|
recorder.add(() => {
|
|
61
70
|
currentMetaStep = [];
|
|
62
|
-
output.reset();
|
|
63
|
-
output.start();
|
|
71
|
+
// output.reset();
|
|
72
|
+
// output.start();
|
|
64
73
|
stepShift = 0;
|
|
65
74
|
});
|
|
75
|
+
|
|
76
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
77
|
+
// reset steps
|
|
78
|
+
global.testomatioDataStore.steps = [];
|
|
66
79
|
});
|
|
67
80
|
|
|
68
81
|
event.dispatcher.on(event.test.started, test => {
|
|
69
82
|
testTimeMap[test.id] = Date.now();
|
|
83
|
+
if (global.testomatioDataStore) global.testomatioDataStore.currentlyRunningTestId = getIdFromTestTitle(test.title);
|
|
70
84
|
});
|
|
71
85
|
|
|
72
86
|
event.dispatcher.on(event.all.result, async () => {
|
|
87
|
+
debug('waiting for all tests to be reported');
|
|
73
88
|
// all tests were reported and we can upload videos
|
|
74
89
|
await Promise.all(reportTestPromises);
|
|
75
90
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const promises = [];
|
|
80
|
-
for (const video of videos) {
|
|
81
|
-
const { testId, title, path, type } = video;
|
|
82
|
-
const file = { path, type, title };
|
|
83
|
-
promises.push(
|
|
84
|
-
client.addTestRun(undefined, {
|
|
85
|
-
...stripExampleFromTitle(title),
|
|
86
|
-
test_id: testId,
|
|
87
|
-
files: [file],
|
|
88
|
-
}),
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
await Promise.all(promises);
|
|
91
|
+
if (upload.isArtifactsEnabled()) {
|
|
92
|
+
uploadAttachments(client, videos, '🎞️ Uploading', 'video');
|
|
93
|
+
uploadAttachments(client, traces, '📁 Uploading', 'trace');
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
const status = failedTests.length === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
@@ -107,10 +109,10 @@ function CodeceptReporter(config) {
|
|
|
107
109
|
suite_title: test.parent && test.parent.title,
|
|
108
110
|
message: testObj.message,
|
|
109
111
|
time: getDuration(test),
|
|
110
|
-
steps:
|
|
112
|
+
steps: global.testomatioDataStore.steps.join('\n') || null,
|
|
111
113
|
test_id: testId,
|
|
112
114
|
});
|
|
113
|
-
output.stop();
|
|
115
|
+
// output.stop();
|
|
114
116
|
});
|
|
115
117
|
|
|
116
118
|
event.dispatcher.on(event.test.failed, (test, err) => {
|
|
@@ -135,7 +137,7 @@ function CodeceptReporter(config) {
|
|
|
135
137
|
time: 0,
|
|
136
138
|
});
|
|
137
139
|
}
|
|
138
|
-
output.stop();
|
|
140
|
+
// output.stop();
|
|
139
141
|
});
|
|
140
142
|
|
|
141
143
|
event.dispatcher.on(event.test.after, test => {
|
|
@@ -151,30 +153,33 @@ function CodeceptReporter(config) {
|
|
|
151
153
|
|
|
152
154
|
const files = [];
|
|
153
155
|
if (artifacts.screenshot) files.push({ path: artifacts.screenshot, type: 'image/png' });
|
|
154
|
-
// todo: video must be uploaded later....
|
|
156
|
+
// todo: video must be uploaded later....
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
});
|
|
174
179
|
|
|
175
180
|
reportTestPromises.push(reportTestPromise);
|
|
176
181
|
|
|
177
|
-
output.stop();
|
|
182
|
+
// output.stop();
|
|
178
183
|
});
|
|
179
184
|
|
|
180
185
|
event.dispatcher.on(event.test.skipped, test => {
|
|
@@ -190,7 +195,7 @@ function CodeceptReporter(config) {
|
|
|
190
195
|
message: testObj.message,
|
|
191
196
|
time: getDuration(test),
|
|
192
197
|
});
|
|
193
|
-
output.stop();
|
|
198
|
+
// output.stop();
|
|
194
199
|
});
|
|
195
200
|
|
|
196
201
|
event.dispatcher.on(event.step.started, step => {
|
|
@@ -215,33 +220,58 @@ function CodeceptReporter(config) {
|
|
|
215
220
|
// eslint-disable-next-line no-continue
|
|
216
221
|
if (!metaSteps[i]) continue;
|
|
217
222
|
if (metaSteps[i].isBDD()) {
|
|
218
|
-
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
|
+
);
|
|
219
227
|
} else {
|
|
220
|
-
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()));
|
|
221
230
|
}
|
|
222
231
|
}
|
|
223
232
|
}
|
|
224
233
|
currentMetaStep = metaSteps;
|
|
225
234
|
stepShift = 2 * shift;
|
|
226
235
|
|
|
227
|
-
const durationMs =
|
|
236
|
+
const durationMs = +new Date() - (+stepStart);
|
|
228
237
|
let duration = '';
|
|
229
238
|
if (durationMs) {
|
|
230
239
|
duration = repeat(1) + chalk.grey(`(${durationMs}ms)`);
|
|
231
240
|
}
|
|
232
241
|
|
|
233
242
|
if (step.status === STATUS.FAILED) {
|
|
234
|
-
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);
|
|
235
245
|
} else {
|
|
236
|
-
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);
|
|
237
248
|
}
|
|
238
249
|
});
|
|
239
250
|
|
|
240
251
|
event.dispatcher.on(event.step.comment, step => {
|
|
241
|
-
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()));
|
|
242
254
|
});
|
|
243
255
|
}
|
|
244
256
|
|
|
257
|
+
async function uploadAttachments(client, attachments, messagePrefix, attachmentType) {
|
|
258
|
+
if (attachments.length > 0) {
|
|
259
|
+
console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType}/-s ...`);
|
|
260
|
+
|
|
261
|
+
const promises = attachments.map(async (attachment) => {
|
|
262
|
+
const { testId, title, path, type } = attachment;
|
|
263
|
+
const file = { path, type, title };
|
|
264
|
+
return client.addTestRun(undefined, {
|
|
265
|
+
...stripExampleFromTitle(title),
|
|
266
|
+
test_id: testId,
|
|
267
|
+
files: [file],
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await Promise.all(promises);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
245
275
|
function parseTest(tags) {
|
|
246
276
|
if (tags) {
|
|
247
277
|
for (const tag of tags) {
|
|
@@ -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