@testomatio/reporter 1.1.0-beta → 1.1.0-beta-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Changelog.md +355 -0
- package/README.md +13 -9
- package/lib/adapter/codecept.js +59 -39
- package/lib/adapter/cucumber/current.js +95 -59
- package/lib/adapter/cucumber/legacy.js +25 -11
- package/lib/adapter/cypress-plugin/index.js +1 -1
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +12 -11
- package/lib/adapter/mocha.js +22 -50
- package/lib/adapter/playwright.js +40 -21
- package/lib/adapter/webdriver.js +1 -1
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +117 -52
- package/lib/constants.js +4 -6
- package/lib/junit-adapter/java.js +27 -9
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +24 -37
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/testomatio.js +138 -34
- package/lib/reporter-functions.js +43 -0
- package/lib/reporter.js +13 -5
- package/lib/storages/artifact-storage.js +70 -0
- package/lib/storages/data-storage.js +307 -0
- package/lib/storages/key-value-storage.js +58 -0
- package/lib/storages/logger.js +290 -0
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/{util.js → utils/utils.js} +130 -10
- package/lib/xmlReader.js +132 -44
- package/package.json +9 -6
- package/lib/_ArtifactStorageOld.js +0 -141
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -172
- package/lib/logger.js +0 -221
|
@@ -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 logger = require('../../storages/logger');
|
|
8
|
+
const { parseTest, fileSystem } = require('../../utils/utils');
|
|
9
|
+
|
|
10
|
+
const { GherkinDocumentParser, PickleParser } = formatterHelpers;
|
|
11
|
+
const { getGherkinScenarioLocationMap, getGherkinStepMap } = GherkinDocumentParser;
|
|
12
|
+
const { getPickleStepMap } = PickleParser;
|
|
13
|
+
|
|
14
|
+
function getTestId(scenario) {
|
|
15
|
+
if (scenario) {
|
|
16
|
+
for (const tag of scenario.tags) {
|
|
17
|
+
const testId = parseTest(tag.name);
|
|
18
|
+
if (testId) return testId;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
7
21
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
getGherkinScenarioLocationMap,
|
|
11
|
-
getGherkinStepMap,
|
|
12
|
-
} = GherkinDocumentParser
|
|
13
|
-
const { getPickleStepMap } = PickleParser
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
14
24
|
|
|
15
25
|
class CucumberReporter extends Formatter {
|
|
16
26
|
constructor(options) {
|
|
17
27
|
super(options);
|
|
18
|
-
options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this))
|
|
28
|
+
options.eventBroadcaster.on('envelope', this.parseEnvelope.bind(this));
|
|
19
29
|
this.failures = [];
|
|
20
30
|
this.cases = [];
|
|
21
31
|
|
|
22
|
-
this.client = new TestomatClient();
|
|
32
|
+
this.client = new TestomatClient({ apiKey: options.apiKey || process.env.TESTOMATIO });
|
|
23
33
|
this.status = STATUS.PASSED;
|
|
24
34
|
|
|
35
|
+
global.testomatioRunningEnvironment = 'cucumber:current';
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
parseEnvelope(envelope) {
|
|
28
|
-
if (envelope.
|
|
39
|
+
if (envelope.testRunStarted) {
|
|
40
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
41
|
+
}
|
|
42
|
+
if (envelope.testCaseStarted && this.client) {
|
|
43
|
+
this.client.createRun();
|
|
44
|
+
this.onTestCaseStarted(envelope.testCaseStarted);
|
|
45
|
+
}
|
|
29
46
|
if (envelope.testCaseFinished) this.onTestCaseFinished(envelope.testCaseFinished);
|
|
30
47
|
if (envelope.testRunFinished) this.onTestRunFinished(envelope);
|
|
31
48
|
}
|
|
32
49
|
|
|
50
|
+
onTestCaseStarted(testCaseStarted) {
|
|
51
|
+
const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseStarted.id);
|
|
52
|
+
const testId = getTestId(testCaseAttempt.pickle);
|
|
53
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
54
|
+
global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
onTestCaseFinished(testCaseFinished) {
|
|
34
58
|
const testCaseAttempt = this.eventDataCollector.getTestCaseAttempt(testCaseFinished.testCaseStartedId);
|
|
35
59
|
|
|
@@ -45,10 +69,10 @@ class CucumberReporter extends Formatter {
|
|
|
45
69
|
if (testCaseAttempt.worstTestStepResult.status === 'SKIPPED') {
|
|
46
70
|
status = STATUS.SKIPPED;
|
|
47
71
|
}
|
|
48
|
-
if (testCaseAttempt.worstTestStepResult.status === 'UNDEFINED') {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
72
|
+
// if (testCaseAttempt.worstTestStepResult.status === 'UNDEFINED') {
|
|
73
|
+
// status = STATUS.SKIPPED;
|
|
74
|
+
// message = 'Undefined steps. Implement missing steps and rerun this scenario';
|
|
75
|
+
// }
|
|
52
76
|
if (testCaseAttempt.worstTestStepResult.status === 'FAILED') {
|
|
53
77
|
message = testCaseAttempt?.worstTestStepResult?.message;
|
|
54
78
|
status = STATUS.FAILED;
|
|
@@ -64,10 +88,12 @@ class CucumberReporter extends Formatter {
|
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
const scenario = testCaseAttempt.pickle.name;
|
|
67
|
-
|
|
91
|
+
// this may broke something (it is supposed to work, but I am sure it did not)
|
|
92
|
+
// const testId = testCaseAttempt.pickle.id;
|
|
93
|
+
const testId = getTestId(testCaseAttempt.pickle);
|
|
68
94
|
|
|
69
95
|
let exampleString = '';
|
|
70
|
-
if (example) exampleString = ` ${example.join(' | ')}
|
|
96
|
+
if (example) exampleString = ` ${example.join(' | ')}`;
|
|
71
97
|
let cliMessage = `${chalk.bold(scenario)}${exampleString}: ${chalk[color].bold(status.toUpperCase())} `;
|
|
72
98
|
|
|
73
99
|
if (message) cliMessage += chalk.gray(message.split('\n')[0]);
|
|
@@ -79,15 +105,21 @@ class CucumberReporter extends Formatter {
|
|
|
79
105
|
|
|
80
106
|
const time = Object.values(testCaseAttempt.stepResults)
|
|
81
107
|
.map(t => t.duration)
|
|
82
|
-
.reduce((sum, duration) => sum +
|
|
108
|
+
.reduce((sum, duration) => sum + duration.seconds * 1000 + duration.nanos / 1000000, 0);
|
|
83
109
|
|
|
84
110
|
if (!this.client) return;
|
|
85
111
|
|
|
112
|
+
const logs = logger.getLogs(testId);
|
|
113
|
+
|
|
86
114
|
this.client.addTestRun(status, {
|
|
87
115
|
// error: testCaseAttempt.worstTestStepResult.message,
|
|
88
116
|
message,
|
|
89
|
-
steps: getSteps(testCaseAttempt)
|
|
90
|
-
|
|
117
|
+
steps: getSteps(testCaseAttempt)
|
|
118
|
+
.map(s => s.toString())
|
|
119
|
+
.join('\n')
|
|
120
|
+
.trim(),
|
|
121
|
+
example: { ...example },
|
|
122
|
+
stack: logs,
|
|
91
123
|
title: scenario,
|
|
92
124
|
test_id: testId,
|
|
93
125
|
time,
|
|
@@ -99,21 +131,20 @@ class CucumberReporter extends Formatter {
|
|
|
99
131
|
console.log(chalk.bold('\nSUMMARY:\n\n'));
|
|
100
132
|
|
|
101
133
|
this.failures.forEach((tc, i) => {
|
|
102
|
-
let message = ` ${i+1}) ${tc.pickle.name}\n`;
|
|
134
|
+
let message = ` ${i + 1}) ${tc.pickle.name}\n`;
|
|
103
135
|
|
|
104
136
|
const steps = getSteps(tc);
|
|
105
137
|
|
|
106
138
|
steps.forEach(s => {
|
|
107
|
-
message += ` ${s.toString()}\n
|
|
108
|
-
})
|
|
139
|
+
message += ` ${s.toString()}\n`;
|
|
140
|
+
});
|
|
109
141
|
|
|
110
142
|
console.log(message);
|
|
111
143
|
if (tc?.worstTestStepResult?.message) {
|
|
112
144
|
console.log(chalk.red(tc?.worstTestStepResult?.message));
|
|
113
145
|
}
|
|
114
146
|
console.log();
|
|
115
|
-
|
|
116
|
-
})
|
|
147
|
+
});
|
|
117
148
|
}
|
|
118
149
|
|
|
119
150
|
const { testRunFinished } = envelope;
|
|
@@ -125,46 +156,48 @@ class CucumberReporter extends Formatter {
|
|
|
125
156
|
|
|
126
157
|
if (!this.client) return;
|
|
127
158
|
|
|
128
|
-
this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED)
|
|
159
|
+
this.client.updateRunStatus(testRunFinished.success ? STATUS.PASSED : STATUS.FAILED);
|
|
129
160
|
}
|
|
130
161
|
}
|
|
131
162
|
|
|
132
163
|
function getSteps(tc) {
|
|
133
164
|
const stepIds = Object.keys(tc.stepResults);
|
|
134
|
-
const pickleSteps = getPickleStepMap(tc.pickle)
|
|
135
|
-
return stepIds
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
165
|
+
const pickleSteps = getPickleStepMap(tc.pickle);
|
|
166
|
+
return stepIds
|
|
167
|
+
.map(stepId => {
|
|
168
|
+
const ts = tc.testCase.testSteps.find(t => t.id === stepId);
|
|
169
|
+
if (!ts) return;
|
|
170
|
+
if (!ts.pickleStepId) return;
|
|
171
|
+
const result = tc.stepResults[stepId];
|
|
172
|
+
const pickleStep = pickleSteps[ts.pickleStepId];
|
|
173
|
+
const sourceStepId = pickleStep.astNodeIds[0];
|
|
174
|
+
const step = {
|
|
175
|
+
text: pickleStep.text,
|
|
176
|
+
duration: result.duration,
|
|
177
|
+
status: result.status,
|
|
178
|
+
};
|
|
179
|
+
const color = getStatusColor(result.status);
|
|
180
|
+
if (sourceStepId && getGherkinStepMap(tc.gherkinDocument)[sourceStepId]) {
|
|
181
|
+
step.keyword = getGherkinStepMap(tc.gherkinDocument)[sourceStepId].keyword;
|
|
182
|
+
}
|
|
183
|
+
step.toString = function toString() {
|
|
184
|
+
const duration = step.duration.seconds * 1000 + step.duration.nanos / 1000000;
|
|
185
|
+
const durationString = ` ${chalk.gray(`(${Number(duration).toFixed(2)}ms)`)}`;
|
|
186
|
+
const stepString = `${chalk.bold(this.keyword)}${this.text}`.trim();
|
|
187
|
+
if (color === 'red') return chalk.red(stepString) + durationString;
|
|
188
|
+
if (color === 'yellow') return chalk.yellow(stepString) + durationString;
|
|
189
|
+
return stepString + durationString;
|
|
190
|
+
};
|
|
191
|
+
return step;
|
|
192
|
+
})
|
|
193
|
+
.filter(s => !!s);
|
|
161
194
|
}
|
|
162
195
|
|
|
163
196
|
function getStatusColor(status) {
|
|
164
197
|
if (status === 'UNDEFINED') return 'yellow';
|
|
165
198
|
if (status === 'SKIPPED') return 'yellow';
|
|
166
199
|
if (status === 'FAILED') return 'red';
|
|
167
|
-
return 'green'
|
|
200
|
+
return 'green';
|
|
168
201
|
}
|
|
169
202
|
|
|
170
203
|
function getExample(testCaseAttempt) {
|
|
@@ -173,11 +206,14 @@ function getExample(testCaseAttempt) {
|
|
|
173
206
|
if (!nodesMap[exampleNodeId]) return;
|
|
174
207
|
const featureDoc = fs.readFileSync(testCaseAttempt.gherkinDocument.uri).toString();
|
|
175
208
|
const { line } = nodesMap[exampleNodeId];
|
|
176
|
-
const example = featureDoc.split('\n')[line-1];
|
|
209
|
+
const example = featureDoc.split('\n')[line - 1];
|
|
177
210
|
if (example) {
|
|
178
|
-
return example
|
|
179
|
-
|
|
180
|
-
|
|
211
|
+
return example
|
|
212
|
+
.trim()
|
|
213
|
+
.split('|')
|
|
214
|
+
.filter(r => !!r)
|
|
215
|
+
.map(r => r.trim());
|
|
216
|
+
}
|
|
181
217
|
}
|
|
182
218
|
|
|
183
219
|
module.exports = CucumberReporter;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
|
|
2
2
|
const { Formatter } = require('cucumber');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
-
const { parseTest } = require('../../
|
|
5
|
-
const { STATUS } = require('../../constants');
|
|
4
|
+
const { parseTest, fileSystem } = require('../../utils/utils');
|
|
5
|
+
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = 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_DIR);
|
|
101
|
+
this?.client?.createRun();
|
|
102
|
+
});
|
|
100
103
|
options.eventBroadcaster.on('test-case-finished', this.onTestCaseFinished.bind(this));
|
|
104
|
+
options.eventBroadcaster.on('test-case-started', this.onTestCaseStarted.bind(this));
|
|
101
105
|
options.eventBroadcaster.on('test-run-finished', () => this?.client?.updateRunStatus(this.status));
|
|
106
|
+
|
|
107
|
+
global.testomatioRunningEnvironment = 'cucumber:legacy';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
onTestCaseStarted(event) {
|
|
111
|
+
const scenario = getScenario(event.sourceLocation);
|
|
112
|
+
const testId = getTestId(scenario);
|
|
113
|
+
|
|
114
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
115
|
+
global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
onTestCaseFinished(event) {
|
|
@@ -111,15 +125,15 @@ const createTestomatFormatter = apiKey => {
|
|
|
111
125
|
|
|
112
126
|
if (!scenario.name) return;
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
const message = '';
|
|
129
|
+
const cliMessage = `- ${scenario.name}: ${chalk.bold(status.toUpperCase())}`;
|
|
116
130
|
|
|
117
|
-
if (event.result.status === 'undefined') {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
131
|
+
// if (event.result.status === 'undefined') {
|
|
132
|
+
// cliMessage += chalk.yellow(
|
|
133
|
+
// ' (undefined steps. Run Cucumber without this formatter and implement missing steps)',
|
|
134
|
+
// );
|
|
135
|
+
// message = 'Undefined steps. Implement missing steps and rerun this scenario';
|
|
136
|
+
// }
|
|
123
137
|
console.log(cliMessage);
|
|
124
138
|
if (status !== STATUS.PASSED && status !== STATUS.SKIPPED) {
|
|
125
139
|
this.status = STATUS.FAILED;
|
package/lib/adapter/jasmine.js
CHANGED
package/lib/adapter/jest.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const TestomatClient = require('../client');
|
|
2
|
-
const { STATUS,
|
|
3
|
-
const { parseTest, ansiRegExp, fileSystem } = require('../
|
|
2
|
+
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
3
|
+
const { parseTest, ansiRegExp, fileSystem } = require('../utils/utils');
|
|
4
4
|
|
|
5
5
|
class JestReporter {
|
|
6
6
|
constructor(globalConfig, options) {
|
|
@@ -11,14 +11,9 @@ class JestReporter {
|
|
|
11
11
|
this.client.createRun();
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
static getIdOfCurrentlyRunningTest() {
|
|
15
|
-
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
16
|
-
return parseTest(expect?.getState()?.currentTestName) || null; // eslint-disable-line
|
|
17
|
-
}
|
|
18
|
-
|
|
19
14
|
onRunStart() {
|
|
20
15
|
// clear tmp dir
|
|
21
|
-
fileSystem.clearDir(
|
|
16
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
22
17
|
}
|
|
23
18
|
|
|
24
19
|
onTestResult(test, testResult) {
|
|
@@ -36,12 +31,21 @@ class JestReporter {
|
|
|
36
31
|
steps = failureMessages[0];
|
|
37
32
|
}
|
|
38
33
|
const testId = parseTest(title);
|
|
34
|
+
let suite_title;
|
|
35
|
+
// this is test without a suite
|
|
36
|
+
if (result.fullName === result.title) {
|
|
37
|
+
suite_title = testResult.testFilePath.split('/').pop();
|
|
38
|
+
} else {
|
|
39
|
+
suite_title = result.fullName.replace(result.title, '');
|
|
40
|
+
}
|
|
41
|
+
|
|
39
42
|
const deducedStatus = status === 'pending' ? 'skipped' : status;
|
|
40
43
|
// In jest if test is not matched with test name pattern it is considered as skipped.
|
|
41
44
|
// So adding a check if it is skipped for real or because of test pattern
|
|
42
45
|
if (!this._globalConfig.testNamePattern || deducedStatus !== 'skipped') {
|
|
43
46
|
this.client.addTestRun(deducedStatus, {
|
|
44
47
|
test_id: testId,
|
|
48
|
+
suite_title,
|
|
45
49
|
error,
|
|
46
50
|
steps,
|
|
47
51
|
title,
|
|
@@ -57,9 +61,6 @@ class JestReporter {
|
|
|
57
61
|
const { numFailedTests } = results;
|
|
58
62
|
const status = numFailedTests === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
59
63
|
this.client.updateRunStatus(status);
|
|
60
|
-
|
|
61
|
-
// clear tmp dir
|
|
62
|
-
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -1,58 +1,40 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
2
|
const Mocha = require('mocha');
|
|
3
|
-
const debug = require('debug')('@testomatio/reporter:adapter:mocha');
|
|
4
3
|
const chalk = require('chalk');
|
|
5
4
|
const TestomatClient = require('../client');
|
|
6
|
-
const { STATUS } = require('../constants');
|
|
7
|
-
const { parseTest,
|
|
8
|
-
const ArtifactStorage = require('../_ArtifactStorageOld');
|
|
5
|
+
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
6
|
+
const { parseTest, fileSystem } = require('../utils/utils');
|
|
9
7
|
|
|
10
|
-
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } =
|
|
8
|
+
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } =
|
|
9
|
+
Mocha.Runner.constants;
|
|
11
10
|
|
|
12
11
|
function MochaReporter(runner, opts) {
|
|
13
12
|
Mocha.reporters.Base.call(this, runner);
|
|
14
|
-
let passes = 0;
|
|
15
|
-
let
|
|
13
|
+
let passes = 0;
|
|
14
|
+
let failures = 0;
|
|
15
|
+
let skipped = 0;
|
|
16
|
+
// let artifactStore;
|
|
16
17
|
|
|
17
18
|
const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
|
|
18
19
|
|
|
19
|
-
if (!apiKey) {
|
|
20
|
-
debug('TESTOMATIO key is empty, ignoring reports');
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
20
|
const client = new TestomatClient({ apiKey });
|
|
24
21
|
|
|
25
22
|
runner.on(EVENT_RUN_BEGIN, () => {
|
|
26
23
|
client.createRun();
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
toFile: true
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
artifactStore = runner._workerReporter !== undefined
|
|
33
|
-
? new ArtifactStorage(params)
|
|
34
|
-
: new ArtifactStorage();
|
|
25
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
35
26
|
});
|
|
36
27
|
|
|
37
|
-
runner.on(EVENT_TEST_PASS, async
|
|
28
|
+
runner.on(EVENT_TEST_PASS, async test => {
|
|
38
29
|
passes += 1;
|
|
39
30
|
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
40
31
|
const testId = parseTest(test.title);
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
client.addTestRun(
|
|
48
|
-
STATUS.PASSED,
|
|
49
|
-
{
|
|
50
|
-
test_id: testId,
|
|
51
|
-
title: test.title,
|
|
52
|
-
time: test.duration,
|
|
53
|
-
},
|
|
54
|
-
content
|
|
55
|
-
);
|
|
33
|
+
client.addTestRun(STATUS.PASSED, {
|
|
34
|
+
test_id: testId,
|
|
35
|
+
title: test.title,
|
|
36
|
+
time: test.duration,
|
|
37
|
+
});
|
|
56
38
|
});
|
|
57
39
|
|
|
58
40
|
runner.on(EVENT_TEST_PENDING, test => {
|
|
@@ -66,33 +48,23 @@ function MochaReporter(runner, opts) {
|
|
|
66
48
|
});
|
|
67
49
|
});
|
|
68
50
|
|
|
69
|
-
runner.on(EVENT_TEST_FAIL, async(test, err) => {
|
|
51
|
+
runner.on(EVENT_TEST_FAIL, async (test, err) => {
|
|
70
52
|
failures += 1;
|
|
71
53
|
console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
|
|
72
54
|
const testId = parseTest(test.title);
|
|
73
55
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
STATUS.FAILED, {
|
|
81
|
-
error: err,
|
|
82
|
-
test_id: testId,
|
|
83
|
-
title: test.title,
|
|
84
|
-
time: test.duration,
|
|
85
|
-
},
|
|
86
|
-
content
|
|
87
|
-
);
|
|
56
|
+
client.addTestRun(STATUS.FAILED, {
|
|
57
|
+
error: err,
|
|
58
|
+
test_id: testId,
|
|
59
|
+
title: test.title,
|
|
60
|
+
time: test.duration,
|
|
61
|
+
});
|
|
88
62
|
});
|
|
89
63
|
|
|
90
64
|
runner.on(EVENT_RUN_END, () => {
|
|
91
65
|
const status = failures === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
92
66
|
console.log(chalk.bold(status), `${passes} passed, ${failures} failed, ${skipped} skipped`);
|
|
93
67
|
client.updateRunStatus(status);
|
|
94
|
-
|
|
95
|
-
artifactStore.cleanup();
|
|
96
68
|
});
|
|
97
69
|
}
|
|
98
70
|
|
|
@@ -3,14 +3,14 @@ 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_DIR } = require('../constants');
|
|
7
7
|
const TestomatioClient = require('../client');
|
|
8
8
|
const { isArtifactsEnabled } = require('../fileUploader');
|
|
9
|
-
const { parseTest } = require('../
|
|
9
|
+
const { parseTest, fileSystem } = require('../utils/utils');
|
|
10
10
|
|
|
11
11
|
const reportTestPromises = [];
|
|
12
12
|
|
|
13
|
-
class
|
|
13
|
+
class PlaywrightReporter {
|
|
14
14
|
constructor(config = {}) {
|
|
15
15
|
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
16
16
|
|
|
@@ -18,11 +18,19 @@ class TestomatioReporter {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
onBegin(_config, suite) {
|
|
21
|
+
// clean data storage
|
|
22
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
21
23
|
if (!this.client) return;
|
|
22
24
|
this.suite = suite;
|
|
23
25
|
this.client.createRun();
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
/*
|
|
29
|
+
onTestBegin(test) {
|
|
30
|
+
// does not work; value is not to the storage context
|
|
31
|
+
global.testTitle = test.title;
|
|
32
|
+
} */
|
|
33
|
+
|
|
26
34
|
onTestEnd(test, result) {
|
|
27
35
|
if (!this.client) return;
|
|
28
36
|
|
|
@@ -37,21 +45,34 @@ class TestomatioReporter {
|
|
|
37
45
|
for (const step of result.steps) {
|
|
38
46
|
appendStep(step, steps);
|
|
39
47
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
|
|
49
|
+
let logs = '';
|
|
50
|
+
if (result.stderr.length || result.stdout.length) {
|
|
51
|
+
logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
|
|
52
|
+
}
|
|
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,
|
|
62
|
+
stack: logs,
|
|
53
63
|
})
|
|
54
|
-
|
|
64
|
+
.then(pipes => {
|
|
65
|
+
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
66
|
+
|
|
67
|
+
this.uploads.push({
|
|
68
|
+
testId,
|
|
69
|
+
title,
|
|
70
|
+
suite_title,
|
|
71
|
+
files: result.attachments.filter(a => a.body || a.path),
|
|
72
|
+
});
|
|
73
|
+
// remove empty uploads
|
|
74
|
+
this.uploads = this.uploads.filter(upload => upload.files.length);
|
|
75
|
+
});
|
|
55
76
|
|
|
56
77
|
reportTestPromises.push(reportTestPromise);
|
|
57
78
|
}
|
|
@@ -67,10 +88,9 @@ class TestomatioReporter {
|
|
|
67
88
|
const promises = [];
|
|
68
89
|
|
|
69
90
|
for (const upload of this.uploads) {
|
|
70
|
-
|
|
71
91
|
const { title, testId, suite_title } = upload;
|
|
72
92
|
|
|
73
|
-
const files = upload.files.map(
|
|
93
|
+
const files = upload.files.map(attachment => {
|
|
74
94
|
if (attachment.body) {
|
|
75
95
|
const fileName = tmpFile();
|
|
76
96
|
fs.writeFileSync(fileName, attachment.body);
|
|
@@ -78,7 +98,6 @@ class TestomatioReporter {
|
|
|
78
98
|
return { path: attachment.path, title, type: attachment.contentType };
|
|
79
99
|
});
|
|
80
100
|
|
|
81
|
-
|
|
82
101
|
promises.push(
|
|
83
102
|
this.client.addTestRun(undefined, {
|
|
84
103
|
test_id: testId,
|
|
@@ -124,4 +143,4 @@ function tmpFile(prefix = 'tmp.') {
|
|
|
124
143
|
return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
|
|
125
144
|
}
|
|
126
145
|
|
|
127
|
-
module.exports =
|
|
146
|
+
module.exports = PlaywrightReporter;
|
package/lib/adapter/webdriver.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
2
|
const WDIOReporter = require('@wdio/reporter').default;
|
|
3
3
|
const TestomatClient = require('../client');
|
|
4
|
-
const { parseTest } = require('../
|
|
4
|
+
const { parseTest } = require('../utils/utils');
|
|
5
5
|
|
|
6
6
|
class WebdriverReporter extends WDIOReporter {
|
|
7
7
|
constructor(options) {
|
package/lib/bin/reportXml.js
CHANGED