@testomatio/reporter 1.2.2-beta → 1.2.3-beta
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/lib/adapter/codecept.js +57 -39
- package/lib/adapter/cucumber/current.js +89 -53
- package/lib/adapter/cucumber/legacy.js +17 -3
- package/lib/adapter/jest.js +7 -2
- package/lib/adapter/playwright.js +35 -19
- package/lib/client.js +8 -7
- package/lib/dataStorage.js +97 -36
- package/lib/helpers.js +34 -0
- package/lib/logger.js +22 -7
- package/package.json +1 -2
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('../util');
|
|
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,34 @@ 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
|
+
global.testomatioDataStore.steps = [];
|
|
68
77
|
});
|
|
69
78
|
|
|
70
79
|
event.dispatcher.on(event.test.started, test => {
|
|
71
80
|
testTimeMap[test.id] = Date.now();
|
|
81
|
+
if (global.testomatioDataStore) global.testomatioDataStore.currentlyRunningTestId = getIdFromTestTitle(test.title);
|
|
72
82
|
});
|
|
73
83
|
|
|
74
84
|
event.dispatcher.on(event.all.result, async () => {
|
|
@@ -97,10 +107,10 @@ function CodeceptReporter(config) {
|
|
|
97
107
|
suite_title: test.parent && test.parent.title,
|
|
98
108
|
message: testObj.message,
|
|
99
109
|
time: getDuration(test),
|
|
100
|
-
steps:
|
|
110
|
+
steps: global.testomatioDataStore.steps.join('\n') || null,
|
|
101
111
|
test_id: testId,
|
|
102
112
|
});
|
|
103
|
-
output.stop();
|
|
113
|
+
// output.stop();
|
|
104
114
|
});
|
|
105
115
|
|
|
106
116
|
event.dispatcher.on(event.test.failed, (test, err) => {
|
|
@@ -125,7 +135,7 @@ function CodeceptReporter(config) {
|
|
|
125
135
|
time: 0,
|
|
126
136
|
});
|
|
127
137
|
}
|
|
128
|
-
output.stop();
|
|
138
|
+
// output.stop();
|
|
129
139
|
});
|
|
130
140
|
|
|
131
141
|
event.dispatcher.on(event.test.after, test => {
|
|
@@ -141,32 +151,33 @@ function CodeceptReporter(config) {
|
|
|
141
151
|
|
|
142
152
|
const files = [];
|
|
143
153
|
if (artifacts.screenshot) files.push({ path: artifacts.screenshot, type: 'image/png' });
|
|
144
|
-
// todo: video must be uploaded later....
|
|
154
|
+
// todo: video must be uploaded later....
|
|
145
155
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
156
|
+
const reportTestPromise = client
|
|
157
|
+
.addTestRun(STATUS.FAILED, {
|
|
158
|
+
...stripExampleFromTitle(title),
|
|
159
|
+
test_id: testId,
|
|
160
|
+
suite_title: test.parent && test.parent.title,
|
|
161
|
+
error,
|
|
162
|
+
message: testObj.message,
|
|
163
|
+
time: getDuration(test),
|
|
164
|
+
files,
|
|
165
|
+
steps: global.testomatioDataStore.steps.join('\n') || null,
|
|
166
|
+
})
|
|
167
|
+
.then(pipes => {
|
|
168
|
+
testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
169
|
+
|
|
170
|
+
debug('artifacts', artifacts);
|
|
171
|
+
|
|
172
|
+
for (const aid in artifacts) {
|
|
173
|
+
if (aid.startsWith('video')) videos.push({ testId, title, path: artifacts[aid], type: 'video/webm' });
|
|
174
|
+
if (aid.startsWith('trace')) traces.push({ testId, title, path: artifacts[aid], type: 'application/zip' });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
166
177
|
|
|
167
178
|
reportTestPromises.push(reportTestPromise);
|
|
168
179
|
|
|
169
|
-
output.stop();
|
|
180
|
+
// output.stop();
|
|
170
181
|
});
|
|
171
182
|
|
|
172
183
|
event.dispatcher.on(event.test.skipped, test => {
|
|
@@ -182,7 +193,7 @@ function CodeceptReporter(config) {
|
|
|
182
193
|
message: testObj.message,
|
|
183
194
|
time: getDuration(test),
|
|
184
195
|
});
|
|
185
|
-
output.stop();
|
|
196
|
+
// output.stop();
|
|
186
197
|
});
|
|
187
198
|
|
|
188
199
|
event.dispatcher.on(event.step.started, step => {
|
|
@@ -207,30 +218,37 @@ function CodeceptReporter(config) {
|
|
|
207
218
|
// eslint-disable-next-line no-continue
|
|
208
219
|
if (!metaSteps[i]) continue;
|
|
209
220
|
if (metaSteps[i].isBDD()) {
|
|
210
|
-
output.push(repeat(stepShift) + chalk.bold(metaSteps[i].toString()) + metaSteps[i].comment);
|
|
221
|
+
// output.push(repeat(stepShift) + chalk.bold(metaSteps[i].toString()) + metaSteps[i].comment);
|
|
222
|
+
global.testomatioDataStore.steps.push(
|
|
223
|
+
repeat(stepShift) + chalk.bold(metaSteps[i].toString()) + metaSteps[i].comment,
|
|
224
|
+
);
|
|
211
225
|
} else {
|
|
212
|
-
output.push(repeat(stepShift) + chalk.green.bold(metaSteps[i].toString()));
|
|
226
|
+
// output.push(repeat(stepShift) + chalk.green.bold(metaSteps[i].toString()));
|
|
227
|
+
global.testomatioDataStore.steps.push(repeat(stepShift) + chalk.green.bold(metaSteps[i].toString()));
|
|
213
228
|
}
|
|
214
229
|
}
|
|
215
230
|
}
|
|
216
231
|
currentMetaStep = metaSteps;
|
|
217
232
|
stepShift = 2 * shift;
|
|
218
233
|
|
|
219
|
-
const durationMs =
|
|
234
|
+
const durationMs = +new Date() - (+stepStart);
|
|
220
235
|
let duration = '';
|
|
221
236
|
if (durationMs) {
|
|
222
237
|
duration = repeat(1) + chalk.grey(`(${durationMs}ms)`);
|
|
223
238
|
}
|
|
224
239
|
|
|
225
240
|
if (step.status === STATUS.FAILED) {
|
|
226
|
-
output.push(repeat(stepShift) + chalk.red(step.toString()) + duration);
|
|
241
|
+
// output.push(repeat(stepShift) + chalk.red(step.toString()) + duration);
|
|
242
|
+
global.testomatioDataStore.steps.push(repeat(stepShift) + chalk.red(step.toString()) + duration);
|
|
227
243
|
} else {
|
|
228
|
-
output.push(repeat(stepShift) + step.toString() + duration);
|
|
244
|
+
// output.push(repeat(stepShift) + step.toString() + duration);
|
|
245
|
+
global.testomatioDataStore.steps.push(repeat(stepShift) + step.toString() + duration);
|
|
229
246
|
}
|
|
230
247
|
});
|
|
231
248
|
|
|
232
249
|
event.dispatcher.on(event.step.comment, step => {
|
|
233
|
-
output.push(chalk.cyan.bold(step.toString()));
|
|
250
|
+
// output.push(chalk.cyan.bold(step.toString()));
|
|
251
|
+
global.testomatioDataStore.steps.push(chalk.cyan.bold(step.toString()));
|
|
234
252
|
});
|
|
235
253
|
}
|
|
236
254
|
|
|
@@ -1,35 +1,59 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
|
-
const { Formatter, formatterHelpers } = require(
|
|
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('../../util');
|
|
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
32
|
this.client = new TestomatClient();
|
|
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
|
|
|
@@ -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('../../util');
|
|
5
|
-
const { STATUS } = require('../../constants');
|
|
4
|
+
const { parseTest, fileSystem } = require('../../util');
|
|
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) {
|
package/lib/adapter/jest.js
CHANGED
|
@@ -13,8 +13,13 @@ class JestReporter {
|
|
|
13
13
|
|
|
14
14
|
static getIdOfCurrentlyRunningTest() {
|
|
15
15
|
if (!process.env.JEST_WORKER_ID) return null;
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
}
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
onRunStart() {
|
|
@@ -3,10 +3,11 @@ 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 } = require('../constants');
|
|
6
|
+
const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE } = require('../constants');
|
|
7
7
|
const TestomatioClient = require('../client');
|
|
8
8
|
const { isArtifactsEnabled } = require('../fileUploader');
|
|
9
|
-
const { parseTest } = require('../util');
|
|
9
|
+
const { parseTest, fileSystem } = require('../util');
|
|
10
|
+
const { setCurrentlyRunningTestId } = require('../helpers');
|
|
10
11
|
|
|
11
12
|
const reportTestPromises = [];
|
|
12
13
|
|
|
@@ -21,9 +22,21 @@ class TestomatioReporter {
|
|
|
21
22
|
if (!this.client) return;
|
|
22
23
|
this.suite = suite;
|
|
23
24
|
this.client.createRun();
|
|
25
|
+
|
|
26
|
+
// for data storage
|
|
27
|
+
// fileSystem.createDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onTestBegin(test, result) {
|
|
31
|
+
const testId = parseTest(test.title);
|
|
32
|
+
setCurrentlyRunningTestId(testId);
|
|
33
|
+
// global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
34
|
+
// TestomatioReporter.currentlyRunningTestId = testId;
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
onTestEnd(test, result) {
|
|
38
|
+
// reset currently running test id
|
|
39
|
+
// setCurrentlyRunningTestId('');
|
|
27
40
|
if (!this.client) return;
|
|
28
41
|
|
|
29
42
|
let testId = parseTest(test.title);
|
|
@@ -37,21 +50,26 @@ class TestomatioReporter {
|
|
|
37
50
|
for (const step of result.steps) {
|
|
38
51
|
appendStep(step, steps);
|
|
39
52
|
}
|
|
40
|
-
|
|
41
|
-
const reportTestPromise = this.client
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
50
|
-
|
|
51
|
-
this.uploads.push({
|
|
52
|
-
testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
|
|
53
|
+
|
|
54
|
+
const reportTestPromise = this.client
|
|
55
|
+
.addTestRun(checkStatus(result.status), {
|
|
56
|
+
error,
|
|
57
|
+
test_id: testId,
|
|
58
|
+
suite_title,
|
|
59
|
+
title,
|
|
60
|
+
steps: steps.join('\n'),
|
|
61
|
+
time: duration,
|
|
53
62
|
})
|
|
54
|
-
|
|
63
|
+
.then(pipes => {
|
|
64
|
+
testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
65
|
+
|
|
66
|
+
this.uploads.push({
|
|
67
|
+
testId,
|
|
68
|
+
title,
|
|
69
|
+
suite_title,
|
|
70
|
+
files: result.attachments.filter(a => a.body || a.path),
|
|
71
|
+
});
|
|
72
|
+
});
|
|
55
73
|
|
|
56
74
|
reportTestPromises.push(reportTestPromise);
|
|
57
75
|
}
|
|
@@ -67,10 +85,9 @@ class TestomatioReporter {
|
|
|
67
85
|
const promises = [];
|
|
68
86
|
|
|
69
87
|
for (const upload of this.uploads) {
|
|
70
|
-
|
|
71
88
|
const { title, testId, suite_title } = upload;
|
|
72
89
|
|
|
73
|
-
const files = upload.files.map(
|
|
90
|
+
const files = upload.files.map(attachment => {
|
|
74
91
|
if (attachment.body) {
|
|
75
92
|
const fileName = tmpFile();
|
|
76
93
|
fs.writeFileSync(fileName, attachment.body);
|
|
@@ -78,7 +95,6 @@ class TestomatioReporter {
|
|
|
78
95
|
return { path: attachment.path, title, type: attachment.contentType };
|
|
79
96
|
});
|
|
80
97
|
|
|
81
|
-
|
|
82
98
|
promises.push(
|
|
83
99
|
this.client.addTestRun(undefined, {
|
|
84
100
|
test_id: testId,
|
package/lib/client.js
CHANGED
|
@@ -7,7 +7,6 @@ const { randomUUID } = require('crypto');
|
|
|
7
7
|
const upload = require('./fileUploader');
|
|
8
8
|
const { APP_PREFIX } = require('./constants');
|
|
9
9
|
const pipesFactory = require('./pipe');
|
|
10
|
-
const logger = require('./logger');
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* @typedef {import('../types').TestData} TestData
|
|
@@ -48,13 +47,13 @@ class Client {
|
|
|
48
47
|
};
|
|
49
48
|
|
|
50
49
|
global.testomatioArtifacts = [];
|
|
51
|
-
|
|
50
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
52
51
|
|
|
53
52
|
this.queue = this.queue
|
|
54
53
|
.then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))))
|
|
55
54
|
.catch(err => console.log(APP_PREFIX, err))
|
|
56
55
|
.then(() => undefined); // fixes return type
|
|
57
|
-
debug('Run', this.queue);
|
|
56
|
+
// debug('Run', this.queue);
|
|
58
57
|
return this.queue;
|
|
59
58
|
}
|
|
60
59
|
|
|
@@ -93,20 +92,22 @@ class Client {
|
|
|
93
92
|
|
|
94
93
|
const uploadedFiles = [];
|
|
95
94
|
|
|
96
|
-
let stack = '';
|
|
95
|
+
let stack = testData.stack || '';
|
|
97
96
|
|
|
98
97
|
if (error) {
|
|
99
98
|
stack = this.formatError(error) || '';
|
|
100
99
|
message = error?.message;
|
|
101
100
|
}
|
|
102
101
|
if (steps) {
|
|
103
|
-
stack
|
|
102
|
+
stack += this.formatSteps(stack, steps);
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
// in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
|
|
106
|
+
const logger = require('./logger');
|
|
106
107
|
const testLogs = logger.getLogs(test_id);
|
|
107
|
-
debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
108
|
+
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
108
109
|
if (stack) stack += '\n\n';
|
|
109
|
-
stack += testLogs;
|
|
110
|
+
stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
|
|
110
111
|
|
|
111
112
|
if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
|
|
112
113
|
debug('CLIENT storeArtifact', storeArtifacts);
|
package/lib/dataStorage.js
CHANGED
|
@@ -6,21 +6,41 @@ const { join } = require('path');
|
|
|
6
6
|
const JestReporter = require('./adapter/jest');
|
|
7
7
|
const { TESTOMAT_TMP_STORAGE } = require('./constants');
|
|
8
8
|
const { fileSystem } = require('./util');
|
|
9
|
-
|
|
9
|
+
// const { getCurrentlyRunningTestId } = require('./helpers');
|
|
10
10
|
class DataStorage {
|
|
11
11
|
/**
|
|
12
12
|
* Creates data storage instance for specific data type.
|
|
13
13
|
* Stores data to global variable or to file depending on what is applicable for current test runner.
|
|
14
14
|
* dataType: 'log' | 'artifact' | ...
|
|
15
15
|
* @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
|
|
16
|
-
* @param {*} params
|
|
17
16
|
*/
|
|
18
|
-
constructor(dataType
|
|
19
|
-
if (!dataType) throw new Error('Data type is required when creating data storage');
|
|
17
|
+
constructor(dataType) {
|
|
18
|
+
// if (!dataType) throw new Error('Data type is required when creating data storage');
|
|
20
19
|
this.dataType = dataType || 'data';
|
|
21
|
-
this.dataDirName =
|
|
22
|
-
|
|
20
|
+
this.dataDirName = this.dataType;
|
|
21
|
+
|
|
22
|
+
this._refreshStorageType();
|
|
23
|
+
|
|
24
|
+
// dir is created in any case (not to recheck its existence every time)
|
|
25
|
+
this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataDirName);
|
|
26
|
+
fileSystem.createDir(this.dataDirPath);
|
|
27
|
+
|
|
28
|
+
debug(`Data storage mode: ${this.isFileStorage ? 'file' : 'memory'}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// TODO: implement
|
|
32
|
+
// eslint-disable-next-line no-unused-vars
|
|
33
|
+
getTestIdFromContext(context) {
|
|
34
|
+
// return testId;
|
|
35
|
+
}
|
|
23
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Refreshes storage type (file or global variable) depending on current environment
|
|
39
|
+
* This method should be run on each attempt to put or get data from/to storage
|
|
40
|
+
* Because storage instance is created before the test runner is started
|
|
41
|
+
*/
|
|
42
|
+
_refreshStorageType() {
|
|
43
|
+
this.isFileStorage = !global.testomatioDataStore;
|
|
24
44
|
/*
|
|
25
45
|
FYI:
|
|
26
46
|
If this storage instance is used within test runner, it works fine.
|
|
@@ -30,20 +50,10 @@ class DataStorage {
|
|
|
30
50
|
and potentially useless when get data from storage
|
|
31
51
|
*/
|
|
32
52
|
this.runningEnvironment = this.getRunningEnviroment();
|
|
33
|
-
if (this.runningEnvironment === 'jest') this.isFileStorage = true;
|
|
34
|
-
|
|
35
|
-
if (this.isFileStorage) {
|
|
36
|
-
this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataDirName);
|
|
37
|
-
fileSystem.createDir(this.dataDirPath);
|
|
38
|
-
}
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// TODO: implement
|
|
44
|
-
getTestIdFromContext(context) { // eslint-disable-line
|
|
45
|
-
// eslint-disable-line
|
|
46
|
-
// return testId;
|
|
54
|
+
// jest does not persist global variables, thus file storage is used
|
|
55
|
+
if (this.runningEnvironment === 'jest') this.isFileStorage = true;
|
|
56
|
+
if (this.runningEnvironment === 'playwright') this.isFileStorage = true;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
59
|
/**
|
|
@@ -51,14 +61,28 @@ class DataStorage {
|
|
|
51
61
|
* @returns jest | mocha | ...
|
|
52
62
|
*/
|
|
53
63
|
getRunningEnviroment() {
|
|
64
|
+
// jest
|
|
54
65
|
if (process.env.JEST_WORKER_ID) return 'jest';
|
|
66
|
+
|
|
67
|
+
// mocha
|
|
55
68
|
try {
|
|
56
69
|
// @ts-expect-error mocha is defined only in mocha environment
|
|
57
70
|
if (typeof mocha !== 'undefined') return 'mocha';
|
|
58
71
|
} catch (e) {
|
|
59
72
|
// ignore
|
|
60
73
|
}
|
|
61
|
-
|
|
74
|
+
|
|
75
|
+
// codeceptjs
|
|
76
|
+
// @ts-expect-error codeceptjs is defined only in codeceptjs environment
|
|
77
|
+
if (global.codeceptjs) return 'codeceptjs';
|
|
78
|
+
|
|
79
|
+
// others
|
|
80
|
+
// 'cucumber:current', 'cucumber:legacy'
|
|
81
|
+
if (global.testomatioRunningEnvironment) return global.testomatioRunningEnvironment;
|
|
82
|
+
|
|
83
|
+
if (process.env.PLAYWRIGHT_TEST_BASE_URL) return 'playwright';
|
|
84
|
+
|
|
85
|
+
return null;
|
|
62
86
|
}
|
|
63
87
|
|
|
64
88
|
/**
|
|
@@ -68,7 +92,7 @@ class DataStorage {
|
|
|
68
92
|
* @returns
|
|
69
93
|
*/
|
|
70
94
|
putData(data, context = null) {
|
|
71
|
-
|
|
95
|
+
this._refreshStorageType();
|
|
72
96
|
|
|
73
97
|
let testId = null;
|
|
74
98
|
if (typeof context === 'string') {
|
|
@@ -80,9 +104,17 @@ class DataStorage {
|
|
|
80
104
|
|
|
81
105
|
// try to get testId for Jest
|
|
82
106
|
if (this.runningEnvironment === 'jest') testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
|
|
107
|
+
if (this.runningEnvironment === 'codeceptjs') testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
|
|
108
|
+
// if (this.runningEnvironment === 'playwright') testId = testId ?? getCurrentlyRunningTestId();
|
|
109
|
+
// get id from global store
|
|
110
|
+
if (global.testomatioDataStore && global.testomatioDataStore.currentlyRunningTestId)
|
|
111
|
+
testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
|
|
83
112
|
|
|
84
113
|
// if testId is not provided, data is be saved to `{dataType}_other` file;
|
|
85
|
-
if (!testId)
|
|
114
|
+
if (!testId) {
|
|
115
|
+
debug(`No test id provided for ${this.dataType} data: ${data}`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
86
118
|
|
|
87
119
|
if (this.isFileStorage) {
|
|
88
120
|
this._putDataToFile(data, testId);
|
|
@@ -92,13 +124,17 @@ class DataStorage {
|
|
|
92
124
|
}
|
|
93
125
|
|
|
94
126
|
/**
|
|
95
|
-
* Returns data, stored for specific testId (or data which was stored without test id specified)
|
|
127
|
+
* Returns data, stored for specific testId (or data which was stored without test id specified).
|
|
128
|
+
* This method will get data from global variable. But if it is not available, it will try to get data from file.
|
|
129
|
+
* Thus, good approach is to remove file storage folder before each test run (and after, for sure).
|
|
96
130
|
*
|
|
97
|
-
*
|
|
131
|
+
* Defining the execution environment is not guaranteed! Is used only as additional check.
|
|
98
132
|
* @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
|
|
99
133
|
* @returns
|
|
100
134
|
*/
|
|
101
135
|
getData(context) {
|
|
136
|
+
this._refreshStorageType();
|
|
137
|
+
|
|
102
138
|
let testId = null;
|
|
103
139
|
if (typeof context === 'string') {
|
|
104
140
|
testId = context;
|
|
@@ -109,16 +145,23 @@ class DataStorage {
|
|
|
109
145
|
|
|
110
146
|
if (!testId) {
|
|
111
147
|
debug(`Cannot get test id from passed context:\n}`, context);
|
|
148
|
+
return null;
|
|
112
149
|
}
|
|
113
150
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
151
|
+
let testData = null;
|
|
152
|
+
|
|
117
153
|
if (global?.testomatioDataStore) {
|
|
118
|
-
|
|
154
|
+
testData = this._getDataFromGlobalVar(testId);
|
|
155
|
+
if (testData) return testData;
|
|
119
156
|
}
|
|
157
|
+
|
|
158
|
+
if (this.isFileStorage || !testData) {
|
|
159
|
+
testData = this._getDataFromFile(testId);
|
|
160
|
+
if (testData) return testData;
|
|
161
|
+
}
|
|
162
|
+
|
|
120
163
|
debug(`No ${this.dataType} data for test id ${testId} in both file and global variable`);
|
|
121
|
-
return
|
|
164
|
+
return testData;
|
|
122
165
|
}
|
|
123
166
|
|
|
124
167
|
_getDataFromGlobalVar(testId) {
|
|
@@ -128,7 +171,7 @@ class DataStorage {
|
|
|
128
171
|
debug(`Data for test id ${testId}:\n${testData}`);
|
|
129
172
|
return testData;
|
|
130
173
|
}
|
|
131
|
-
debug(`No ${this.dataType} data for test id ${testId}`);
|
|
174
|
+
debug(`No ${this.dataType} data for test id ${testId} in <global> storage`);
|
|
132
175
|
return null;
|
|
133
176
|
} catch (e) {
|
|
134
177
|
// there could be no data, ignore
|
|
@@ -137,13 +180,13 @@ class DataStorage {
|
|
|
137
180
|
|
|
138
181
|
_getDataFromFile(testId) {
|
|
139
182
|
try {
|
|
140
|
-
const filepath = join(
|
|
183
|
+
const filepath = join(this.dataDirPath, `${this.dataType}_${testId}`);
|
|
141
184
|
if (fs.existsSync(filepath)) {
|
|
142
185
|
const testData = fs.readFileSync(filepath, 'utf-8');
|
|
143
186
|
debug(`Data for test id ${testId}:\n${testData}`);
|
|
144
187
|
return testData;
|
|
145
188
|
}
|
|
146
|
-
debug(`No ${this.dataType} data for test id ${testId}`);
|
|
189
|
+
debug(`No ${this.dataType} data for test id ${testId} in <file> storage`);
|
|
147
190
|
return null;
|
|
148
191
|
} catch (e) {
|
|
149
192
|
// there could be no data, ignore
|
|
@@ -153,14 +196,16 @@ class DataStorage {
|
|
|
153
196
|
|
|
154
197
|
_putDataToGlobalVar(data, testId) {
|
|
155
198
|
debug('Saving data to global variable for test', testId, ':\n', data, '\n');
|
|
156
|
-
global.testomatioDataStore[this.dataDirName] = {};
|
|
157
|
-
global.testomatioDataStore[this.dataDirName][testId]
|
|
199
|
+
if (!global.testomatioDataStore[this.dataDirName]) global.testomatioDataStore[this.dataDirName] = {};
|
|
200
|
+
global.testomatioDataStore[this.dataDirName][testId] // eslint-disable-line no-unused-expressions
|
|
201
|
+
? (global.testomatioDataStore[this.dataDirName][testId] += `\n${data}`)
|
|
202
|
+
: (global.testomatioDataStore[this.dataDirName][testId] = data);
|
|
158
203
|
}
|
|
159
204
|
|
|
160
205
|
_putDataToFile(data, testId) {
|
|
161
206
|
if (typeof data !== 'string') data = JSON.stringify(data);
|
|
162
207
|
const filename = `${this.dataType}_${testId}`;
|
|
163
|
-
const filepath = join(
|
|
208
|
+
const filepath = join(this.dataDirPath, filename);
|
|
164
209
|
debug('Saving data to file for test', testId, 'to', filepath, ':\n', data, '\n');
|
|
165
210
|
|
|
166
211
|
// TODO: handle multiple invocations of JSON.stringify.
|
|
@@ -177,4 +222,20 @@ module.exports.DataStorage = DataStorage;
|
|
|
177
222
|
// TODO: rewrite client - everything regarding storing artifacts
|
|
178
223
|
// TODO: try to define adapter inside client
|
|
179
224
|
// TODO: use .env
|
|
180
|
-
// TODO: ability to intercept multiple loggers
|
|
225
|
+
// TODO: ability to intercept multiple loggers (upd: no need)
|
|
226
|
+
|
|
227
|
+
/* Playwright
|
|
228
|
+
There is no global scope sharing between tests and reporter listeners.
|
|
229
|
+
Thus need to use file storage. But when tests are run in parallel, the new problem occurs.
|
|
230
|
+
Tried to solve it by using the TEST_WORKER_INDEX env variable, but it is available inside the listener
|
|
231
|
+
(available only inside the test). Thus when cannot match currently running test id from the reporter listener
|
|
232
|
+
with the currently running test.
|
|
233
|
+
The only solution I see for now is to pass the test id to the logger from the test itself.
|
|
234
|
+
By passing the testInfo param to the test function:
|
|
235
|
+
async ({ page }, testInfo)
|
|
236
|
+
and then retrieving the .title from it.
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
/* Cypress
|
|
240
|
+
Parallelization is only available if using Cypress Dashboard??
|
|
241
|
+
*/
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const { TESTOMAT_TMP_STORAGE } = require('./constants');
|
|
4
|
+
|
|
5
|
+
// NEXT HELPERS ARE CREATED FOR PLAYWRIGHT. IGNORE FOR NOW. PROBABLY WILL BE REMOVED SHORTLY
|
|
6
|
+
|
|
7
|
+
// TEST_WORKER_INDEX is not available inside the test listener :/
|
|
8
|
+
/**
|
|
9
|
+
* Sets id of currently running test to storage (global variable or file)
|
|
10
|
+
* Used for Playwright
|
|
11
|
+
* @param {*} testId
|
|
12
|
+
*/
|
|
13
|
+
function setCurrentlyRunningTestIdForPlaywright(testId) {
|
|
14
|
+
const workerId = +process.env.TEST_WORKER_INDEX || 0;
|
|
15
|
+
const filePath = path.join(TESTOMAT_TMP_STORAGE.mainDir, 'current_test_id_' + workerId);
|
|
16
|
+
fs.writeFileSync(filePath, testId);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Gets id of currently running test from storage (global variable or file)
|
|
21
|
+
* Used for Playwright
|
|
22
|
+
* @returns string test id of currently running test
|
|
23
|
+
*/
|
|
24
|
+
function getCurrentlyRunningTestIdForPlaywright() {
|
|
25
|
+
const workerId = +process.env.TEST_WORKER_INDEX || 0;
|
|
26
|
+
const filePath = path.join(TESTOMAT_TMP_STORAGE.mainDir, 'current_test_id_' + workerId);
|
|
27
|
+
if (fs.existsSync(filePath)) {
|
|
28
|
+
return fs.readFileSync(filePath, 'utf8') || null;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
module.exports.setCurrentlyRunningTestId = setCurrentlyRunningTestIdForPlaywright;
|
|
34
|
+
module.exports.getCurrentlyRunningTestId = getCurrentlyRunningTestIdForPlaywright;
|
package/lib/logger.js
CHANGED
|
@@ -24,11 +24,11 @@ class Logger {
|
|
|
24
24
|
// _originalUserLogger used to output logs to console by the user logger
|
|
25
25
|
// _loggerToIntercept intercepted and reassigned immediately when added
|
|
26
26
|
|
|
27
|
-
constructor(
|
|
27
|
+
constructor() {
|
|
28
28
|
// set default logger to be used in log, warn, error, etc methods
|
|
29
29
|
this._originalUserLogger = { ...console };
|
|
30
30
|
|
|
31
|
-
this.dataStorage = new DataStorage('log'
|
|
31
|
+
this.dataStorage = new DataStorage('log');
|
|
32
32
|
this.logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
|
|
33
33
|
|
|
34
34
|
// commented because prefer to use "intercept" method
|
|
@@ -77,8 +77,8 @@ class Logger {
|
|
|
77
77
|
/**
|
|
78
78
|
* Allows you to define a step inside a test. Step name is attached to the report and
|
|
79
79
|
* helps to understand the test flow.
|
|
80
|
-
* @param {*} strings
|
|
81
|
-
* @param {...any} values
|
|
80
|
+
* @param {*} strings
|
|
81
|
+
* @param {...any} values
|
|
82
82
|
*/
|
|
83
83
|
step(strings, ...values) {
|
|
84
84
|
let logs = '';
|
|
@@ -98,7 +98,8 @@ class Logger {
|
|
|
98
98
|
* @returns
|
|
99
99
|
*/
|
|
100
100
|
getLogs(context) {
|
|
101
|
-
|
|
101
|
+
const logs = this.dataStorage.getData(context);
|
|
102
|
+
return logs || '';
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
_strinfifyLogs(...args) {
|
|
@@ -121,6 +122,9 @@ class Logger {
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
assert(...args) {
|
|
125
|
+
// sometimes user invokes logger without any arguments passed
|
|
126
|
+
if (!args.length) return;
|
|
127
|
+
|
|
124
128
|
const level = 'ERROR';
|
|
125
129
|
const severity = LEVELS[level].severity;
|
|
126
130
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
@@ -135,6 +139,8 @@ class Logger {
|
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
debug(...args) {
|
|
142
|
+
if (!args.length) return;
|
|
143
|
+
|
|
138
144
|
const level = 'DEBUG';
|
|
139
145
|
const severity = LEVELS[level].severity;
|
|
140
146
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
@@ -151,6 +157,8 @@ class Logger {
|
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
error(...args) {
|
|
160
|
+
if (!args.length) return;
|
|
161
|
+
|
|
154
162
|
const level = 'ERROR';
|
|
155
163
|
const severity = LEVELS[level].severity;
|
|
156
164
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
@@ -166,6 +174,8 @@ class Logger {
|
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
info(...args) {
|
|
177
|
+
if (!args.length) return;
|
|
178
|
+
|
|
169
179
|
const level = 'INFO';
|
|
170
180
|
const severity = LEVELS[level].severity;
|
|
171
181
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
@@ -180,6 +190,8 @@ class Logger {
|
|
|
180
190
|
}
|
|
181
191
|
|
|
182
192
|
log(...args) {
|
|
193
|
+
if (!args.length) return;
|
|
194
|
+
|
|
183
195
|
const level = 'INFO';
|
|
184
196
|
const severity = LEVELS[level].severity;
|
|
185
197
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
@@ -194,6 +206,8 @@ class Logger {
|
|
|
194
206
|
}
|
|
195
207
|
|
|
196
208
|
trace(...args) {
|
|
209
|
+
if (!args.length) return;
|
|
210
|
+
|
|
197
211
|
const level = 'TRACE';
|
|
198
212
|
const severity = LEVELS[level].severity;
|
|
199
213
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
@@ -209,6 +223,8 @@ class Logger {
|
|
|
209
223
|
}
|
|
210
224
|
|
|
211
225
|
warn(...args) {
|
|
226
|
+
if (!args.length) return;
|
|
227
|
+
|
|
212
228
|
const level = 'WARN';
|
|
213
229
|
const severity = LEVELS[level].severity;
|
|
214
230
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
@@ -233,7 +249,7 @@ class Logger {
|
|
|
233
249
|
*/
|
|
234
250
|
intercept(userLogger) {
|
|
235
251
|
if (!userLogger) return;
|
|
236
|
-
debug(`
|
|
252
|
+
debug(`User logger intercepted`);
|
|
237
253
|
|
|
238
254
|
// save original user logger to use it for logging (last intercepted will be used for console output)
|
|
239
255
|
this._originalUserLogger = { ...userLogger };
|
|
@@ -275,4 +291,3 @@ module.exports = new Logger();
|
|
|
275
291
|
|
|
276
292
|
// TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
|
|
277
293
|
// upd: did not face such loggers, but still could be useful
|
|
278
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3-beta",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@cucumber/cucumber": "^8.6.0",
|
|
55
54
|
"@redocly/cli": "^1.0.0-beta.125",
|
|
56
55
|
"@wdio/reporter": "^7.16.13",
|
|
57
56
|
"chai": "^4.3.6",
|