@testomatio/reporter 0.7.3 → 0.8.0-beta.10
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 +1 -0
- package/lib/adapter/codecept.js +5 -6
- package/lib/adapter/cucumber/current.js +1 -7
- package/lib/adapter/jasmine.js +1 -8
- package/lib/adapter/jest.js +1 -7
- package/lib/adapter/mocha.js +5 -10
- package/lib/adapter/playwright.js +1 -7
- package/lib/adapter/webdriver.js +1 -4
- package/lib/client.js +64 -142
- package/lib/junit-adapter/index.js +27 -0
- package/lib/pipe/github.js +179 -0
- package/lib/pipe/index.js +49 -0
- package/lib/pipe/testomatio.js +137 -0
- package/lib/util.js +3 -1
- package/lib/xmlReader.js +18 -100
- package/package.json +4 -2
package/Changelog.md
CHANGED
package/lib/adapter/codecept.js
CHANGED
|
@@ -35,11 +35,6 @@ function CodeceptReporter(config) {
|
|
|
35
35
|
const testTimeMap = {};
|
|
36
36
|
const { apiKey } = config;
|
|
37
37
|
|
|
38
|
-
if (!apiKey) {
|
|
39
|
-
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
38
|
const getDuration = test => {
|
|
44
39
|
if (testTimeMap[test.id]) {
|
|
45
40
|
return Date.now() - testTimeMap[test.id];
|
|
@@ -148,7 +143,11 @@ function CodeceptReporter(config) {
|
|
|
148
143
|
const files = [];
|
|
149
144
|
if (artifacts.screenshot) files.push({ path: artifacts.screenshot, type: 'image/png' });
|
|
150
145
|
// todo: video must be uploaded later....
|
|
151
|
-
|
|
146
|
+
|
|
147
|
+
for (const aid in artifacts) {
|
|
148
|
+
if (aid.startsWith('video')) videos.push({ testId: id, title, path: artifacts[aid], type: 'video/webm' });
|
|
149
|
+
if (aid.startsWith('trace')) files.push({ testId: id, title, path: artifacts[aid], type: 'application/zip' });
|
|
150
|
+
}
|
|
152
151
|
|
|
153
152
|
client.addTestRun(testId, TRConstants.FAILED, {
|
|
154
153
|
...stripExampleFromTitle(title),
|
|
@@ -18,13 +18,7 @@ class CucumberReporter extends Formatter {
|
|
|
18
18
|
this.failures = [];
|
|
19
19
|
this.cases = [];
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
if (!apiKey || apiKey === '') {
|
|
23
|
-
console.log(chalk.red('TESTOMATIO key is empty, ignoring reports'));
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
this.client = new TestomatClient({ apiKey });
|
|
21
|
+
this.client = new TestomatClient();
|
|
28
22
|
this.status = TRConstants.PASSED;
|
|
29
23
|
|
|
30
24
|
}
|
package/lib/adapter/jasmine.js
CHANGED
|
@@ -5,14 +5,7 @@ const { PASSED, FAILED } = require('../constants');
|
|
|
5
5
|
class JasmineReporter {
|
|
6
6
|
constructor(options) {
|
|
7
7
|
this.testTimeMap = {};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (!apiKey) {
|
|
11
|
-
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
this.client = new TestomatClient({ apiKey });
|
|
8
|
+
this.client = new TestomatClient({ apiKey: options?.apiKey });
|
|
16
9
|
this.client.createRun();
|
|
17
10
|
}
|
|
18
11
|
|
package/lib/adapter/jest.js
CHANGED
|
@@ -6,14 +6,8 @@ class JestReporter {
|
|
|
6
6
|
constructor(globalConfig, options) {
|
|
7
7
|
this._globalConfig = globalConfig;
|
|
8
8
|
this._options = options;
|
|
9
|
-
const { apiKey } = options;
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
this.client = new TestomatClient({ apiKey });
|
|
10
|
+
this.client = new TestomatClient({ apiKey: options?.apiKey });
|
|
17
11
|
this.client.createRun();
|
|
18
12
|
}
|
|
19
13
|
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
2
|
const Mocha = require('mocha');
|
|
3
|
+
const chalk = require('chalk');
|
|
3
4
|
const TestomatClient = require('../client');
|
|
4
5
|
const TRConstants = require('../constants');
|
|
5
6
|
const { parseTest } = require('../util');
|
|
@@ -11,13 +12,7 @@ function MochaReporter(runner, opts) {
|
|
|
11
12
|
let passes = 0;
|
|
12
13
|
let failures = 0;
|
|
13
14
|
|
|
14
|
-
const { apiKey
|
|
15
|
-
|
|
16
|
-
if (!apiKey) {
|
|
17
|
-
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
const client = new TestomatClient({ apiKey });
|
|
15
|
+
const client = new TestomatClient({ apiKey: opts?.reporterOptions?.apiKey });
|
|
21
16
|
|
|
22
17
|
runner.on(EVENT_RUN_BEGIN, () => {
|
|
23
18
|
client.createRun();
|
|
@@ -25,7 +20,7 @@ function MochaReporter(runner, opts) {
|
|
|
25
20
|
|
|
26
21
|
runner.on(EVENT_TEST_PASS, test => {
|
|
27
22
|
passes += 1;
|
|
28
|
-
console.log('
|
|
23
|
+
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
29
24
|
const testId = parseTest(test.title);
|
|
30
25
|
client.addTestRun(testId, TRConstants.PASSED, {
|
|
31
26
|
title: test.title,
|
|
@@ -35,7 +30,7 @@ function MochaReporter(runner, opts) {
|
|
|
35
30
|
|
|
36
31
|
runner.on(EVENT_TEST_FAIL, (test, err) => {
|
|
37
32
|
failures += 1;
|
|
38
|
-
console.log('
|
|
33
|
+
console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
|
|
39
34
|
const testId = parseTest(test.title);
|
|
40
35
|
client.addTestRun(testId, TRConstants.FAILED, {
|
|
41
36
|
error: err,
|
|
@@ -45,8 +40,8 @@ function MochaReporter(runner, opts) {
|
|
|
45
40
|
});
|
|
46
41
|
|
|
47
42
|
runner.on(EVENT_RUN_END, () => {
|
|
48
|
-
console.log('end: %d/%d', passes, passes + failures);
|
|
49
43
|
const status = failures === 0 ? TRConstants.PASSED : TRConstants.FAILED;
|
|
44
|
+
console.log(chalk.bold(status), `${passes} passed, ${failures} failed`);
|
|
50
45
|
client.updateRunStatus(status);
|
|
51
46
|
});
|
|
52
47
|
}
|
|
@@ -10,13 +10,7 @@ const { parseTest } = require('../util');
|
|
|
10
10
|
|
|
11
11
|
class TestomatioReporter {
|
|
12
12
|
constructor(config = {}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!apiKey) {
|
|
16
|
-
console.log('API Key is not provided. Testomat.io report is disabled');
|
|
17
|
-
} else {
|
|
18
|
-
this.client = new TestomatioClient({ apiKey });
|
|
19
|
-
}
|
|
13
|
+
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
20
14
|
|
|
21
15
|
this.videos = [];
|
|
22
16
|
}
|
package/lib/adapter/webdriver.js
CHANGED
|
@@ -7,10 +7,7 @@ class WebdriverReporter extends WDIOReporter {
|
|
|
7
7
|
constructor(options) {
|
|
8
8
|
super(options);
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
if (!apiKey) return;
|
|
12
|
-
|
|
13
|
-
this.client = new TestomatClient({ apiKey });
|
|
10
|
+
this.client = new TestomatClient({ apiKey: options?.apiKey });
|
|
14
11
|
options = Object.assign(options, { stdout: true });
|
|
15
12
|
|
|
16
13
|
this._addTestPromises = [];
|
package/lib/client.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
-
const JsonCycle = require('json-cycle');
|
|
3
2
|
const createCallsiteRecord = require('callsite-record');
|
|
4
3
|
const { sep, join } = require('path');
|
|
5
4
|
const fs = require('fs');
|
|
6
5
|
const chalk = require('chalk');
|
|
7
6
|
const upload = require('./fileUploader');
|
|
8
|
-
const { isValidUrl } = require('./util');
|
|
9
7
|
const { PASSED, FAILED, FINISHED, APP_PREFIX } = require('./constants');
|
|
8
|
+
const pipesFactory = require('./pipe');
|
|
10
9
|
|
|
11
|
-
const
|
|
12
|
-
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
|
|
10
|
+
const { TESTOMATIO_ENV } = process.env;
|
|
13
11
|
|
|
14
|
-
if (TESTOMATIO_RUN) {
|
|
15
|
-
process.env.runId = TESTOMATIO_RUN;
|
|
16
|
-
}
|
|
17
12
|
|
|
18
13
|
class TestomatClient {
|
|
19
14
|
/**
|
|
@@ -21,18 +16,17 @@ class TestomatClient {
|
|
|
21
16
|
*
|
|
22
17
|
* @param {*} params
|
|
23
18
|
*/
|
|
24
|
-
constructor(params) {
|
|
25
|
-
this.apiKey = params.apiKey || process.env.TESTOMATIO;
|
|
19
|
+
constructor(params = {}) {
|
|
26
20
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
27
|
-
this.createNewTests = !!process.env.TESTOMATIO_CREATE;
|
|
28
|
-
this.proceed = process.env.TESTOMATIO_PROCEED;
|
|
29
21
|
this.env = TESTOMATIO_ENV;
|
|
30
22
|
this.parallel = params.parallel;
|
|
31
|
-
|
|
23
|
+
const store = {}
|
|
24
|
+
this.pipes = pipesFactory(params, store);
|
|
32
25
|
this.queue = Promise.resolve();
|
|
33
26
|
this.axios = axios.create();
|
|
34
27
|
this.totalUploaded = 0;
|
|
35
28
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
29
|
+
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
/**
|
|
@@ -41,47 +35,18 @@ class TestomatClient {
|
|
|
41
35
|
* @returns {Promise} - resolves to Run id which should be used to update / add test
|
|
42
36
|
*/
|
|
43
37
|
createRun() {
|
|
44
|
-
|
|
45
|
-
if (!this.
|
|
38
|
+
// all pipes disabled, skipping
|
|
39
|
+
if (!this.pipes.filter(p => p.isEnabled).length) return;
|
|
40
|
+
|
|
46
41
|
const runParams = {
|
|
47
|
-
api_key: this.apiKey.trim(),
|
|
48
42
|
title: this.title,
|
|
49
43
|
parallel: this.parallel,
|
|
50
44
|
env: this.env,
|
|
51
|
-
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
52
45
|
};
|
|
53
46
|
|
|
54
47
|
global.testomatioArtifacts = [];
|
|
55
48
|
|
|
56
|
-
|
|
57
|
-
console.log(
|
|
58
|
-
APP_PREFIX,
|
|
59
|
-
chalk.red(`Error creating report on Testomat.io, report url '${TESTOMAT_URL}' is invalid`),
|
|
60
|
-
);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (runId) {
|
|
65
|
-
this.runId = runId;
|
|
66
|
-
this.queue = this.queue.then(() => axios.put(`${TESTOMAT_URL.trim()}/api/reporter/${runId}`, runParams));
|
|
67
|
-
return Promise.resolve(runId);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.queue = this.queue
|
|
71
|
-
.then(() =>
|
|
72
|
-
this.axios.post(`${TESTOMAT_URL.trim()}/api/reporter`, runParams).then(resp => {
|
|
73
|
-
this.runId = resp.data.uid;
|
|
74
|
-
this.runUrl = `${TESTOMAT_URL}/${resp.data.url.split('/').splice(3).join('/')}`;
|
|
75
|
-
console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId, chalk.gray(`v${this.version}`));
|
|
76
|
-
process.env.runId = this.runId;
|
|
77
|
-
}),
|
|
78
|
-
)
|
|
79
|
-
.catch(() => {
|
|
80
|
-
console.log(
|
|
81
|
-
APP_PREFIX,
|
|
82
|
-
'Error creating report Testomat.io, please check if your API key is valid. Skipping report',
|
|
83
|
-
);
|
|
84
|
-
});
|
|
49
|
+
this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))));
|
|
85
50
|
|
|
86
51
|
return this.queue;
|
|
87
52
|
}
|
|
@@ -92,6 +57,9 @@ class TestomatClient {
|
|
|
92
57
|
* @returns {Promise}
|
|
93
58
|
*/
|
|
94
59
|
async addTestRun(testId, status, testData = {}) {
|
|
60
|
+
// all pipes disabled, skipping
|
|
61
|
+
if (!this.pipes.filter(p => p.isEnabled).length) return;
|
|
62
|
+
|
|
95
63
|
const {
|
|
96
64
|
error = '',
|
|
97
65
|
time = '',
|
|
@@ -120,73 +88,42 @@ class TestomatClient {
|
|
|
120
88
|
stack = this.formatSteps(stack, steps);
|
|
121
89
|
}
|
|
122
90
|
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
for (const file of files) {
|
|
130
|
-
uploadedFiles.push(upload.uploadFileByPath(file, this.runId));
|
|
131
|
-
}
|
|
91
|
+
if (Array.isArray(global.testomatioArtifacts)) {
|
|
92
|
+
files.push(...global.testomatioArtifacts);
|
|
93
|
+
global.testomatioArtifacts = [];
|
|
94
|
+
}
|
|
132
95
|
|
|
133
|
-
|
|
134
|
-
|
|
96
|
+
for (const file of files) {
|
|
97
|
+
uploadedFiles.push(upload.uploadFileByPath(file, this.runId));
|
|
98
|
+
}
|
|
135
99
|
|
|
136
|
-
|
|
137
|
-
}
|
|
100
|
+
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
101
|
+
const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
|
|
102
|
+
uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.runId));
|
|
138
103
|
}
|
|
139
104
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
message,
|
|
161
|
-
run_time: time,
|
|
162
|
-
artifacts: await Promise.all(uploadedFiles),
|
|
163
|
-
});
|
|
164
|
-
return this.axios.post(`${TESTOMAT_URL}/api/reporter/${this.runId}/testrun`, json, {
|
|
165
|
-
maxContentLength: Infinity,
|
|
166
|
-
maxBodyLength: Infinity,
|
|
167
|
-
headers: {
|
|
168
|
-
// Overwrite Axios's automatically set Content-Type
|
|
169
|
-
'Content-Type': 'application/json',
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
})
|
|
173
|
-
.catch(err => {
|
|
174
|
-
if (err.response) {
|
|
175
|
-
if (err.response.status >= 400) {
|
|
176
|
-
const data = err.response.data || { message: '' };
|
|
177
|
-
console.log(
|
|
178
|
-
APP_PREFIX,
|
|
179
|
-
chalk.blue(title),
|
|
180
|
-
`Report couldn't be processed: (${err.response.status}) ${data.message}`,
|
|
181
|
-
);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
console.log(APP_PREFIX, chalk.blue(title), `Report couldn't be processed: ${err.response.data.message}`);
|
|
185
|
-
} else {
|
|
186
|
-
console.log(APP_PREFIX, chalk.blue(title), "Report couldn't be processed", err);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
105
|
+
const artifacts = await Promise.all(uploadedFiles)
|
|
106
|
+
|
|
107
|
+
global.testomatioArtifacts = [];
|
|
108
|
+
|
|
109
|
+
this.totalUploaded += uploadedFiles.filter(n => n).length;
|
|
110
|
+
|
|
111
|
+
const data = {
|
|
112
|
+
files,
|
|
113
|
+
steps,
|
|
114
|
+
status,
|
|
115
|
+
stack,
|
|
116
|
+
example,
|
|
117
|
+
title,
|
|
118
|
+
suite_title,
|
|
119
|
+
suite_id,
|
|
120
|
+
test_id,
|
|
121
|
+
message,
|
|
122
|
+
run_time: parseFloat(time),
|
|
123
|
+
artifacts,
|
|
124
|
+
};
|
|
189
125
|
|
|
126
|
+
this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.addTest(data))));
|
|
190
127
|
return this.queue;
|
|
191
128
|
}
|
|
192
129
|
|
|
@@ -196,41 +133,26 @@ class TestomatClient {
|
|
|
196
133
|
* @returns {Promise}
|
|
197
134
|
*/
|
|
198
135
|
updateRunStatus(status, isParallel) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
`🗄️ Total ${this.totalUploaded} artifacts ${
|
|
220
|
-
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
221
|
-
} uploaded to S3 bucket `,
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (this.runUrl && this.proceed) {
|
|
226
|
-
const notFinishedMessage = chalk.yellow.bold('Run was not finished because of $TESTOMATIO_PROCEED');
|
|
227
|
-
console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${chalk.magenta(this.runUrl)}`);
|
|
228
|
-
console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
.catch(err => {
|
|
232
|
-
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
233
|
-
});
|
|
136
|
+
// all pipes disabled, skipping
|
|
137
|
+
if (!this.pipes.filter(p => p.isEnabled).length) return;
|
|
138
|
+
|
|
139
|
+
let statusEvent;
|
|
140
|
+
if (status === FINISHED) statusEvent = 'finish';
|
|
141
|
+
if (status === PASSED) statusEvent = 'pass';
|
|
142
|
+
if (status === FAILED) statusEvent = 'fail';
|
|
143
|
+
if (isParallel) statusEvent += '_parallel';
|
|
144
|
+
const runParams = { status, statusEvent }
|
|
145
|
+
|
|
146
|
+
this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))));
|
|
147
|
+
|
|
148
|
+
if (upload.isArtifactsEnabled && this.totalUploaded > 0) {
|
|
149
|
+
console.log(
|
|
150
|
+
APP_PREFIX,
|
|
151
|
+
`🗄️ Total ${this.totalUploaded} artifacts ${
|
|
152
|
+
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
153
|
+
} uploaded to S3 bucket `,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
234
156
|
return this.queue;
|
|
235
157
|
}
|
|
236
158
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const Adapter = require('./adapter');
|
|
2
|
+
const JavaScriptAdapter = require('./javascript');
|
|
3
|
+
const JavaAdapter = require('./java');
|
|
4
|
+
const PythonAdapter = require('./python');
|
|
5
|
+
const RubyAdapter = require('./ruby');
|
|
6
|
+
const CSharpAdapter = require('./csharp');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
module.exports = function (lang, opts) {
|
|
10
|
+
if (!lang) return new Adapter(opts);
|
|
11
|
+
|
|
12
|
+
if (lang === 'java') {
|
|
13
|
+
return new JavaAdapter(opts);
|
|
14
|
+
}
|
|
15
|
+
if (lang === 'js') {
|
|
16
|
+
return new JavaScriptAdapter(opts);
|
|
17
|
+
}
|
|
18
|
+
if (lang === 'python') {
|
|
19
|
+
return new PythonAdapter(opts);
|
|
20
|
+
}
|
|
21
|
+
if (lang === 'ruby') {
|
|
22
|
+
return new RubyAdapter(opts);
|
|
23
|
+
}
|
|
24
|
+
if (lang === 'c#' || lang === 'csharp') {
|
|
25
|
+
return new CSharpAdapter(opts);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { Octokit } = require("@octokit/rest");
|
|
3
|
+
const { APP_PREFIX } = require('../constants');
|
|
4
|
+
const { ansiRegExp, isSameTest } = require('../util');
|
|
5
|
+
const merge = require('lodash.merge');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitHubPipe {
|
|
9
|
+
|
|
10
|
+
isEnabled = false;
|
|
11
|
+
|
|
12
|
+
constructor(params, store = {}) {
|
|
13
|
+
this.store = store;
|
|
14
|
+
this.tests = []
|
|
15
|
+
this.token = params.GH_PAT || process.env.GH_PAT;
|
|
16
|
+
this.ref = process.env.GITHUB_REF
|
|
17
|
+
this.repo = process.env.GITHUB_REPOSITORY
|
|
18
|
+
|
|
19
|
+
if (process.env.DEBUG) {
|
|
20
|
+
console.log(APP_PREFIX, 'GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', this.ref, this.repo);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!this.token || !this.ref || !this.repo) return;
|
|
24
|
+
this.isEnabled = true;
|
|
25
|
+
const matchedIssue = this.ref.match(/refs\/pull\/(\d+)\/merge/);
|
|
26
|
+
if (!matchedIssue) return;
|
|
27
|
+
this.issue = matchedIssue[1]
|
|
28
|
+
this.start = new Date();
|
|
29
|
+
|
|
30
|
+
if (process.env.DEBUG) {
|
|
31
|
+
console.log(APP_PREFIX, 'GitHub Pipe: Enabled');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async createRun() {}
|
|
36
|
+
|
|
37
|
+
updateRun() {}
|
|
38
|
+
|
|
39
|
+
addTest(test) {
|
|
40
|
+
if (!this.isEnabled) return;
|
|
41
|
+
|
|
42
|
+
const index = this.tests.findIndex(t => isSameTest(t, test))
|
|
43
|
+
// update if they were already added
|
|
44
|
+
if (index >= 0) {
|
|
45
|
+
this.tests[index] = merge(this.tests[index], test);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.tests.push(test)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async finishRun(runParams) {
|
|
53
|
+
if (!this.isEnabled) return;
|
|
54
|
+
|
|
55
|
+
if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
this.octokit = new Octokit({
|
|
59
|
+
auth: this.token,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const [owner, repo] = this.repo.split('/');
|
|
63
|
+
if (!(owner || repo)) return;
|
|
64
|
+
|
|
65
|
+
// ... create a comment on GitHub
|
|
66
|
+
const passedCount = this.tests.filter(t => t.status === 'passed').length;
|
|
67
|
+
const failedCount = this.tests.filter(t => t.status === 'failed').length;
|
|
68
|
+
const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
|
|
69
|
+
|
|
70
|
+
let summary = `
|
|
71
|
+
### Run Report
|
|
72
|
+
|
|
73
|
+
| | ${statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
76
|
+
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji('passed')} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
77
|
+
| Duration | 🕐 **${parseFloat(this.tests.reduce((a, t) => a + (t.run_time || 0), 0)).toFixed(2)}**ms |
|
|
78
|
+
`
|
|
79
|
+
if (this.store.runUrl) {
|
|
80
|
+
summary += `| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
|
|
81
|
+
}
|
|
82
|
+
if (process.env.GITHUB_WORKFLOW) {
|
|
83
|
+
summary += `| Workflow | 🗂️ ${process.env.GITHUB_WORKFLOW} | `;
|
|
84
|
+
}
|
|
85
|
+
if (process.env.RUNNER_OS) {
|
|
86
|
+
summary += `| Operating System | 🖥️ \`${process.env.RUNNER_OS}\` ${process.env.RUNNER_ARCH || ''} | `;
|
|
87
|
+
}
|
|
88
|
+
if (process.env.GITHUB_HEAD_REF) {
|
|
89
|
+
summary += `| Branch | 🌳 \`${process.env.GITHUB_HEAD_REF}\` | `;
|
|
90
|
+
}
|
|
91
|
+
if (process.env.GITHUB_RUN_ATTEMPT) {
|
|
92
|
+
summary += `| Run Attempt | 🌒 \`${process.env.GITHUB_RUN_ATTEMPT}\` | `;
|
|
93
|
+
}
|
|
94
|
+
if (process.env.GITHUB_RUN_ID) {
|
|
95
|
+
summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${this.repo}/actions/runs/${process.env.GITHUB_RUN_ID} | `;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
const failures = this.tests.filter(t => t.status === 'failed').slice(0, 20).map(t => {
|
|
100
|
+
let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
|
|
101
|
+
text += "\n\n"
|
|
102
|
+
if (t.message) text += "> " + t.message.replace(/[^\x20-\x7E]/g, '').replace(ansiRegExp(), '').trim() + "\n"
|
|
103
|
+
if (t.stack) text += "```diff\n" + t.stack.replace(ansiRegExp(), '').trim() + "\n```\n";
|
|
104
|
+
|
|
105
|
+
if (t.artifacts && t.artifacts.length && !process.env.TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
106
|
+
t.artifacts.filter(f => f.endsWith('.png')).forEach(f => {
|
|
107
|
+
if (f.endsWith('.png')) return text+= `\n`
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
text += "\n---\n"
|
|
112
|
+
|
|
113
|
+
return text;
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
let body = summary;
|
|
117
|
+
|
|
118
|
+
if (failures.length) {
|
|
119
|
+
body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
|
|
120
|
+
if (failures.length > 20) {
|
|
121
|
+
body += "\n> Notice\n> Only first 20 failures shown*"
|
|
122
|
+
}
|
|
123
|
+
body += "\n\n</details>\n";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (this.tests.length > 0) {
|
|
127
|
+
body += "\n<details>\n<summary><h3>🐢 Slowest Tests</h3>\n\n"
|
|
128
|
+
body += this.tests.sort((a, b) => b?.run_time - a?.run_time).slice(0, 5).map(t => {
|
|
129
|
+
return `* ${fullName(t)} (${parseFloat(t.run_time).toFixed(2)}ms)`
|
|
130
|
+
}).join('\n')
|
|
131
|
+
body += "\n</details>"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const resp = await this.octokit.rest.issues.createComment({
|
|
136
|
+
owner,
|
|
137
|
+
repo,
|
|
138
|
+
issue_number: this.issue,
|
|
139
|
+
body,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const url = resp.data?.html_url;
|
|
143
|
+
this.store.githubUrl = url;
|
|
144
|
+
|
|
145
|
+
console.log(
|
|
146
|
+
APP_PREFIX,
|
|
147
|
+
chalk.yellow('GitHub'),
|
|
148
|
+
`Report created: ${chalk.magenta(url)}`,
|
|
149
|
+
);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.log(
|
|
152
|
+
APP_PREFIX,
|
|
153
|
+
chalk.yellow('GitHub'),
|
|
154
|
+
`Couldn't create GitHub report ${err}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
toString() {
|
|
160
|
+
return 'GitHub Reporter'
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function statusEmoji(status) {
|
|
165
|
+
if (status === 'passed') return '🟢';
|
|
166
|
+
if (status === 'failed') return '🔴';
|
|
167
|
+
if (status === 'skipped') return '🟡';
|
|
168
|
+
return ''
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function fullName(t) {
|
|
172
|
+
let line = '';
|
|
173
|
+
if (t.suite_title) line = `${t.suite_title}: `;
|
|
174
|
+
line += `**${t.title}**`
|
|
175
|
+
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
176
|
+
return line;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = GitHubPipe;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { APP_PREFIX } = require('../constants');
|
|
5
|
+
const TestomatioPipe = require('./testomatio');
|
|
6
|
+
const GitHubPipe = require('./github');
|
|
7
|
+
|
|
8
|
+
module.exports = function(params, opts) {
|
|
9
|
+
const extraPipes = [];
|
|
10
|
+
|
|
11
|
+
// Add extra pipes into package.json file:
|
|
12
|
+
// "testomatio": {
|
|
13
|
+
// "pipes": ["my-module-pipe", "./local/js/file/pipe"]
|
|
14
|
+
// }
|
|
15
|
+
|
|
16
|
+
const packageJsonFile = path.join(process.cwd(), 'package.json');
|
|
17
|
+
if (fs.existsSync(packageJsonFile)) {
|
|
18
|
+
const package = fs.readFileSync(packageJsonFile);
|
|
19
|
+
const pipeDefs = package?.testomatio?.pipes || [];
|
|
20
|
+
|
|
21
|
+
for (const pipeDef of pipeDefs) {
|
|
22
|
+
let pipeClass;
|
|
23
|
+
try {
|
|
24
|
+
pipeClass = require(pipeDef);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.log(APP_PREFIX, `Can't load module Testomatio pipe module from ${pipeDef}`);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
extraPipes.push(new pipeClass(params, opts))
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.log(APP_PREFIX, `Can't instantiate Testomatio for ${pipeDef}`, err);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const pipes = [
|
|
41
|
+
new TestomatioPipe(params, opts),
|
|
42
|
+
new GitHubPipe(params, opts),
|
|
43
|
+
...extraPipes
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
console.log(APP_PREFIX, chalk.cyan('Pipes:'), chalk.cyan(pipes.filter(p => p.isEnabled).map(p => p.toString()).join(', ') || "No pipes enabled"));
|
|
47
|
+
|
|
48
|
+
return pipes;
|
|
49
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
const JsonCycle = require('json-cycle');
|
|
4
|
+
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_RUN } = process.env;
|
|
5
|
+
const { APP_PREFIX } = require('../constants');
|
|
6
|
+
const { isValidUrl } = require('../util');
|
|
7
|
+
|
|
8
|
+
if (TESTOMATIO_RUN) {
|
|
9
|
+
process.env.runId = TESTOMATIO_RUN;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class TestomatioPipe {
|
|
13
|
+
isEnabled = false;
|
|
14
|
+
|
|
15
|
+
constructor(params, store = {}) {
|
|
16
|
+
this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
17
|
+
this.apiKey = params.apiKey || process.env.TESTOMATIO;
|
|
18
|
+
if (process.env.DEBUG) {
|
|
19
|
+
console.log(APP_PREFIX, 'Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
|
|
20
|
+
}
|
|
21
|
+
if (!this.apiKey) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (process.env.DEBUG) {
|
|
25
|
+
console.log(APP_PREFIX, 'Testomatio Pipe: Enabled');
|
|
26
|
+
}
|
|
27
|
+
this.store = store;
|
|
28
|
+
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
29
|
+
this.axios = axios.create();
|
|
30
|
+
this.isEnabled = true;
|
|
31
|
+
this.proceed = process.env.TESTOMATIO_PROCEED;
|
|
32
|
+
this.runId = params.runId || process.env.runId;
|
|
33
|
+
this.createNewTests = !!process.env.TESTOMATIO_CREATE;
|
|
34
|
+
|
|
35
|
+
if (!isValidUrl(this.url.trim())) {
|
|
36
|
+
this.isEnabled = false;
|
|
37
|
+
console.log(
|
|
38
|
+
APP_PREFIX,
|
|
39
|
+
chalk.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`),
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async createRun(runParams) {
|
|
46
|
+
if (!this.isEnabled) return;
|
|
47
|
+
|
|
48
|
+
runParams.api_key = this.apiKey.trim();
|
|
49
|
+
runParams.group_title = TESTOMATIO_RUNGROUP_TITLE;
|
|
50
|
+
|
|
51
|
+
if (this.runId) {
|
|
52
|
+
return this.axios.put(`${this.url.trim()}/api/reporter/${this.runId}`, runParams)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const resp = await this.axios.post(`${this.url.trim()}/api/reporter`, runParams, {
|
|
57
|
+
maxContentLength: Infinity,
|
|
58
|
+
maxBodyLength: Infinity,
|
|
59
|
+
})
|
|
60
|
+
this.runId = resp.data.uid;
|
|
61
|
+
this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
|
|
62
|
+
this.store.runUrl = this.runUrl;
|
|
63
|
+
this.store.runId = this.runId;
|
|
64
|
+
console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId);
|
|
65
|
+
process.env.runId = this.runId;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
console.log(
|
|
68
|
+
APP_PREFIX,
|
|
69
|
+
'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async addTest(data) {
|
|
75
|
+
if (!this.isEnabled) return;
|
|
76
|
+
if (!this.runId) return;
|
|
77
|
+
data.api_key = this.apiKey;
|
|
78
|
+
data.create = this.createNewTests;
|
|
79
|
+
const json = JsonCycle.stringify(data);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
return await this.axios.post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
|
|
83
|
+
maxContentLength: Infinity,
|
|
84
|
+
maxBodyLength: Infinity,
|
|
85
|
+
headers: {
|
|
86
|
+
// Overwrite Axios's automatically set Content-Type
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (err.response) {
|
|
92
|
+
if (err.response.status >= 400) {
|
|
93
|
+
const data = err.response.data || { message: '' };
|
|
94
|
+
console.log(
|
|
95
|
+
APP_PREFIX,
|
|
96
|
+
chalk.blue(this.title),
|
|
97
|
+
`Report couldn't be processed: (${err.response.status}) ${data.message}`,
|
|
98
|
+
);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
console.log(APP_PREFIX, chalk.blue(this.title), `Report couldn't be processed: ${err.response.data.message}`);
|
|
102
|
+
} else {
|
|
103
|
+
console.log(APP_PREFIX, chalk.blue(this.title), "Report couldn't be processed", err);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async finishRun(params) {
|
|
110
|
+
if (!this.isEnabled) return;
|
|
111
|
+
try {
|
|
112
|
+
if (this.runId && !this.proceed) {
|
|
113
|
+
await this.axios.put(`${this.url}/api/reporter/${this.runId}`, {
|
|
114
|
+
api_key: this.apiKey,
|
|
115
|
+
status_event: params.statusEvent,
|
|
116
|
+
status: params.status,
|
|
117
|
+
});
|
|
118
|
+
if (this.runUrl) {
|
|
119
|
+
console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (this.runUrl && this.proceed) {
|
|
123
|
+
const notFinishedMessage = chalk.yellow.bold('Run was not finished because of $TESTOMATIO_PROCEED');
|
|
124
|
+
console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${chalk.magenta(this.runUrl)}`);
|
|
125
|
+
console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
toString() {
|
|
133
|
+
return 'Testomatio Reporter';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = TestomatioPipe;
|
package/lib/util.js
CHANGED
|
@@ -135,13 +135,15 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
135
135
|
}
|
|
136
136
|
return result.join('\n');
|
|
137
137
|
}
|
|
138
|
-
|
|
139
138
|
}
|
|
140
139
|
|
|
140
|
+
const isSameTest = (test, t) => t.title == test.title && t.suite_title == test.suite_title && Object.values(t.example) == Object.values(test.example) && t.test_id == test.test_id
|
|
141
|
+
|
|
141
142
|
module.exports = {
|
|
142
143
|
parseTest,
|
|
143
144
|
parseSuite,
|
|
144
145
|
ansiRegExp,
|
|
146
|
+
isSameTest,
|
|
145
147
|
isValidUrl,
|
|
146
148
|
fetchSourceCode,
|
|
147
149
|
fetchSourceCodeFromStackTrace,
|
package/lib/xmlReader.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
|
-
const axios = require('axios');
|
|
3
2
|
const chalk = require('chalk');
|
|
4
3
|
const fs = require("fs");
|
|
5
4
|
|
|
6
5
|
// const util = require("util"); // you can see a result
|
|
7
6
|
const { XMLParser } = require("fast-xml-parser");
|
|
8
7
|
const { APP_PREFIX, PASSED, FAILED, SKIPPED } = require('./constants');
|
|
9
|
-
const {
|
|
8
|
+
const { fetchFilesFromStackTrace, fetchSourceCode, fetchSourceCodeFromStackTrace } = require('./util');
|
|
10
9
|
const upload = require('./fileUploader');
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const JavaAdapter = require('./junit-adapter/java');
|
|
14
|
-
const PythonAdapter = require('./junit-adapter/python');
|
|
15
|
-
const RubyAdapter = require('./junit-adapter/ruby');
|
|
16
|
-
const CSharpAdapter = require('./junit-adapter/csharp');
|
|
10
|
+
const pipesFactory = require('./pipe');
|
|
11
|
+
const adapterFactory = require('./junit-adapter');
|
|
17
12
|
|
|
18
13
|
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || "https://app.testomat.io";
|
|
19
14
|
const TESTOMATIO = process.env.TESTOMATIO; // key?
|
|
@@ -40,32 +35,24 @@ class XmlReader {
|
|
|
40
35
|
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
41
36
|
};
|
|
42
37
|
this.runId = opts.runId || TESTOMATIO_RUN;
|
|
43
|
-
this.adapter =
|
|
38
|
+
this.adapter = adapterFactory(null, opts)
|
|
44
39
|
this.opts = opts;
|
|
45
|
-
|
|
40
|
+
const store = {}
|
|
41
|
+
this.pipes = pipesFactory(opts, store);
|
|
42
|
+
|
|
46
43
|
this.parser = new XMLParser(options);
|
|
47
44
|
this.tests = []
|
|
48
45
|
this.stats = {}
|
|
49
46
|
this.stats.language = opts.lang?.toLowerCase();
|
|
50
47
|
this.filesToUpload = {}
|
|
48
|
+
|
|
49
|
+
this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
|
|
50
|
+
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
connectAdapter() {
|
|
54
|
-
if (this.opts.javaTests
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
if (this.stats.language === 'js') {
|
|
58
|
-
this.adapter = new JavaScriptAdapter(this.opts);
|
|
59
|
-
}
|
|
60
|
-
if (this.stats.language === 'python') {
|
|
61
|
-
this.adapter = new PythonAdapter(this.opts);
|
|
62
|
-
}
|
|
63
|
-
if (this.stats.language === 'ruby') {
|
|
64
|
-
this.adapter = new RubyAdapter(this.opts);
|
|
65
|
-
}
|
|
66
|
-
if (this.stats.language === 'c#' || this.stats.language === 'csharp') {
|
|
67
|
-
this.adapter = new CSharpAdapter(this.opts);
|
|
68
|
-
}
|
|
54
|
+
if (this.opts.javaTests) return adapterFactory('java', this.opts);
|
|
55
|
+
return adapterFactory(this.stats.language, this.opts);
|
|
69
56
|
}
|
|
70
57
|
|
|
71
58
|
parse(fileName) {
|
|
@@ -149,7 +136,7 @@ class XmlReader {
|
|
|
149
136
|
|
|
150
137
|
const results = jsonSuite?.TestRun?.Results?.UnitTestResult?.map(td => ({
|
|
151
138
|
id: td.executionId,
|
|
152
|
-
run_time: td.duration,
|
|
139
|
+
run_time: parseFloat(td.duration),
|
|
153
140
|
status: td.outcome,
|
|
154
141
|
stack: td.Output.StdOut
|
|
155
142
|
}));
|
|
@@ -276,7 +263,6 @@ class XmlReader {
|
|
|
276
263
|
}
|
|
277
264
|
|
|
278
265
|
async uploadArtifacts() {
|
|
279
|
-
if (!this.runId) return;
|
|
280
266
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
281
267
|
const files = fetchFilesFromStackTrace(test.stack);
|
|
282
268
|
test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, this.runId)));
|
|
@@ -284,8 +270,6 @@ class XmlReader {
|
|
|
284
270
|
}
|
|
285
271
|
|
|
286
272
|
async createRun() {
|
|
287
|
-
if (this.runId) return;
|
|
288
|
-
|
|
289
273
|
const runParams = {
|
|
290
274
|
api_key: this.requestParams.apiKey,
|
|
291
275
|
title: this.requestParams.title,
|
|
@@ -295,30 +279,7 @@ class XmlReader {
|
|
|
295
279
|
|
|
296
280
|
if (process.env.DEBUG) console.log("Run", runParams);
|
|
297
281
|
|
|
298
|
-
|
|
299
|
-
const resp = await this.axios.post(this.url, runParams, {
|
|
300
|
-
maxContentLength: Infinity,
|
|
301
|
-
maxBodyLength: Infinity,
|
|
302
|
-
headers: {
|
|
303
|
-
// Overwrite Axios's automatically set Content-Type
|
|
304
|
-
'Content-Type': 'application/json',
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
if (resp.status >= 400) {
|
|
308
|
-
const data = resp.data || { message: '' };
|
|
309
|
-
console.log(
|
|
310
|
-
APP_PREFIX,
|
|
311
|
-
`Report couldn't be processed: (${resp.status}) ${data.message}`,
|
|
312
|
-
);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
this.runId = resp.data.uid;
|
|
316
|
-
this.runUrl = `${TESTOMATIO_URL}/${resp.data.url.split('/').splice(3).join('/')}`;
|
|
317
|
-
} catch(err) {
|
|
318
|
-
if (process.env.DEBUG) console.log(err)
|
|
319
|
-
const data = err?.response?.data || { message: '' };
|
|
320
|
-
console.log(APP_PREFIX, 'Error creating run, skipping...', err?.response?.statusText, data);
|
|
321
|
-
}
|
|
282
|
+
return Promise.all(this.pipes.map(p => p.createRun(runParams)));
|
|
322
283
|
}
|
|
323
284
|
|
|
324
285
|
async uploadData() {
|
|
@@ -336,56 +297,14 @@ class XmlReader {
|
|
|
336
297
|
})
|
|
337
298
|
}
|
|
338
299
|
|
|
339
|
-
const dataString =
|
|
300
|
+
const dataString = {
|
|
340
301
|
...this.stats,
|
|
341
302
|
api_key: this.requestParams.apiKey,
|
|
342
303
|
status_event: 'finish',
|
|
343
304
|
tests: this.tests,
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
try {
|
|
347
|
-
const resp = await this.axios.put(`${this.url}/${this.runId}`, dataString, {
|
|
348
|
-
maxContentLength: Infinity,
|
|
349
|
-
maxBodyLength: Infinity,
|
|
350
|
-
headers: {
|
|
351
|
-
// Overwrite Axios's automatically set Content-Type
|
|
352
|
-
'Content-Type': 'application/json',
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
if (resp.status >= 400) {
|
|
357
|
-
const data = resp.data || { message: '' };
|
|
358
|
-
console.log(
|
|
359
|
-
APP_PREFIX,
|
|
360
|
-
`Report couldn't be processed: (${resp.status}) ${data.message}`,
|
|
361
|
-
);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (this.runUrl) {
|
|
366
|
-
console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
|
|
367
|
-
}
|
|
368
|
-
process.env.runId = this.runId;
|
|
369
|
-
return resp;
|
|
370
|
-
} catch (err) {
|
|
371
|
-
// if (process.env.DEBUG) console.log(err.response)
|
|
372
|
-
const data = err.response.data || { message: '' };
|
|
373
|
-
console.log(APP_PREFIX, 'Error uploading, skipping...', err.response.statusText, data);
|
|
374
|
-
return err.response;
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
get url() {
|
|
380
|
-
if (!isValidUrl(this.requestParams.url)) {
|
|
381
|
-
console.log(
|
|
382
|
-
APP_PREFIX,
|
|
383
|
-
chalk.red(`Error creating report on Testomat.io, report url '${this.requestParams.url}' is invalid`),
|
|
384
|
-
);
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
305
|
+
};
|
|
387
306
|
|
|
388
|
-
return
|
|
307
|
+
return Promise.all(this.pipes.map(p => p.finishRun(dataString)));
|
|
389
308
|
}
|
|
390
309
|
}
|
|
391
310
|
|
|
@@ -426,7 +345,7 @@ function reduceTestCases(prev, item) {
|
|
|
426
345
|
stack,
|
|
427
346
|
message,
|
|
428
347
|
line: testCaseItem.lineno,
|
|
429
|
-
run_time: testCaseItem.time || testCaseItem.duration,
|
|
348
|
+
run_time: parseFloat(testCaseItem.time || testCaseItem.duration),
|
|
430
349
|
status,
|
|
431
350
|
title: testCaseItem.name,
|
|
432
351
|
suite_title: reduceOptions.preferClassname ? testCaseItem.classname : (item.name || testCaseItem.classname),
|
|
@@ -449,4 +368,3 @@ function processTestSuite(testsuite) {
|
|
|
449
368
|
|
|
450
369
|
return res;
|
|
451
370
|
}
|
|
452
|
-
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-beta.10",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"repository": "git@github.com:testomatio/reporter.git",
|
|
7
7
|
"author": "Michael Bodnarchuk <davert@testomat.io>,Koushik Mohan <koushikmohan1996@gmail.com>",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"dependencies": {
|
|
10
|
+
"@octokit/rest": "^19.0.5",
|
|
10
11
|
"aws-sdk": "^2.1072.0",
|
|
11
12
|
"axios": "^0.25.0",
|
|
12
13
|
"callsite-record": "^4.1.4",
|
|
@@ -18,7 +19,8 @@
|
|
|
18
19
|
"has-flag": "^5.0.1",
|
|
19
20
|
"is-valid-path": "^0.1.1",
|
|
20
21
|
"json-cycle": "^1.3.0",
|
|
21
|
-
"lodash.memoize": "^4.1.2"
|
|
22
|
+
"lodash.memoize": "^4.1.2",
|
|
23
|
+
"lodash.merge": "^4.6.2"
|
|
22
24
|
},
|
|
23
25
|
"files": [
|
|
24
26
|
"bin",
|