testomatio-reporter-cli 2.8.4 → 2.8.5-beta.2-yarn
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 +3 -3
- package/bin/cli.js +6 -26
- package/package.json +39 -4
- package/src/adapter/codecept.js +626 -0
- package/src/adapter/cucumber/current.js +230 -0
- package/src/adapter/cucumber/legacy.js +158 -0
- package/src/adapter/cucumber.js +4 -0
- package/src/adapter/cypress-plugin/index.js +110 -0
- package/src/adapter/jasmine.js +60 -0
- package/src/adapter/jest.js +108 -0
- package/src/adapter/mocha.cjs +2 -0
- package/src/adapter/mocha.js +211 -0
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/playwright.js +343 -0
- package/src/adapter/utils/playwright.js +121 -0
- package/src/adapter/utils/step-formatter.js +232 -0
- package/src/adapter/vitest.js +455 -0
- package/src/adapter/webdriver.js +201 -0
- package/src/bin/cli.js +507 -0
- package/src/bin/reportXml.js +79 -0
- package/src/bin/startTest.js +54 -0
- package/src/bin/uploadArtifacts.js +91 -0
- package/src/client.js +524 -0
- package/src/config.js +30 -0
- package/src/constants.js +72 -0
- package/src/data-storage.js +204 -0
- package/src/helpers.js +1 -0
- package/src/junit-adapter/adapter.js +23 -0
- package/src/junit-adapter/csharp.js +70 -0
- package/src/junit-adapter/index.js +28 -0
- package/src/junit-adapter/java.js +58 -0
- package/src/junit-adapter/javascript.js +31 -0
- package/src/junit-adapter/nunit-parser.js +474 -0
- package/src/junit-adapter/python.js +42 -0
- package/src/junit-adapter/ruby.js +10 -0
- package/src/output.js +57 -0
- package/src/pipe/bitbucket.js +285 -0
- package/src/pipe/coverage.js +500 -0
- package/src/pipe/csv.js +161 -0
- package/src/pipe/debug.js +143 -0
- package/src/pipe/github.js +256 -0
- package/src/pipe/gitlab.js +258 -0
- package/src/pipe/html.js +1153 -0
- package/src/pipe/index.js +73 -0
- package/src/pipe/markdown.js +753 -0
- package/src/pipe/testomatio.js +707 -0
- package/src/replay.js +274 -0
- package/src/reporter-functions.js +155 -0
- package/src/reporter.js +42 -0
- package/src/services/artifacts.js +59 -0
- package/src/services/index.js +15 -0
- package/src/services/key-values.js +59 -0
- package/src/services/links.js +69 -0
- package/src/services/logger.js +320 -0
- package/src/template/emptyData.svg +23 -0
- package/src/template/testomatio-old.hbs +1421 -0
- package/src/template/testomatio.hbs +3726 -0
- package/src/uploader.js +382 -0
- package/src/utils/constants.js +12 -0
- package/src/utils/debug.js +20 -0
- package/src/utils/log-formatter.js +118 -0
- package/src/utils/log.js +88 -0
- package/src/utils/pipe_utils.js +193 -0
- package/src/utils/utils.js +732 -0
- package/src/xmlReader.js +834 -0
- package/types/types.d.ts +425 -0
- package/types/vitest.types.d.ts +93 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Formatter, formatterHelpers } from '@cucumber/cucumber';
|
|
2
|
+
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { STATUS, TESTOMAT_TMP_STORAGE_DIR } from '../../constants.js';
|
|
6
|
+
import TestomatClient from '../../client.js';
|
|
7
|
+
import { getTestomatIdFromTestTitle, fileSystem } from '../../utils/utils.js';
|
|
8
|
+
import { config } from '../../config.js';
|
|
9
|
+
import { services } from '../../services/index.js';
|
|
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 = getTestomatIdFromTestTitle(tag.name);
|
|
19
|
+
if (testId) return testId;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class CucumberReporter extends Formatter {
|
|
27
|
+
constructor(options) {
|
|
28
|
+
super(options);
|
|
29
|
+
options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this));
|
|
30
|
+
this.failures = [];
|
|
31
|
+
this.cases = [];
|
|
32
|
+
|
|
33
|
+
this.client = new TestomatClient({ apiKey: options.apiKey || config.TESTOMATIO });
|
|
34
|
+
this.client.createRun();
|
|
35
|
+
this.status = STATUS.PASSED;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
parseEnvelope(envelope) {
|
|
39
|
+
if (envelope.testRunStarted) {
|
|
40
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
41
|
+
}
|
|
42
|
+
if (envelope.testCaseStarted && this.client) {
|
|
43
|
+
this.onTestCaseStarted(envelope.testCaseStarted);
|
|
44
|
+
}
|
|
45
|
+
if (envelope.testCaseFinished) this.onTestCaseFinished(envelope.testCaseFinished);
|
|
46
|
+
if (envelope.testRunFinished) this.onTestRunFinished(envelope);
|
|
47
|
+
}
|
|
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
|
+
|
|
57
|
+
onTestCaseFinished(testCaseFinished) {
|
|
58
|
+
const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseFinished.testCaseStartedId);
|
|
59
|
+
|
|
60
|
+
let example;
|
|
61
|
+
|
|
62
|
+
let status = STATUS.PASSED;
|
|
63
|
+
let color = 'green';
|
|
64
|
+
let message;
|
|
65
|
+
|
|
66
|
+
this.cases.push(testCaseAttempt);
|
|
67
|
+
|
|
68
|
+
if (testCaseAttempt.worstTestStepResult) {
|
|
69
|
+
if (testCaseAttempt.worstTestStepResult.status === 'SKIPPED') {
|
|
70
|
+
status = STATUS.SKIPPED;
|
|
71
|
+
}
|
|
72
|
+
// if (testCaseAttempt.worstTestStepResult.status === 'UNDEFINED') {
|
|
73
|
+
// status = STATUS.SKIPPED;
|
|
74
|
+
// message = 'Undefined steps. Implement missing steps and rerun this scenario';
|
|
75
|
+
// }
|
|
76
|
+
if (testCaseAttempt.worstTestStepResult.status === 'FAILED') {
|
|
77
|
+
message = testCaseAttempt?.worstTestStepResult?.message;
|
|
78
|
+
status = STATUS.FAILED;
|
|
79
|
+
}
|
|
80
|
+
color = getStatusColor(testCaseAttempt.worstTestStepResult.status);
|
|
81
|
+
if (status !== STATUS.PASSED) this.failures.push(testCaseAttempt);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (testCaseAttempt.pickle.astNodeIds.length > 1) {
|
|
85
|
+
example = getExample(testCaseAttempt);
|
|
86
|
+
// @ts-ignore
|
|
87
|
+
testCaseAttempt.example = example;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const scenario = testCaseAttempt.pickle.name;
|
|
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);
|
|
94
|
+
|
|
95
|
+
let exampleString = '';
|
|
96
|
+
if (example) exampleString = ` ${example.join(' | ')}`;
|
|
97
|
+
let cliMessage = `${pc.bold(scenario)}${exampleString}: ${pc[color](pc.bold(status.toUpperCase()))} `;
|
|
98
|
+
|
|
99
|
+
if (message) cliMessage += pc.gray(message.split('\n')[0]);
|
|
100
|
+
console.log(cliMessage);
|
|
101
|
+
|
|
102
|
+
if (status !== STATUS.PASSED && status !== STATUS.SKIPPED) {
|
|
103
|
+
this.status = STATUS.FAILED;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const time = Object.values(testCaseAttempt.stepResults)
|
|
107
|
+
.map(t => t.duration)
|
|
108
|
+
.reduce((sum, duration) => sum + duration.seconds * 1000 + duration.nanos / 1000000, 0);
|
|
109
|
+
|
|
110
|
+
if (!this.client) return;
|
|
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
|
+
const links = services.links.get(testTitle);
|
|
117
|
+
|
|
118
|
+
this.client.addTestRun(status, {
|
|
119
|
+
// error: testCaseAttempt.worstTestStepResult.message,
|
|
120
|
+
message,
|
|
121
|
+
steps: getSteps(testCaseAttempt)
|
|
122
|
+
.map(s => s.toString())
|
|
123
|
+
.join('\n')
|
|
124
|
+
.trim(),
|
|
125
|
+
example: { ...example },
|
|
126
|
+
logs,
|
|
127
|
+
links,
|
|
128
|
+
manuallyAttachedArtifacts: artifacts,
|
|
129
|
+
meta: keyValues,
|
|
130
|
+
title: scenario,
|
|
131
|
+
test_id: testId,
|
|
132
|
+
time,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
services.setContext(null);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onTestRunFinished(envelope) {
|
|
139
|
+
if (this.failures.length > 0) {
|
|
140
|
+
console.log(pc.bold('\nSUMMARY:\n\n'));
|
|
141
|
+
|
|
142
|
+
this.failures.forEach((tc, i) => {
|
|
143
|
+
let message = ` ${i + 1}) ${tc.pickle.name}\n`;
|
|
144
|
+
|
|
145
|
+
const steps = getSteps(tc);
|
|
146
|
+
|
|
147
|
+
steps.forEach(s => {
|
|
148
|
+
message += ` ${s.toString()}\n`;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
console.log(message);
|
|
152
|
+
if (tc?.worstTestStepResult?.message) {
|
|
153
|
+
console.log(pc.red(tc?.worstTestStepResult?.message));
|
|
154
|
+
}
|
|
155
|
+
console.log();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { testRunFinished } = envelope;
|
|
160
|
+
|
|
161
|
+
const bgColor = testRunFinished.success ? 'bgGreen' : 'bgRed';
|
|
162
|
+
const prefixSummary = `${pc.bold(testRunFinished.success ? ' SUCCESS ' : ' FALIURE ')}`;
|
|
163
|
+
console.log();
|
|
164
|
+
console.log(pc[bgColor](` ${prefixSummary} | Total Scenarios: ${pc.bold(this.cases.length)} `));
|
|
165
|
+
|
|
166
|
+
if (!this.client) return;
|
|
167
|
+
|
|
168
|
+
// @ts-ignore
|
|
169
|
+
this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function getSteps(tc) {
|
|
174
|
+
const stepIds = Object.keys(tc.stepResults);
|
|
175
|
+
const pickleSteps = getPickleStepMap(tc.pickle);
|
|
176
|
+
return stepIds
|
|
177
|
+
.map(stepId => {
|
|
178
|
+
const ts = tc.testCase.testSteps.find(t => t.id === stepId);
|
|
179
|
+
if (!ts) return;
|
|
180
|
+
if (!ts.pickleStepId) return;
|
|
181
|
+
const result = tc.stepResults[stepId];
|
|
182
|
+
const pickleStep = pickleSteps[ts.pickleStepId];
|
|
183
|
+
const sourceStepId = pickleStep.astNodeIds[0];
|
|
184
|
+
const step = {
|
|
185
|
+
text: pickleStep.text,
|
|
186
|
+
duration: result.duration,
|
|
187
|
+
status: result.status,
|
|
188
|
+
};
|
|
189
|
+
const color = getStatusColor(result.status);
|
|
190
|
+
if (sourceStepId && getGherkinStepMap(tc.gherkinDocument)[sourceStepId]) {
|
|
191
|
+
step.keyword = getGherkinStepMap(tc.gherkinDocument)[sourceStepId].keyword;
|
|
192
|
+
}
|
|
193
|
+
step.toString = function toString() {
|
|
194
|
+
const duration = step.duration.seconds * 1000 + step.duration.nanos / 1000000;
|
|
195
|
+
const durationString = ` ${pc.gray(`(${Number(duration).toFixed(2)}ms)`)}`;
|
|
196
|
+
const stepString = `${pc.bold(this.keyword)}${this.text}`.trim();
|
|
197
|
+
if (color === 'red') return pc.red(stepString) + durationString;
|
|
198
|
+
if (color === 'yellow') return pc.yellow(stepString) + durationString;
|
|
199
|
+
return stepString + durationString;
|
|
200
|
+
};
|
|
201
|
+
return step;
|
|
202
|
+
})
|
|
203
|
+
.filter(s => !!s);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getStatusColor(status) {
|
|
207
|
+
if (status === 'UNDEFINED') return 'yellow';
|
|
208
|
+
if (status === 'SKIPPED') return 'yellow';
|
|
209
|
+
if (status === 'FAILED') return 'red';
|
|
210
|
+
return 'green';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getExample(testCaseAttempt) {
|
|
214
|
+
const nodesMap = getGherkinScenarioLocationMap(testCaseAttempt.gherkinDocument);
|
|
215
|
+
const exampleNodeId = testCaseAttempt.pickle.astNodeIds[1];
|
|
216
|
+
if (!nodesMap[exampleNodeId]) return;
|
|
217
|
+
const featureDoc = fs.readFileSync(testCaseAttempt.gherkinDocument.uri).toString();
|
|
218
|
+
const { line } = nodesMap[exampleNodeId];
|
|
219
|
+
const example = featureDoc.split('\n')[line - 1];
|
|
220
|
+
if (example) {
|
|
221
|
+
return example
|
|
222
|
+
.trim()
|
|
223
|
+
.split('|')
|
|
224
|
+
.filter(r => !!r)
|
|
225
|
+
.map(r => r.trim());
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { CucumberReporter };
|
|
230
|
+
export default CucumberReporter;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// import { Formatter } from 'cucumber';
|
|
2
|
+
|
|
3
|
+
// import pc from 'picocolors';
|
|
4
|
+
// import { getTestomatIdFromTestTitle, fileSystem } from '../../utils/utils.js';
|
|
5
|
+
// import { STATUS, TESTOMAT_TMP_STORAGE_DIR } from '../../constants.js';
|
|
6
|
+
// import TestomatClient from '../../client.js';
|
|
7
|
+
// import {config} from '../../config.js';
|
|
8
|
+
|
|
9
|
+
// const createTestomatFormatter = apiKey => {
|
|
10
|
+
// if (!apiKey || apiKey === '') {
|
|
11
|
+
// console.log(pc.red('TESTOMATIO key is empty, ignoring reports'));
|
|
12
|
+
// }
|
|
13
|
+
|
|
14
|
+
// const documents = {};
|
|
15
|
+
// const dataTableMap = {};
|
|
16
|
+
|
|
17
|
+
// const addDocument = gherkinDocument => {
|
|
18
|
+
// documents[gherkinDocument.uri] = gherkinDocument.document;
|
|
19
|
+
// };
|
|
20
|
+
|
|
21
|
+
// const getTitle = scenario => {
|
|
22
|
+
// let { name } = scenario;
|
|
23
|
+
// if (scenario.tags.length) {
|
|
24
|
+
// let tags = '';
|
|
25
|
+
// for (const tag of scenario.tags) {
|
|
26
|
+
// tags = `${tags} ${tag.name}`;
|
|
27
|
+
// }
|
|
28
|
+
// name = `${name}${tags}`;
|
|
29
|
+
// }
|
|
30
|
+
// return name;
|
|
31
|
+
// };
|
|
32
|
+
|
|
33
|
+
// const getFeature = uri => documents[uri].feature;
|
|
34
|
+
|
|
35
|
+
// const getScenario = location => {
|
|
36
|
+
// const { children } = getFeature(location.uri);
|
|
37
|
+
// for (const scenario of children) {
|
|
38
|
+
// if (scenario.type === 'Scenario' && scenario.location.line === location.line) {
|
|
39
|
+
// return scenario;
|
|
40
|
+
// }
|
|
41
|
+
// if (scenario.type === 'ScenarioOutline') {
|
|
42
|
+
// for (const example of scenario.examples) {
|
|
43
|
+
// for (const tableBody of example.tableBody) {
|
|
44
|
+
// if (tableBody.location.line === location.line) {
|
|
45
|
+
// return scenario;
|
|
46
|
+
// }
|
|
47
|
+
// }
|
|
48
|
+
// }
|
|
49
|
+
// }
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
// return null;
|
|
53
|
+
// };
|
|
54
|
+
|
|
55
|
+
// const loadDataTable = (scenario, uri) => {
|
|
56
|
+
// if (scenario.type === 'ScenarioOutline') {
|
|
57
|
+
// for (const example of scenario.examples) {
|
|
58
|
+
// for (const tableBody of example.tableBody) {
|
|
59
|
+
// const dataMap = example.tableHeader.cells.reduce((acc, cell, index) => {
|
|
60
|
+
// acc[cell.value] = tableBody.cells[index].value;
|
|
61
|
+
// return acc;
|
|
62
|
+
// }, {});
|
|
63
|
+
|
|
64
|
+
// dataTableMap[`${uri}:${tableBody.location.line}`] = JSON.stringify(dataMap);
|
|
65
|
+
// }
|
|
66
|
+
// }
|
|
67
|
+
// }
|
|
68
|
+
// };
|
|
69
|
+
|
|
70
|
+
// const getDataTableMap = (scenario, sourceLocation) => {
|
|
71
|
+
// const key = `${sourceLocation.uri}:${sourceLocation.line}`;
|
|
72
|
+
// if (!dataTableMap[key]) {
|
|
73
|
+
// loadDataTable(scenario, sourceLocation.uri);
|
|
74
|
+
// }
|
|
75
|
+
|
|
76
|
+
// return dataTableMap[key] || '';
|
|
77
|
+
// };
|
|
78
|
+
|
|
79
|
+
// const getTestId = scenario => {
|
|
80
|
+
// if (scenario) {
|
|
81
|
+
// for (const tag of scenario.tags) {
|
|
82
|
+
// const testId = getTestomatIdFromTestTitle(tag.name);
|
|
83
|
+
// if (testId) return testId;
|
|
84
|
+
// }
|
|
85
|
+
// }
|
|
86
|
+
|
|
87
|
+
// return null;
|
|
88
|
+
// };
|
|
89
|
+
|
|
90
|
+
// return class TestomatFormatter extends Formatter {
|
|
91
|
+
// constructor(options) {
|
|
92
|
+
// super(options);
|
|
93
|
+
|
|
94
|
+
// if (!apiKey) return;
|
|
95
|
+
|
|
96
|
+
// this.client = new TestomatClient({ apiKey });
|
|
97
|
+
// this.status = STATUS.PASSED.toString();
|
|
98
|
+
|
|
99
|
+
// options.eventBroadcaster.on('gherkin-document', addDocument);
|
|
100
|
+
// options.eventBroadcaster.on('test-run-started', () => {
|
|
101
|
+
// fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
102
|
+
// this?.client?.createRun();
|
|
103
|
+
// });
|
|
104
|
+
// options.eventBroadcaster.on('test-case-finished', this.onTestCaseFinished.bind(this));
|
|
105
|
+
// options.eventBroadcaster.on('test-case-started', this.onTestCaseStarted.bind(this));
|
|
106
|
+
// // @ts-ignore
|
|
107
|
+
// options.eventBroadcaster.on('test-run-finished', () => this?.client?.updateRunStatus(this.status));
|
|
108
|
+
|
|
109
|
+
// global.testomatioRunningEnvironment = 'cucumber:legacy';
|
|
110
|
+
// }
|
|
111
|
+
|
|
112
|
+
// onTestCaseStarted(event) {
|
|
113
|
+
// const scenario = getScenario(event.sourceLocation);
|
|
114
|
+
// const testId = getTestId(scenario);
|
|
115
|
+
|
|
116
|
+
// if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
117
|
+
// global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
118
|
+
// }
|
|
119
|
+
|
|
120
|
+
// onTestCaseFinished(event) {
|
|
121
|
+
// const scenario = getScenario(event.sourceLocation);
|
|
122
|
+
// const testId = getTestId(scenario);
|
|
123
|
+
// const status = event.result.status === 'undefined' ? STATUS.SKIPPED : event.result.status;
|
|
124
|
+
|
|
125
|
+
// let example = getDataTableMap(scenario, event.sourceLocation);
|
|
126
|
+
// if (example) example = JSON.parse(example);
|
|
127
|
+
|
|
128
|
+
// if (!scenario.name) return;
|
|
129
|
+
|
|
130
|
+
// const message = '';
|
|
131
|
+
// const cliMessage = `- ${scenario.name}: ${pc.bold(status.toUpperCase())}`;
|
|
132
|
+
|
|
133
|
+
// // if (event.result.status === 'undefined') {
|
|
134
|
+
// // cliMessage += pc.yellow(
|
|
135
|
+
// // ' (undefined steps. Run Cucumber without this formatter and implement missing steps)',
|
|
136
|
+
// // );
|
|
137
|
+
// // message = 'Undefined steps. Implement missing steps and rerun this scenario';
|
|
138
|
+
// // }
|
|
139
|
+
// console.log(cliMessage);
|
|
140
|
+
// if (status !== STATUS.PASSED && status !== STATUS.SKIPPED) {
|
|
141
|
+
// this.status = STATUS.FAILED;
|
|
142
|
+
// }
|
|
143
|
+
|
|
144
|
+
// this.client?.addTestRun(status, {
|
|
145
|
+
// error: event.result.exception instanceof Error ? event.result.exception : undefined,
|
|
146
|
+
// message,
|
|
147
|
+
// example,
|
|
148
|
+
// test_id: testId,
|
|
149
|
+
// title: getTitle(scenario),
|
|
150
|
+
// suite_title: getTitle(getFeature(event.sourceLocation.uri)),
|
|
151
|
+
// });
|
|
152
|
+
// }
|
|
153
|
+
// };
|
|
154
|
+
// };
|
|
155
|
+
|
|
156
|
+
// const CucumberLegacyReporter = createTestomatFormatter(config.TESTOMATIO);
|
|
157
|
+
// export { CucumberLegacyReporter };
|
|
158
|
+
// export default CucumberLegacyReporter;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { STATUS } from '../../constants.js';
|
|
2
|
+
import { getTestomatIdFromTestTitle, parseSuite } from '../../utils/utils.js';
|
|
3
|
+
import TestomatClient from '../../client.js';
|
|
4
|
+
import { config } from '../../config.js';
|
|
5
|
+
|
|
6
|
+
const testomatioReporter = on => {
|
|
7
|
+
if (!config.TESTOMATIO) {
|
|
8
|
+
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const client = new TestomatClient({ apiKey: config.TESTOMATIO });
|
|
12
|
+
|
|
13
|
+
on('before:run', async () => {
|
|
14
|
+
// TODO: looks like client.env does not exist
|
|
15
|
+
// if (!client.env) {
|
|
16
|
+
// client.env = `${run.browser.displayName},${run.system.osName}`;
|
|
17
|
+
// }
|
|
18
|
+
await client.createRun();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
on('after:spec', async (_spec, results) => {
|
|
22
|
+
const addSpecTestsPromises = [];
|
|
23
|
+
|
|
24
|
+
const videos = [results.video];
|
|
25
|
+
|
|
26
|
+
for (const test of results.tests) {
|
|
27
|
+
const lastAttemptIndex = test.attempts.length - 1;
|
|
28
|
+
const latestAttempt = test.attempts[lastAttemptIndex];
|
|
29
|
+
|
|
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;
|
|
33
|
+
|
|
34
|
+
let title = test.title.pop();
|
|
35
|
+
const examples = title.match(/\(example (#\d+)\)/);
|
|
36
|
+
let example = null;
|
|
37
|
+
if (examples && examples[1]) example = { example: examples[1] };
|
|
38
|
+
title = title.replace(/\(example #\d+\)/, '').trim();
|
|
39
|
+
|
|
40
|
+
const suiteTitle = test.title.pop();
|
|
41
|
+
|
|
42
|
+
const testId = getTestomatIdFromTestTitle(title);
|
|
43
|
+
const suiteId = parseSuite(suiteTitle);
|
|
44
|
+
|
|
45
|
+
if (!error && test.displayError) {
|
|
46
|
+
error = { message: test.displayError };
|
|
47
|
+
error.inspect = function () {
|
|
48
|
+
return this.message;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const formattedError = error
|
|
53
|
+
? {
|
|
54
|
+
message: error.message,
|
|
55
|
+
name: error.name,
|
|
56
|
+
inspect:
|
|
57
|
+
error.inspect ||
|
|
58
|
+
function () {
|
|
59
|
+
return this.message;
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
: undefined;
|
|
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
|
+
: [];
|
|
69
|
+
|
|
70
|
+
const files = [...videos, ...screenshots];
|
|
71
|
+
|
|
72
|
+
let state;
|
|
73
|
+
switch (test.state) {
|
|
74
|
+
case 'passed':
|
|
75
|
+
state = STATUS.PASSED;
|
|
76
|
+
break;
|
|
77
|
+
case 'failed':
|
|
78
|
+
state = STATUS.FAILED;
|
|
79
|
+
break;
|
|
80
|
+
case 'skipped':
|
|
81
|
+
case 'pending':
|
|
82
|
+
default:
|
|
83
|
+
state = STATUS.SKIPPED;
|
|
84
|
+
}
|
|
85
|
+
|
|
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
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await Promise.all(addSpecTestsPromises);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
on('after:run', async results => {
|
|
104
|
+
const status = results.totalFailed ? STATUS.FAILED : STATUS.PASSED;
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
await client.updateRunStatus(status);
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export default testomatioReporter;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import TestomatClient from '../client.js';
|
|
2
|
+
import { getTestomatIdFromTestTitle, ansiRegExp } from '../utils/utils.js';
|
|
3
|
+
import { STATUS } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
class JasmineReporter {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.testTimeMap = {};
|
|
8
|
+
this.client = new TestomatClient({ apiKey: options?.apiKey });
|
|
9
|
+
this.client.createRun();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getDuration(test) {
|
|
13
|
+
if (this.testTimeMap[test.id]) {
|
|
14
|
+
return Date.now() - this.testTimeMap[test.id];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
specStarted(result) {
|
|
21
|
+
this.testTimeMap[result.id] = Date.now();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
specDone(result) {
|
|
25
|
+
if (!this.client) return;
|
|
26
|
+
|
|
27
|
+
const title = result.description;
|
|
28
|
+
const { status } = result;
|
|
29
|
+
let errorMessage = '';
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < result.failedExpectations.length; i += 1) {
|
|
32
|
+
errorMessage = `${errorMessage}Failure: ${result.failedExpectations[i].message}\n`;
|
|
33
|
+
errorMessage = `${errorMessage}\n ${result.failedExpectations[i].stack}`;
|
|
34
|
+
}
|
|
35
|
+
console.log(`${title} : ${STATUS.PASSED}`);
|
|
36
|
+
console.log(errorMessage);
|
|
37
|
+
const testId = getTestomatIdFromTestTitle(title);
|
|
38
|
+
errorMessage = errorMessage.replace(ansiRegExp(), '');
|
|
39
|
+
this.client.addTestRun(status, {
|
|
40
|
+
error: result.failedExpectations[0],
|
|
41
|
+
message: errorMessage,
|
|
42
|
+
test_id: testId,
|
|
43
|
+
title,
|
|
44
|
+
time: this.getDuration(result),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
jasmineDone(suiteInfo, done) {
|
|
49
|
+
if (!this.client) return;
|
|
50
|
+
|
|
51
|
+
const { overallStatus } = suiteInfo;
|
|
52
|
+
const status = overallStatus === 'failed' ? STATUS.FAILED : STATUS.PASSED;
|
|
53
|
+
|
|
54
|
+
// @ts-ignore
|
|
55
|
+
this.client.updateRunStatus(status).then(() => done);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { JasmineReporter };
|
|
60
|
+
export default JasmineReporter;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import TestomatClient from '../client.js';
|
|
3
|
+
import { TESTOMAT_TMP_STORAGE_DIR } from '../constants.js';
|
|
4
|
+
import { getTestomatIdFromTestTitle, ansiRegExp, fileSystem } from '../utils/utils.js';
|
|
5
|
+
import { services } from '../services/index.js';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import createDebugMessages from 'debug';
|
|
8
|
+
|
|
9
|
+
const debug = createDebugMessages('@testomatio/reporter:adapter-jest');
|
|
10
|
+
|
|
11
|
+
export class JestReporter {
|
|
12
|
+
constructor(globalConfig, options) {
|
|
13
|
+
this._globalConfig = globalConfig;
|
|
14
|
+
this._options = options;
|
|
15
|
+
|
|
16
|
+
this.client = new TestomatClient({ apiKey: options?.apiKey });
|
|
17
|
+
this.client.createRun();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onRunStart() {
|
|
21
|
+
// clear tmp dir
|
|
22
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// start of test file (including beforeAll)
|
|
26
|
+
onTestStart(testFile) {
|
|
27
|
+
debug('Start running test file:', testFile.path);
|
|
28
|
+
services.setContext(testFile.path);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// start of the test (including beforeEach)
|
|
32
|
+
onTestCaseStart(test, testCase) {
|
|
33
|
+
debug('Start running test:', testCase.fullName);
|
|
34
|
+
services.setContext(testCase.fullName);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// end of test file! (there is also onTestCaseResult listener)
|
|
38
|
+
onTestResult(test, testResult) {
|
|
39
|
+
if (!this.client) return;
|
|
40
|
+
|
|
41
|
+
const { testResults } = testResult;
|
|
42
|
+
for (const result of testResults) {
|
|
43
|
+
let error;
|
|
44
|
+
let steps;
|
|
45
|
+
const { status, title, duration, failureMessages } = result;
|
|
46
|
+
if (failureMessages[0]) {
|
|
47
|
+
const errorMessage = failureMessages[0].replace(ansiRegExp(), '');
|
|
48
|
+
error = new Error(errorMessage);
|
|
49
|
+
steps = failureMessages[0];
|
|
50
|
+
}
|
|
51
|
+
const testId = getTestomatIdFromTestTitle(title);
|
|
52
|
+
|
|
53
|
+
// suite titles from most outer to most inner, separated by space
|
|
54
|
+
let fullSuiteTitle = testResult.ancestorTitles?.join(' ');
|
|
55
|
+
// if no suite titles, use file name
|
|
56
|
+
if (!fullSuiteTitle && testResult.testFilePath) fullSuiteTitle = path.basename(testResult.testFilePath);
|
|
57
|
+
|
|
58
|
+
const logs = getTestLogs(result);
|
|
59
|
+
const artifacts = services.artifacts.get(result.fullName);
|
|
60
|
+
const keyValues = services.keyValues.get(result.fullName);
|
|
61
|
+
const links = services.links.get(result.fullName);
|
|
62
|
+
|
|
63
|
+
const deducedStatus = status === 'pending' ? 'skipped' : status;
|
|
64
|
+
// In jest if test is not matched with test name pattern it is considered as skipped.
|
|
65
|
+
// So adding a check if it is skipped for real or because of test pattern
|
|
66
|
+
if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
|
|
67
|
+
this.client.addTestRun(deducedStatus, {
|
|
68
|
+
test_id: testId,
|
|
69
|
+
suite_title: fullSuiteTitle,
|
|
70
|
+
error,
|
|
71
|
+
steps,
|
|
72
|
+
title,
|
|
73
|
+
time: duration,
|
|
74
|
+
logs,
|
|
75
|
+
links,
|
|
76
|
+
manuallyAttachedArtifacts: artifacts,
|
|
77
|
+
meta: keyValues,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onRunComplete(contexts, results) {
|
|
84
|
+
if (!this.client) return;
|
|
85
|
+
|
|
86
|
+
const { numFailedTests } = results;
|
|
87
|
+
const status = numFailedTests === 0 ? 'passed' : 'failed';
|
|
88
|
+
this.client.updateRunStatus(status);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getTestLogs(testResult) {
|
|
93
|
+
const suiteLogsArr = services.logger.getLogs(testResult.testFilePath);
|
|
94
|
+
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
95
|
+
const testLogsArr = services.logger.getLogs(testResult.fullName);
|
|
96
|
+
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
97
|
+
|
|
98
|
+
let logs = '';
|
|
99
|
+
if (suiteLogs) {
|
|
100
|
+
logs += `${pc.bold('\t--- Suite ---')}\n${suiteLogs}`;
|
|
101
|
+
}
|
|
102
|
+
if (testLogs) {
|
|
103
|
+
logs += `\n${pc.bold('\t--- Test ---')}\n${testLogs}`;
|
|
104
|
+
}
|
|
105
|
+
return logs;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export default JestReporter;
|