@testomatio/reporter 1.2.2-beta → 1.2.2-beta-html-pagination-feature
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -54
- package/lib/adapter/codecept.js +140 -61
- package/lib/adapter/cucumber/current.js +103 -60
- package/lib/adapter/cucumber/legacy.js +27 -12
- package/lib/adapter/cucumber.js +2 -2
- package/lib/adapter/cypress-plugin/index.js +52 -25
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +49 -11
- package/lib/adapter/mocha.js +103 -51
- package/lib/adapter/playwright.js +100 -31
- package/lib/adapter/webdriver.js +1 -1
- package/lib/bin/reportXml.js +14 -13
- package/lib/bin/startTest.js +27 -6
- package/lib/client.js +193 -69
- package/lib/config.js +34 -0
- package/lib/constants.js +19 -7
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +128 -53
- package/lib/junit-adapter/adapter.js +0 -2
- package/lib/junit-adapter/csharp.js +3 -4
- package/lib/junit-adapter/index.js +3 -3
- package/lib/junit-adapter/java.js +35 -17
- package/lib/junit-adapter/javascript.js +1 -2
- package/lib/junit-adapter/python.js +12 -14
- package/lib/junit-adapter/ruby.js +1 -2
- package/lib/pipe/csv.js +5 -3
- package/lib/pipe/github.js +27 -39
- package/lib/pipe/gitlab.js +20 -24
- package/lib/pipe/html.js +360 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +183 -56
- package/lib/reporter-functions.js +46 -0
- package/lib/reporter.js +11 -9
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/services/key-values.js +58 -0
- package/lib/services/logger.js +311 -0
- package/lib/template/testomatio.hbs +1226 -0
- package/lib/utils/pipe_utils.js +128 -0
- package/lib/{util.js → utils/utils.js} +144 -12
- package/lib/xmlReader.js +211 -122
- package/package.json +18 -8
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -180
- package/lib/logger.js +0 -278
|
@@ -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_DIR } = require('../../constants');
|
|
6
6
|
const TestomatClient = require('../../client');
|
|
7
|
+
const { parseTest, fileSystem } = require('../../utils/utils');
|
|
8
|
+
const config = require('../../config');
|
|
9
|
+
const { services } = require('../../services');
|
|
10
|
+
|
|
11
|
+
const { GherkinDocumentParser, PickleParser } = formatterHelpers;
|
|
12
|
+
const { getGherkinScenarioLocationMap, getGherkinStepMap } = GherkinDocumentParser;
|
|
13
|
+
const { getPickleStepMap } = PickleParser;
|
|
14
|
+
|
|
15
|
+
function getTestId(scenario) {
|
|
16
|
+
if (scenario) {
|
|
17
|
+
for (const tag of scenario.tags) {
|
|
18
|
+
const testId = parseTest(tag.name);
|
|
19
|
+
if (testId) return testId;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
7
22
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
getGherkinScenarioLocationMap,
|
|
11
|
-
getGherkinStepMap,
|
|
12
|
-
} = GherkinDocumentParser
|
|
13
|
-
const { getPickleStepMap } = PickleParser
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
14
25
|
|
|
15
26
|
class CucumberReporter extends Formatter {
|
|
16
27
|
constructor(options) {
|
|
17
28
|
super(options);
|
|
18
|
-
options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this))
|
|
29
|
+
options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this));
|
|
19
30
|
this.failures = [];
|
|
20
31
|
this.cases = [];
|
|
21
32
|
|
|
22
|
-
this.client = new TestomatClient();
|
|
33
|
+
this.client = new TestomatClient({ apiKey: options.apiKey || config.TESTOMATIO });
|
|
23
34
|
this.status = STATUS.PASSED;
|
|
24
|
-
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
parseEnvelope(envelope) {
|
|
28
|
-
if (envelope.
|
|
38
|
+
if (envelope.testRunStarted) {
|
|
39
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
40
|
+
}
|
|
41
|
+
if (envelope.testCaseStarted && this.client) {
|
|
42
|
+
this.client.createRun();
|
|
43
|
+
this.onTestCaseStarted(envelope.testCaseStarted);
|
|
44
|
+
}
|
|
29
45
|
if (envelope.testCaseFinished) this.onTestCaseFinished(envelope.testCaseFinished);
|
|
30
46
|
if (envelope.testRunFinished) this.onTestRunFinished(envelope);
|
|
31
47
|
}
|
|
32
48
|
|
|
49
|
+
onTestCaseStarted(testCaseStarted) {
|
|
50
|
+
const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseStarted.id);
|
|
51
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
52
|
+
|
|
53
|
+
const testTitle = testCaseAttempt.pickle.name + testCaseAttempt.pickle.uri;
|
|
54
|
+
services.setContext(testTitle);
|
|
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,19 +105,32 @@ 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 testTitle = testCaseAttempt.pickle.name + testCaseAttempt.pickle.uri;
|
|
113
|
+
const logs = services.logger.getLogs(testTitle).join('\n');
|
|
114
|
+
const artifacts = services.artifacts.get(testTitle);
|
|
115
|
+
const keyValues = services.keyValues.get(testTitle);
|
|
116
|
+
|
|
86
117
|
this.client.addTestRun(status, {
|
|
87
118
|
// error: testCaseAttempt.worstTestStepResult.message,
|
|
88
119
|
message,
|
|
89
|
-
steps: getSteps(testCaseAttempt)
|
|
90
|
-
|
|
120
|
+
steps: getSteps(testCaseAttempt)
|
|
121
|
+
.map(s => s.toString())
|
|
122
|
+
.join('\n')
|
|
123
|
+
.trim(),
|
|
124
|
+
example: { ...example },
|
|
125
|
+
logs,
|
|
126
|
+
manuallyAttachedArtifacts: artifacts,
|
|
127
|
+
meta: keyValues,
|
|
91
128
|
title: scenario,
|
|
92
129
|
test_id: testId,
|
|
93
130
|
time,
|
|
94
131
|
});
|
|
132
|
+
|
|
133
|
+
services.setContext(null);
|
|
95
134
|
}
|
|
96
135
|
|
|
97
136
|
onTestRunFinished(envelope) {
|
|
@@ -99,21 +138,20 @@ class CucumberReporter extends Formatter {
|
|
|
99
138
|
console.log(chalk.bold('\nSUMMARY:\n\n'));
|
|
100
139
|
|
|
101
140
|
this.failures.forEach((tc, i) => {
|
|
102
|
-
let message = ` ${i+1}) ${tc.pickle.name}\n`;
|
|
141
|
+
let message = ` ${i + 1}) ${tc.pickle.name}\n`;
|
|
103
142
|
|
|
104
143
|
const steps = getSteps(tc);
|
|
105
144
|
|
|
106
145
|
steps.forEach(s => {
|
|
107
|
-
message += ` ${s.toString()}\n
|
|
108
|
-
})
|
|
146
|
+
message += ` ${s.toString()}\n`;
|
|
147
|
+
});
|
|
109
148
|
|
|
110
149
|
console.log(message);
|
|
111
150
|
if (tc?.worstTestStepResult?.message) {
|
|
112
151
|
console.log(chalk.red(tc?.worstTestStepResult?.message));
|
|
113
152
|
}
|
|
114
153
|
console.log();
|
|
115
|
-
|
|
116
|
-
})
|
|
154
|
+
});
|
|
117
155
|
}
|
|
118
156
|
|
|
119
157
|
const { testRunFinished } = envelope;
|
|
@@ -125,46 +163,48 @@ class CucumberReporter extends Formatter {
|
|
|
125
163
|
|
|
126
164
|
if (!this.client) return;
|
|
127
165
|
|
|
128
|
-
this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED)
|
|
166
|
+
this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED);
|
|
129
167
|
}
|
|
130
168
|
}
|
|
131
169
|
|
|
132
170
|
function getSteps(tc) {
|
|
133
171
|
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
|
-
|
|
172
|
+
const pickleSteps = getPickleStepMap(tc.pickle);
|
|
173
|
+
return stepIds
|
|
174
|
+
.map(stepId => {
|
|
175
|
+
const ts = tc.testCase.testSteps.find(t => t.id === stepId);
|
|
176
|
+
if (!ts) return;
|
|
177
|
+
if (!ts.pickleStepId) return;
|
|
178
|
+
const result = tc.stepResults[stepId];
|
|
179
|
+
const pickleStep = pickleSteps[ts.pickleStepId];
|
|
180
|
+
const sourceStepId = pickleStep.astNodeIds[0];
|
|
181
|
+
const step = {
|
|
182
|
+
text: pickleStep.text,
|
|
183
|
+
duration: result.duration,
|
|
184
|
+
status: result.status,
|
|
185
|
+
};
|
|
186
|
+
const color = getStatusColor(result.status);
|
|
187
|
+
if (sourceStepId && getGherkinStepMap(tc.gherkinDocument)[sourceStepId]) {
|
|
188
|
+
step.keyword = getGherkinStepMap(tc.gherkinDocument)[sourceStepId].keyword;
|
|
189
|
+
}
|
|
190
|
+
step.toString = function toString() {
|
|
191
|
+
const duration = step.duration.seconds * 1000 + step.duration.nanos / 1000000;
|
|
192
|
+
const durationString = ` ${chalk.gray(`(${Number(duration).toFixed(2)}ms)`)}`;
|
|
193
|
+
const stepString = `${chalk.bold(this.keyword)}${this.text}`.trim();
|
|
194
|
+
if (color === 'red') return chalk.red(stepString) + durationString;
|
|
195
|
+
if (color === 'yellow') return chalk.yellow(stepString) + durationString;
|
|
196
|
+
return stepString + durationString;
|
|
197
|
+
};
|
|
198
|
+
return step;
|
|
199
|
+
})
|
|
200
|
+
.filter(s => !!s);
|
|
161
201
|
}
|
|
162
202
|
|
|
163
203
|
function getStatusColor(status) {
|
|
164
204
|
if (status === 'UNDEFINED') return 'yellow';
|
|
165
205
|
if (status === 'SKIPPED') return 'yellow';
|
|
166
206
|
if (status === 'FAILED') return 'red';
|
|
167
|
-
return 'green'
|
|
207
|
+
return 'green';
|
|
168
208
|
}
|
|
169
209
|
|
|
170
210
|
function getExample(testCaseAttempt) {
|
|
@@ -173,11 +213,14 @@ function getExample(testCaseAttempt) {
|
|
|
173
213
|
if (!nodesMap[exampleNodeId]) return;
|
|
174
214
|
const featureDoc = fs.readFileSync(testCaseAttempt.gherkinDocument.uri).toString();
|
|
175
215
|
const { line } = nodesMap[exampleNodeId];
|
|
176
|
-
const example = featureDoc.split('\n')[line-1];
|
|
216
|
+
const example = featureDoc.split('\n')[line - 1];
|
|
177
217
|
if (example) {
|
|
178
|
-
return example
|
|
179
|
-
|
|
180
|
-
|
|
218
|
+
return example
|
|
219
|
+
.trim()
|
|
220
|
+
.split('|')
|
|
221
|
+
.filter(r => !!r)
|
|
222
|
+
.map(r => r.trim());
|
|
223
|
+
}
|
|
181
224
|
}
|
|
182
225
|
|
|
183
226
|
module.exports = CucumberReporter;
|
|
@@ -1,9 +1,10 @@
|
|
|
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_DIR } = require('../../constants');
|
|
6
6
|
const TestomatClient = require('../../client');
|
|
7
|
+
const config = require('../../config');
|
|
7
8
|
|
|
8
9
|
const createTestomatFormatter = apiKey => {
|
|
9
10
|
if (!apiKey || apiKey === '') {
|
|
@@ -96,9 +97,23 @@ const createTestomatFormatter = apiKey => {
|
|
|
96
97
|
this.status = STATUS.PASSED.toString();
|
|
97
98
|
|
|
98
99
|
options.eventBroadcaster.on('gherkin-document', addDocument);
|
|
99
|
-
options.eventBroadcaster.on('test-run-started', () =>
|
|
100
|
+
options.eventBroadcaster.on('test-run-started', () => {
|
|
101
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
102
|
+
this?.client?.createRun();
|
|
103
|
+
});
|
|
100
104
|
options.eventBroadcaster.on('test-case-finished', this.onTestCaseFinished.bind(this));
|
|
105
|
+
options.eventBroadcaster.on('test-case-started', this.onTestCaseStarted.bind(this));
|
|
101
106
|
options.eventBroadcaster.on('test-run-finished', () => this?.client?.updateRunStatus(this.status));
|
|
107
|
+
|
|
108
|
+
global.testomatioRunningEnvironment = 'cucumber:legacy';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
onTestCaseStarted(event) {
|
|
112
|
+
const scenario = getScenario(event.sourceLocation);
|
|
113
|
+
const testId = getTestId(scenario);
|
|
114
|
+
|
|
115
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
116
|
+
global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
onTestCaseFinished(event) {
|
|
@@ -111,15 +126,15 @@ const createTestomatFormatter = apiKey => {
|
|
|
111
126
|
|
|
112
127
|
if (!scenario.name) return;
|
|
113
128
|
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
const message = '';
|
|
130
|
+
const cliMessage = `- ${scenario.name}: ${chalk.bold(status.toUpperCase())}`;
|
|
116
131
|
|
|
117
|
-
if (event.result.status === 'undefined') {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
132
|
+
// if (event.result.status === 'undefined') {
|
|
133
|
+
// cliMessage += chalk.yellow(
|
|
134
|
+
// ' (undefined steps. Run Cucumber without this formatter and implement missing steps)',
|
|
135
|
+
// );
|
|
136
|
+
// message = 'Undefined steps. Implement missing steps and rerun this scenario';
|
|
137
|
+
// }
|
|
123
138
|
console.log(cliMessage);
|
|
124
139
|
if (status !== STATUS.PASSED && status !== STATUS.SKIPPED) {
|
|
125
140
|
this.status = STATUS.FAILED;
|
|
@@ -137,4 +152,4 @@ const createTestomatFormatter = apiKey => {
|
|
|
137
152
|
};
|
|
138
153
|
};
|
|
139
154
|
|
|
140
|
-
module.exports = createTestomatFormatter(
|
|
155
|
+
module.exports = createTestomatFormatter(config.TESTOMATIO);
|
package/lib/adapter/cucumber.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
try {
|
|
2
2
|
// eslint-disable-next-line import/no-unresolved
|
|
3
|
-
require.resolve(
|
|
3
|
+
require.resolve('@cucumber/cucumber');
|
|
4
4
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
5
5
|
module.exports = require('./cucumber/current');
|
|
6
6
|
} catch (_e) {
|
|
@@ -11,6 +11,6 @@ try {
|
|
|
11
11
|
module.exports = require('./cucumber/legacy');
|
|
12
12
|
} catch (_err) {
|
|
13
13
|
console.error('Cucumber packages: "@cucumber/cucumber" or "cucumber" were not detected. Report won\'t be sent');
|
|
14
|
-
module.exports = {}
|
|
14
|
+
module.exports = {};
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
const { STATUS } = require('../../constants');
|
|
2
|
-
const { parseTest, parseSuite } = require('../../
|
|
2
|
+
const { parseTest, parseSuite } = require('../../utils/utils');
|
|
3
3
|
const TestomatClient = require('../../client');
|
|
4
|
+
const config = require('../../config');
|
|
4
5
|
|
|
5
6
|
const testomatioReporter = on => {
|
|
6
|
-
if (!
|
|
7
|
+
if (!config.TESTOMATIO) {
|
|
7
8
|
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
8
|
-
return
|
|
9
|
+
return;
|
|
9
10
|
}
|
|
10
|
-
const client = new TestomatClient({ apiKey:
|
|
11
|
+
const client = new TestomatClient({ apiKey: config.TESTOMATIO });
|
|
11
12
|
|
|
12
|
-
on('before:run', async
|
|
13
|
+
on('before:run', async run => {
|
|
14
|
+
// TODO: looks like client.env does not exist
|
|
13
15
|
if (!client.env) {
|
|
14
|
-
client.env = `${run.browser.displayName},${run.system.osName}
|
|
16
|
+
client.env = `${run.browser.displayName},${run.system.osName}`;
|
|
15
17
|
}
|
|
16
18
|
await client.createRun();
|
|
17
19
|
});
|
|
@@ -25,8 +27,9 @@ const testomatioReporter = on => {
|
|
|
25
27
|
const lastAttemptIndex = test.attempts.length - 1;
|
|
26
28
|
const latestAttempt = test.attempts[lastAttemptIndex];
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
+
// latestAttempt.duration && latestAttempt.error were available in adapters version up to 13 JFYI
|
|
31
|
+
const time = latestAttempt.duration || latestAttempt.wallClockDuration || test.duration;
|
|
32
|
+
let error = latestAttempt.error;
|
|
30
33
|
|
|
31
34
|
let title = test.title.pop();
|
|
32
35
|
const examples = title.match(/\(example (#\d+)\)/);
|
|
@@ -39,35 +42,59 @@ const testomatioReporter = on => {
|
|
|
39
42
|
const testId = parseTest(title);
|
|
40
43
|
const suiteId = parseSuite(suiteTitle);
|
|
41
44
|
|
|
42
|
-
if (error) {
|
|
43
|
-
error
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
45
|
+
if (!error && test.displayError) {
|
|
46
|
+
error = { message: test.displayError };
|
|
47
|
+
error.inspect = function () {
|
|
48
|
+
// eslint-disable-line func-names
|
|
49
|
+
return this.message;
|
|
50
|
+
};
|
|
49
51
|
}
|
|
50
52
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const formattedError = error
|
|
54
|
+
? {
|
|
55
|
+
message: error.message,
|
|
56
|
+
inspect:
|
|
57
|
+
error.inspect ||
|
|
58
|
+
function () {
|
|
59
|
+
return this.message;
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
: '';
|
|
63
|
+
|
|
64
|
+
const screenshots = Array.isArray(results.screenshots)
|
|
65
|
+
? results.screenshots
|
|
66
|
+
.filter(screenshot => screenshot?.path && screenshot?.path.includes(title) && screenshot?.takenAt)
|
|
67
|
+
.map(screenshot => screenshot.path)
|
|
68
|
+
: [];
|
|
55
69
|
|
|
56
70
|
const files = [...videos, ...screenshots];
|
|
57
71
|
|
|
58
72
|
let state;
|
|
59
73
|
switch (test.state) {
|
|
60
|
-
case 'passed':
|
|
61
|
-
|
|
74
|
+
case 'passed':
|
|
75
|
+
state = STATUS.PASSED;
|
|
76
|
+
break;
|
|
77
|
+
case 'failed':
|
|
78
|
+
state = STATUS.FAILED;
|
|
79
|
+
break;
|
|
62
80
|
case 'skipped':
|
|
63
|
-
case 'pending':
|
|
81
|
+
case 'pending':
|
|
64
82
|
default:
|
|
65
83
|
state = STATUS.SKIPPED;
|
|
66
84
|
}
|
|
67
85
|
|
|
68
|
-
addSpecTestsPromises.push(
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
addSpecTestsPromises.push(
|
|
87
|
+
client.addTestRun(state, {
|
|
88
|
+
title,
|
|
89
|
+
time,
|
|
90
|
+
example,
|
|
91
|
+
error: formattedError,
|
|
92
|
+
files,
|
|
93
|
+
suite_title: suiteTitle,
|
|
94
|
+
test_id: testId,
|
|
95
|
+
suite_id: suiteId,
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
71
98
|
}
|
|
72
99
|
|
|
73
100
|
await Promise.all(addSpecTestsPromises);
|
package/lib/adapter/jasmine.js
CHANGED
package/lib/adapter/jest.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
1
2
|
const TestomatClient = require('../client');
|
|
2
|
-
const { STATUS,
|
|
3
|
-
const { parseTest, ansiRegExp, fileSystem } = require('../
|
|
3
|
+
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
4
|
+
const { parseTest, ansiRegExp, fileSystem } = require('../utils/utils');
|
|
5
|
+
const { services } = require('../services');
|
|
6
|
+
const debug = require('debug')('@testomatio/reporter:adapter-jest');
|
|
7
|
+
const path = require('path');
|
|
4
8
|
|
|
5
9
|
class JestReporter {
|
|
6
10
|
constructor(globalConfig, options) {
|
|
@@ -11,17 +15,24 @@ class JestReporter {
|
|
|
11
15
|
this.client.createRun();
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
static getIdOfCurrentlyRunningTest() {
|
|
15
|
-
if (!process.env.JEST_WORKER_ID) return null;
|
|
16
|
-
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
17
|
-
return parseTest(expect?.getState()?.currentTestName) || null; // eslint-disable-line
|
|
18
|
-
}
|
|
19
|
-
|
|
20
18
|
onRunStart() {
|
|
21
19
|
// clear tmp dir
|
|
22
|
-
fileSystem.clearDir(
|
|
20
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// start of test file (including beforeAll)
|
|
24
|
+
onTestStart(testFile) {
|
|
25
|
+
debug('Start running test file:', testFile.path);
|
|
26
|
+
services.setContext(testFile.path);
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
// start of the test (including beforeEach)
|
|
30
|
+
onTestCaseStart(test, testCase) {
|
|
31
|
+
debug('Start running test:', testCase.fullName);
|
|
32
|
+
services.setContext(testCase.fullName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// end of test file! (there is also onTestCaseResult listener)
|
|
25
36
|
onTestResult(test, testResult) {
|
|
26
37
|
if (!this.client) return;
|
|
27
38
|
|
|
@@ -37,16 +48,30 @@ class JestReporter {
|
|
|
37
48
|
steps = failureMessages[0];
|
|
38
49
|
}
|
|
39
50
|
const testId = parseTest(title);
|
|
51
|
+
|
|
52
|
+
// suite titles from most outer to most inner, separated by space
|
|
53
|
+
let fullSuiteTitle = testResult.ancestorTitles?.join(' ');
|
|
54
|
+
// if no suite titles, use file name
|
|
55
|
+
if (!fullSuiteTitle && testResult.testFilePath) fullSuiteTitle = path.basename(testResult.testFilePath);
|
|
56
|
+
|
|
57
|
+
const logs = getTestLogs(result);
|
|
58
|
+
const artifacts = services.artifacts.get(result.fullName);
|
|
59
|
+
const keyValues = services.keyValues.get(result.fullName);
|
|
60
|
+
|
|
40
61
|
const deducedStatus = status === 'pending' ? 'skipped' : status;
|
|
41
62
|
// In jest if test is not matched with test name pattern it is considered as skipped.
|
|
42
63
|
// So adding a check if it is skipped for real or because of test pattern
|
|
43
64
|
if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
|
|
44
65
|
this.client.addTestRun(deducedStatus, {
|
|
45
66
|
test_id: testId,
|
|
67
|
+
suite_title: fullSuiteTitle,
|
|
46
68
|
error,
|
|
47
69
|
steps,
|
|
48
70
|
title,
|
|
49
71
|
time: duration,
|
|
72
|
+
logs,
|
|
73
|
+
manuallyAttachedArtifacts: artifacts,
|
|
74
|
+
meta: keyValues,
|
|
50
75
|
});
|
|
51
76
|
}
|
|
52
77
|
}
|
|
@@ -58,10 +83,23 @@ class JestReporter {
|
|
|
58
83
|
const { numFailedTests } = results;
|
|
59
84
|
const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
60
85
|
this.client.updateRunStatus(status);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
function getTestLogs(testResult) {
|
|
90
|
+
const suiteLogsArr = services.logger.getLogs(testResult.testFilePath);
|
|
91
|
+
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
92
|
+
const testLogsArr = services.logger.getLogs(testResult.fullName);
|
|
93
|
+
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
94
|
+
|
|
95
|
+
let logs = '';
|
|
96
|
+
if (suiteLogs) {
|
|
97
|
+
logs += `${chalk.bold('\t--- Suite ---')}\n${suiteLogs}`;
|
|
98
|
+
}
|
|
99
|
+
if (testLogs) {
|
|
100
|
+
logs += `\n${chalk.bold('\t--- Test ---')}\n${testLogs}`;
|
|
64
101
|
}
|
|
102
|
+
return logs;
|
|
65
103
|
}
|
|
66
104
|
|
|
67
105
|
module.exports = JestReporter;
|