@testomatio/reporter 1.0.12 → 1.0.14-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bin/reportXml.js +1 -0
- package/lib/client.js +3 -0
- package/lib/pipe/testomatio.js +64 -44
- package/lib/util.js +39 -4
- package/lib/xmlReader.js +50 -30
- package/package.json +1 -1
package/lib/bin/reportXml.js
CHANGED
package/lib/client.js
CHANGED
|
@@ -36,6 +36,7 @@ class Client {
|
|
|
36
36
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
37
37
|
*/
|
|
38
38
|
createRun() {
|
|
39
|
+
debug('Creating run...');
|
|
39
40
|
// all pipes disabled, skipping
|
|
40
41
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
41
42
|
|
|
@@ -59,6 +60,7 @@ class Client {
|
|
|
59
60
|
* @returns {Promise<PipeResult[]>}
|
|
60
61
|
*/
|
|
61
62
|
async addTestRun(status, testData, storeArtifacts = []) {
|
|
63
|
+
debug('Adding test run for test', testData?.test_id || 'unknown test');
|
|
62
64
|
// all pipes disabled, skipping
|
|
63
65
|
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
64
66
|
|
|
@@ -170,6 +172,7 @@ class Client {
|
|
|
170
172
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
171
173
|
*/
|
|
172
174
|
updateRunStatus(status, isParallel = false) {
|
|
175
|
+
debug('Updating run status...');
|
|
173
176
|
// all pipes disabled, skipping
|
|
174
177
|
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
175
178
|
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -33,7 +33,9 @@ class TestomatioPipe {
|
|
|
33
33
|
this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
|
|
34
34
|
this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
|
|
35
35
|
this.env = process.env.TESTOMATIO_ENV;
|
|
36
|
-
this.axios = axios.create(
|
|
36
|
+
this.axios = axios.create({
|
|
37
|
+
timeout: 30000,
|
|
38
|
+
});
|
|
37
39
|
this.isEnabled = true;
|
|
38
40
|
// do not finish this run (for parallel testing)
|
|
39
41
|
this.proceed = process.env.TESTOMATIO_PROCEED;
|
|
@@ -51,6 +53,7 @@ class TestomatioPipe {
|
|
|
51
53
|
* @returns Promise<void>
|
|
52
54
|
*/
|
|
53
55
|
async createRun() {
|
|
56
|
+
debug('Creating run...');
|
|
54
57
|
if (!this.isEnabled) return;
|
|
55
58
|
|
|
56
59
|
let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
|
|
@@ -58,7 +61,7 @@ class TestomatioPipe {
|
|
|
58
61
|
// GitHub Actions Url
|
|
59
62
|
if (!buildUrl && process.env.GITHUB_RUN_ID) {
|
|
60
63
|
// eslint-disable-next-line max-len
|
|
61
|
-
buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}
|
|
64
|
+
buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
// Azure DevOps Url
|
|
@@ -83,7 +86,7 @@ class TestomatioPipe {
|
|
|
83
86
|
env: this.env,
|
|
84
87
|
title: this.title,
|
|
85
88
|
shared_run: this.sharedRun,
|
|
86
|
-
}).filter(([, value]) => !!value)
|
|
89
|
+
}).filter(([, value]) => !!value),
|
|
87
90
|
);
|
|
88
91
|
|
|
89
92
|
if (this.runId) {
|
|
@@ -110,57 +113,65 @@ class TestomatioPipe {
|
|
|
110
113
|
console.error(
|
|
111
114
|
APP_PREFIX,
|
|
112
115
|
'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
|
|
116
|
+
err,
|
|
113
117
|
);
|
|
114
118
|
}
|
|
119
|
+
debug('Run created');
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
/**
|
|
118
|
-
*
|
|
119
|
-
* @param testData data
|
|
120
|
-
* @returns
|
|
123
|
+
*
|
|
124
|
+
* @param testData data
|
|
125
|
+
* @returns
|
|
121
126
|
*/
|
|
122
127
|
addTest(data) {
|
|
128
|
+
debug('Adding test...');
|
|
123
129
|
if (!this.isEnabled) return;
|
|
124
130
|
if (!this.runId) return;
|
|
125
131
|
data.api_key = this.apiKey;
|
|
126
132
|
data.create = this.createNewTests;
|
|
127
133
|
const json = JsonCycle.stringify(data);
|
|
128
134
|
|
|
129
|
-
return this.axios
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (err.response
|
|
140
|
-
|
|
135
|
+
return this.axios
|
|
136
|
+
.post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
|
|
137
|
+
maxContentLength: Infinity,
|
|
138
|
+
maxBodyLength: Infinity,
|
|
139
|
+
headers: {
|
|
140
|
+
// Overwrite Axios's automatically set Content-Type
|
|
141
|
+
'Content-Type': 'application/json',
|
|
142
|
+
},
|
|
143
|
+
})
|
|
144
|
+
.catch(err => {
|
|
145
|
+
if (err.response) {
|
|
146
|
+
if (err.response.status >= 400) {
|
|
147
|
+
const responseData = err.response.data || { message: '' };
|
|
148
|
+
console.log(
|
|
149
|
+
APP_PREFIX,
|
|
150
|
+
chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
151
|
+
chalk.grey(data?.title || ''),
|
|
152
|
+
);
|
|
153
|
+
if (err.response.data.message.includes('could not be matched')) {
|
|
154
|
+
this.hasUnmatchedTests = true;
|
|
155
|
+
}
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
141
158
|
console.log(
|
|
142
159
|
APP_PREFIX,
|
|
143
|
-
chalk.yellow(`Warning: ${
|
|
144
|
-
|
|
160
|
+
chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
161
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
145
162
|
);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
return;
|
|
163
|
+
} else {
|
|
164
|
+
console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
150
165
|
}
|
|
151
|
-
|
|
152
|
-
console.log(APP_PREFIX, chalk.blue(data?.title || ''), `Report couldn't be processed: ${err.response.data.message}`);
|
|
153
|
-
} else {
|
|
154
|
-
console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
166
|
+
});
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
/**
|
|
160
|
-
* @param {import('../../types').RunData} params
|
|
161
|
-
* @returns
|
|
170
|
+
* @param {import('../../types').RunData} params
|
|
171
|
+
* @returns
|
|
162
172
|
*/
|
|
163
173
|
async finishRun(params) {
|
|
174
|
+
debug('Finishing run...');
|
|
164
175
|
if (!this.isEnabled) return;
|
|
165
176
|
|
|
166
177
|
const { status, parallel } = params;
|
|
@@ -185,7 +196,6 @@ class TestomatioPipe {
|
|
|
185
196
|
if (this.runPublicUrl) {
|
|
186
197
|
console.log(APP_PREFIX, '🌟 Public URL:', chalk.magenta(this.runPublicUrl));
|
|
187
198
|
}
|
|
188
|
-
|
|
189
199
|
}
|
|
190
200
|
if (this.runUrl && this.proceed) {
|
|
191
201
|
const notFinishedMessage = chalk.yellow.bold('Run was not finished because of $TESTOMATIO_PROCEED');
|
|
@@ -197,19 +207,30 @@ class TestomatioPipe {
|
|
|
197
207
|
// eslint-disable-next-line max-len
|
|
198
208
|
console.log(APP_PREFIX, chalk.yellow.bold('⚠️ Some reported tests were not found in Testomat.io project'));
|
|
199
209
|
// eslint-disable-next-line max-len
|
|
200
|
-
console.log(
|
|
210
|
+
console.log(
|
|
211
|
+
APP_PREFIX,
|
|
212
|
+
`If you use Testomat.io as a reporter only, please re-run tests using ${chalk.bold('TESTOMATIO_CREATE=1')}`,
|
|
213
|
+
);
|
|
201
214
|
// eslint-disable-next-line max-len
|
|
202
|
-
console.log(
|
|
215
|
+
console.log(
|
|
216
|
+
APP_PREFIX,
|
|
217
|
+
`But to keep your tests consistent it is recommended to ${chalk.bold('import tests first')}`,
|
|
218
|
+
);
|
|
203
219
|
console.log(APP_PREFIX, 'If tests were imported but still not matched, assign test IDs to your tests.');
|
|
204
220
|
console.log(APP_PREFIX, 'You can do that automatically via command line tools:');
|
|
205
221
|
console.log(APP_PREFIX, chalk.bold('npx check-tests ... --update-ids'), 'See: https://bit.ly/js-update-ids');
|
|
206
222
|
console.log(APP_PREFIX, 'or for Cucumber:');
|
|
207
223
|
// eslint-disable-next-line max-len
|
|
208
|
-
console.log(
|
|
224
|
+
console.log(
|
|
225
|
+
APP_PREFIX,
|
|
226
|
+
chalk.bold('npx check-cucumber ... --update-ids'),
|
|
227
|
+
'See: https://bit.ly/bdd-update-ids',
|
|
228
|
+
);
|
|
209
229
|
}
|
|
210
230
|
} catch (err) {
|
|
211
231
|
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
212
232
|
}
|
|
233
|
+
debug('Run finished');
|
|
213
234
|
}
|
|
214
235
|
|
|
215
236
|
toString() {
|
|
@@ -219,17 +240,16 @@ class TestomatioPipe {
|
|
|
219
240
|
|
|
220
241
|
module.exports = TestomatioPipe;
|
|
221
242
|
|
|
222
|
-
|
|
223
|
-
function setS3Credentials(artifacts) {
|
|
243
|
+
function setS3Credentials(artifacts) {
|
|
224
244
|
if (!Object.keys(artifacts).length) return;
|
|
225
245
|
|
|
226
246
|
console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
|
|
227
247
|
|
|
228
|
-
if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
|
|
229
|
-
if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
|
|
230
|
-
if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
|
|
231
|
-
if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
|
|
232
|
-
if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
233
|
-
if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
248
|
+
if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
|
|
249
|
+
if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
|
|
250
|
+
if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
|
|
251
|
+
if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
|
|
252
|
+
if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
253
|
+
if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
234
254
|
resetConfig();
|
|
235
255
|
}
|
package/lib/util.js
CHANGED
|
@@ -55,11 +55,21 @@ const isValidUrl = s => {
|
|
|
55
55
|
}
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
const fileMatchRegex = /file:(\/\/?[^:\s]+?\.(png|avi|webm|jpg|html|txt))/ig;
|
|
59
|
+
|
|
58
60
|
const fetchFilesFromStackTrace = (stack = '') => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
|
|
62
|
+
const files = Array.from(stack.matchAll(fileMatchRegex))
|
|
63
|
+
.map(f => f[1].trim())
|
|
64
|
+
.map(f => f.startsWith('//') ? f.substring(1) : f )
|
|
65
|
+
|
|
66
|
+
debug('Found files in stack trace: ', files);
|
|
67
|
+
|
|
68
|
+
return files.filter(f => {
|
|
69
|
+
const isFile = fs.existsSync(f);
|
|
70
|
+
if (!isFile) debug('File %s could not be found and uploaded as artifact', f);
|
|
71
|
+
return isFile;
|
|
72
|
+
});
|
|
63
73
|
};
|
|
64
74
|
|
|
65
75
|
const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
@@ -96,6 +106,29 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
96
106
|
.join('\n');
|
|
97
107
|
};
|
|
98
108
|
|
|
109
|
+
const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
110
|
+
|
|
111
|
+
const fetchIdFromCode = (code, opts = {}) => {
|
|
112
|
+
const comments = code.split('\n').map(l => l.trim()).filter(l => {
|
|
113
|
+
switch (opts.lang) {
|
|
114
|
+
case 'ruby':
|
|
115
|
+
case 'python':
|
|
116
|
+
return l.startsWith('# ');
|
|
117
|
+
default:
|
|
118
|
+
return l.startsWith('// ');
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return comments.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
const fetchIdFromOutput = (output) => {
|
|
127
|
+
const lines = output.split('\n').map(l => l.trim()).filter(l => l.startsWith('tid://'));
|
|
128
|
+
|
|
129
|
+
return lines.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
|
|
130
|
+
}
|
|
131
|
+
|
|
99
132
|
const fetchSourceCode = (contents, opts = {}) => {
|
|
100
133
|
if (!opts.title && !opts.line) return '';
|
|
101
134
|
|
|
@@ -252,6 +285,8 @@ module.exports = {
|
|
|
252
285
|
isSameTest,
|
|
253
286
|
fetchSourceCode,
|
|
254
287
|
fetchSourceCodeFromStackTrace,
|
|
288
|
+
fetchIdFromCode,
|
|
289
|
+
fetchIdFromOutput,
|
|
255
290
|
fetchFilesFromStackTrace,
|
|
256
291
|
fileSystem,
|
|
257
292
|
getCurrentDateTime,
|
package/lib/xmlReader.js
CHANGED
|
@@ -4,7 +4,12 @@ const chalk = require('chalk');
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const { XMLParser } = require("fast-xml-parser");
|
|
6
6
|
const { APP_PREFIX, STATUS } = require('./constants');
|
|
7
|
-
const { fetchFilesFromStackTrace,
|
|
7
|
+
const { fetchFilesFromStackTrace,
|
|
8
|
+
fetchIdFromOutput,
|
|
9
|
+
fetchSourceCode,
|
|
10
|
+
fetchSourceCodeFromStackTrace,
|
|
11
|
+
fetchIdFromCode
|
|
12
|
+
} = require('./util');
|
|
8
13
|
const upload = require('./fileUploader');
|
|
9
14
|
const pipesFactory = require('./pipe');
|
|
10
15
|
const adapterFactory = require('./junit-adapter');
|
|
@@ -25,7 +30,7 @@ const options = {
|
|
|
25
30
|
const reduceOptions = {};
|
|
26
31
|
|
|
27
32
|
class XmlReader {
|
|
28
|
-
|
|
33
|
+
|
|
29
34
|
constructor(opts = {}) {
|
|
30
35
|
this.requestParams = {
|
|
31
36
|
apiKey: opts.apiKey || TESTOMATIO,
|
|
@@ -39,8 +44,8 @@ class XmlReader {
|
|
|
39
44
|
if (!this.adapter) throw new Error('XML adapter for this format not found');
|
|
40
45
|
|
|
41
46
|
this.opts = opts || {};
|
|
42
|
-
|
|
43
|
-
this.pipes = pipesFactory(opts, store);
|
|
47
|
+
this.store = {}
|
|
48
|
+
this.pipes = pipesFactory(opts, this.store);
|
|
44
49
|
|
|
45
50
|
this.parser = new XMLParser(options);
|
|
46
51
|
this.tests = []
|
|
@@ -49,7 +54,7 @@ class XmlReader {
|
|
|
49
54
|
this.filesToUpload = {}
|
|
50
55
|
|
|
51
56
|
this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
|
|
52
|
-
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
57
|
+
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
connectAdapter() {
|
|
@@ -61,21 +66,21 @@ class XmlReader {
|
|
|
61
66
|
return this.adapter;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
parse(fileName) {
|
|
69
|
+
parse(fileName) {
|
|
65
70
|
const xmlData = fs.readFileSync(path.resolve(fileName));
|
|
66
71
|
const jsonResult = this.parser.parse(xmlData);
|
|
67
72
|
let jsonSuite;
|
|
68
|
-
|
|
73
|
+
|
|
69
74
|
if (jsonResult.testsuites) {
|
|
70
75
|
jsonSuite = jsonResult.testsuites;
|
|
71
76
|
} else if (jsonResult.testsuite) {
|
|
72
77
|
jsonSuite = jsonResult;
|
|
73
78
|
} else if (jsonResult.TestRun) {
|
|
74
|
-
return this.processTRX(jsonResult);
|
|
79
|
+
return this.processTRX(jsonResult);
|
|
75
80
|
} else if (jsonResult['test-run']) {
|
|
76
81
|
return this.processNUnit(jsonResult['test-run']);
|
|
77
82
|
} else if (jsonResult.assemblies) {
|
|
78
|
-
return this.processXUnit(jsonResult.assemblies);
|
|
83
|
+
return this.processXUnit(jsonResult.assemblies);
|
|
79
84
|
} else {
|
|
80
85
|
console.log(jsonResult)
|
|
81
86
|
throw new Error("Format can't be parsed")
|
|
@@ -89,9 +94,9 @@ class XmlReader {
|
|
|
89
94
|
|
|
90
95
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
91
96
|
const resultTests = processTestSuite(testsuite);
|
|
92
|
-
|
|
97
|
+
|
|
93
98
|
const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
|
|
94
|
-
const status = (
|
|
99
|
+
const status = (failures > 0 || errors > 0 || hasFailures) ? 'failed' : 'passed';
|
|
95
100
|
|
|
96
101
|
this.tests = this.tests.concat(resultTests);
|
|
97
102
|
|
|
@@ -124,7 +129,7 @@ class XmlReader {
|
|
|
124
129
|
skipped_count: parseInt(inconclusive + skipped, 10),
|
|
125
130
|
tests: resultTests,
|
|
126
131
|
};
|
|
127
|
-
}
|
|
132
|
+
}
|
|
128
133
|
|
|
129
134
|
processTRX(jsonSuite) {
|
|
130
135
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
@@ -133,10 +138,10 @@ class XmlReader {
|
|
|
133
138
|
const tests = defs.map(td => {
|
|
134
139
|
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
135
140
|
let example = td.name.match(/\((.*?)\)/);
|
|
136
|
-
if (example) example = { ...example[1].split(',')};
|
|
141
|
+
if (example) example = { ...example[1].split(',') };
|
|
137
142
|
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
138
143
|
const suite_title = suite.pop();
|
|
139
|
-
return {
|
|
144
|
+
return {
|
|
140
145
|
title,
|
|
141
146
|
example,
|
|
142
147
|
file: suite.join('/'),
|
|
@@ -149,18 +154,18 @@ class XmlReader {
|
|
|
149
154
|
let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
|
|
150
155
|
if (!Array.isArray(result)) result = [result].filter(d => !!d);
|
|
151
156
|
|
|
152
|
-
const results = result.map(td => ({
|
|
157
|
+
const results = result.map(td => ({
|
|
153
158
|
id: td.executionId,
|
|
154
159
|
// seconds are used in junit reports, but ms are used by testomatio
|
|
155
160
|
run_time: parseFloat(td.duration) * 1000,
|
|
156
|
-
status: td.outcome,
|
|
161
|
+
status: td.outcome,
|
|
157
162
|
stack: td.Output.StdOut,
|
|
158
163
|
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
|
|
159
164
|
}));
|
|
160
165
|
|
|
161
|
-
|
|
166
|
+
|
|
162
167
|
results.forEach(r => {
|
|
163
|
-
const test = tests.find(t => t.id === r.id) || {
|
|
168
|
+
const test = tests.find(t => t.id === r.id) || {};
|
|
164
169
|
r.suite_title = test.suite_title;
|
|
165
170
|
r.title = test.title?.trim();
|
|
166
171
|
if (test.code) r.code = test.code;
|
|
@@ -175,7 +180,7 @@ class XmlReader {
|
|
|
175
180
|
});
|
|
176
181
|
|
|
177
182
|
debug(results);
|
|
178
|
-
|
|
183
|
+
|
|
179
184
|
const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
|
|
180
185
|
|
|
181
186
|
const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
|
|
@@ -193,7 +198,7 @@ class XmlReader {
|
|
|
193
198
|
skipped_count: parseInt(counters.notExecuted, 10),
|
|
194
199
|
failed_count,
|
|
195
200
|
tests: results,
|
|
196
|
-
};
|
|
201
|
+
};
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
processXUnit(assemblies) {
|
|
@@ -227,7 +232,7 @@ class XmlReader {
|
|
|
227
232
|
let status = STATUS.PASSED;
|
|
228
233
|
if (result === 'Pass') status = STATUS.PASSED;
|
|
229
234
|
if (result === 'Fail') status = STATUS.FAILED;
|
|
230
|
-
if (result === 'Skip') status = STATUS.SKIPPED;
|
|
235
|
+
if (result === 'Skip') status = STATUS.SKIPPED;
|
|
231
236
|
|
|
232
237
|
const pathParts = type.split('.');
|
|
233
238
|
const suite_title = pathParts[pathParts.length - 1];
|
|
@@ -300,12 +305,19 @@ class XmlReader {
|
|
|
300
305
|
if (file.endsWith('.py')) this.stats.language = 'python';
|
|
301
306
|
if (file.endsWith('.java')) this.stats.language = 'java';
|
|
302
307
|
if (file.endsWith('.rb')) this.stats.language = 'ruby';
|
|
303
|
-
if (file.endsWith('.js')) this.stats.language
|
|
308
|
+
if (file.endsWith('.js')) this.stats.language = 'js';
|
|
304
309
|
if (file.endsWith('.ts')) this.stats.language = 'ts';
|
|
305
310
|
}
|
|
306
311
|
|
|
312
|
+
if (!fs.existsSync(file)) {
|
|
313
|
+
debug('Failed to open file with the source code', file)
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
307
316
|
const contents = fs.readFileSync(file).toString();
|
|
308
317
|
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language })
|
|
318
|
+
if (t.code) debug('Fetched code for test %s', t.title);
|
|
319
|
+
t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language })
|
|
320
|
+
if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
|
|
309
321
|
} catch (err) {
|
|
310
322
|
debug(err)
|
|
311
323
|
}
|
|
@@ -349,8 +361,12 @@ class XmlReader {
|
|
|
349
361
|
let files = [];
|
|
350
362
|
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f))
|
|
351
363
|
files = [...files, ...fetchFilesFromStackTrace(test.stack)];
|
|
352
|
-
|
|
353
|
-
|
|
364
|
+
|
|
365
|
+
if (!files.length) continue;
|
|
366
|
+
|
|
367
|
+
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
368
|
+
test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, runId)));
|
|
369
|
+
console.log(APP_PREFIX, `🗄️ Uploaded ${chalk.bold(`${files.length} artifacts`)} for test ${test.title}`);
|
|
354
370
|
}
|
|
355
371
|
}
|
|
356
372
|
|
|
@@ -360,7 +376,7 @@ class XmlReader {
|
|
|
360
376
|
title: this.requestParams.title,
|
|
361
377
|
env: this.requestParams.env,
|
|
362
378
|
group_title: this.requestParams.group_title,
|
|
363
|
-
};
|
|
379
|
+
};
|
|
364
380
|
|
|
365
381
|
debug("Run", runParams);
|
|
366
382
|
|
|
@@ -378,9 +394,9 @@ class XmlReader {
|
|
|
378
394
|
debug(
|
|
379
395
|
'Uploading data',
|
|
380
396
|
{
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
397
|
+
...this.stats,
|
|
398
|
+
tests: this.tests,
|
|
399
|
+
})
|
|
384
400
|
|
|
385
401
|
const dataString = {
|
|
386
402
|
...this.stats,
|
|
@@ -402,6 +418,8 @@ function reduceTestCases(prev, item) {
|
|
|
402
418
|
if (!Array.isArray(testCases)) {
|
|
403
419
|
testCases = [testCases]
|
|
404
420
|
}
|
|
421
|
+
const suiteOutput = item['system-out'] || item.output || item.log || '';
|
|
422
|
+
const suiteErr = item['system-err'] || item.output || item.log || '';
|
|
405
423
|
testCases.filter(t => !!t).forEach(testCaseItem => {
|
|
406
424
|
const file = testCaseItem.file || item.filepath || '';
|
|
407
425
|
|
|
@@ -417,8 +435,9 @@ function reduceTestCases(prev, item) {
|
|
|
417
435
|
if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
|
|
418
436
|
if (!message) message = stack.trim().split('\n')[0];
|
|
419
437
|
|
|
420
|
-
//
|
|
421
|
-
stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}`.trim()
|
|
438
|
+
// eslint-disable-next-line
|
|
439
|
+
stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim()
|
|
440
|
+
const testId = fetchIdFromOutput(stack);
|
|
422
441
|
|
|
423
442
|
let status = STATUS.PASSED.toString();
|
|
424
443
|
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
@@ -428,6 +447,7 @@ function reduceTestCases(prev, item) {
|
|
|
428
447
|
create: true,
|
|
429
448
|
file,
|
|
430
449
|
stack,
|
|
450
|
+
test_id: testId,
|
|
431
451
|
message,
|
|
432
452
|
line: testCaseItem.lineno,
|
|
433
453
|
// seconds are used in junit reports, but ms are used by testomatio
|