@testomatio/reporter 1.0.9-beta.1 → 1.0.9
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/startTest.js +4 -24
- package/lib/client.js +0 -51
- package/lib/junit-adapter/java.js +27 -9
- package/lib/pipe/csv.js +0 -2
- package/lib/pipe/github.js +15 -3
- package/lib/pipe/gitlab.js +15 -3
- package/lib/pipe/testomatio.js +22 -56
- package/lib/util.js +45 -9
- package/lib/xmlReader.js +84 -16
- package/package.json +1 -1
- package/lib/pipe/misc.js +0 -134
package/lib/bin/startTest.js
CHANGED
|
@@ -13,11 +13,9 @@ program
|
|
|
13
13
|
.option('--launch', 'Start a new run and return its ID')
|
|
14
14
|
.option('--finish', 'Finish Run by its ID')
|
|
15
15
|
.option("--env-file <envfile>", "Load environment variables from env file")
|
|
16
|
-
.
|
|
17
|
-
.action(async (opts) => {
|
|
18
|
-
const { launch, finish, filter } = opts;
|
|
19
|
-
let { command } = opts;
|
|
16
|
+
.action(opts => {
|
|
20
17
|
|
|
18
|
+
const { command, launch, finish } = opts;
|
|
21
19
|
if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
|
|
22
20
|
|
|
23
21
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || process.env.TESTOMATIO;
|
|
@@ -53,26 +51,6 @@ program
|
|
|
53
51
|
|
|
54
52
|
let exitCode = 0;
|
|
55
53
|
|
|
56
|
-
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
57
|
-
|
|
58
|
-
if(filter) {
|
|
59
|
-
const [pipe, opts] = filter.split(":");
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const tests = await client.prepareRun({pipe, opts});
|
|
63
|
-
|
|
64
|
-
if(!tests || tests.length === 0) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const grep = ` --grep (${tests.join('|')})`;
|
|
69
|
-
command += grep;
|
|
70
|
-
}
|
|
71
|
-
catch(err) {
|
|
72
|
-
console.log(APP_PREFIX, err);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
54
|
if (!command.split) {
|
|
77
55
|
process.exitCode = 255;
|
|
78
56
|
console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
|
|
@@ -95,6 +73,8 @@ program
|
|
|
95
73
|
return;
|
|
96
74
|
}
|
|
97
75
|
|
|
76
|
+
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
77
|
+
|
|
98
78
|
client.createRun().then(() => {
|
|
99
79
|
const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
|
|
100
80
|
|
package/lib/client.js
CHANGED
|
@@ -27,60 +27,9 @@ class Client {
|
|
|
27
27
|
this.queue = Promise.resolve();
|
|
28
28
|
this.totalUploaded = 0;
|
|
29
29
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
30
|
-
this.executionList = Promise.resolve();
|
|
31
|
-
|
|
32
30
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
/**
|
|
36
|
-
* Asynchronously prepares the execution list for running tests through various pipes.
|
|
37
|
-
* Each pipe in the client is checked for enablement,
|
|
38
|
-
* and if all pipes are disabled, the function returns a resolved Promise.
|
|
39
|
-
* Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
|
|
40
|
-
* The results are then filtered to remove any undefined values.
|
|
41
|
-
* If no valid results are found, the function returns undefined.
|
|
42
|
-
* Otherwise, it returns the first non-empty array from the filtered results.
|
|
43
|
-
*
|
|
44
|
-
* @param {Object} params - The options for preparing the test execution list.
|
|
45
|
-
* @param {string} params.pipe - Name of the executed pipe.
|
|
46
|
-
* @param {string} params.opts - Filter option.
|
|
47
|
-
* @returns {Promise<any>} - A Promise that resolves to an
|
|
48
|
-
* array containing the prepared execution list,
|
|
49
|
-
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
50
|
-
*/
|
|
51
|
-
async prepareRun(params) {
|
|
52
|
-
const { pipe, opts } = params;
|
|
53
|
-
// all pipes disabled, skipping
|
|
54
|
-
if (!this.pipes.some(p => p.isEnabled)) {
|
|
55
|
-
return Promise.resolve();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
if (pipe.toLowerCase() !== "testomatio") {
|
|
60
|
-
//TODO: for the future for the another pipes
|
|
61
|
-
console.warn(APP_PREFIX, `At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`)
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const results = await Promise.all(this.pipes.map(async p => {
|
|
66
|
-
return { pipe: p.toString(), result: await p.prepareRun(opts) };
|
|
67
|
-
}));
|
|
68
|
-
|
|
69
|
-
const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
|
|
70
|
-
|
|
71
|
-
if (!result || result.length === 0) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
debug('Execution tests list', result);
|
|
76
|
-
|
|
77
|
-
return result;
|
|
78
|
-
} catch (err) {
|
|
79
|
-
console.error(APP_PREFIX, err);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
33
|
/**
|
|
85
34
|
* Used to create a new Test run
|
|
86
35
|
*
|
|
@@ -10,24 +10,42 @@ class JavaAdapter extends Adapter {
|
|
|
10
10
|
|
|
11
11
|
formatTest(t) {
|
|
12
12
|
const fileParts = t.suite_title.split('.')
|
|
13
|
-
const example = t.title.match(/\[(.*)\]/)?.[1];
|
|
14
|
-
if (example) t.example = { "#": example }
|
|
15
13
|
|
|
16
14
|
t.file = namespaceToFileName(t.suite_title);
|
|
17
15
|
t.title = t.title.split('(')[0];
|
|
16
|
+
|
|
17
|
+
// detect params
|
|
18
|
+
const paramMatches = t.title.match(/\[(.*?)\]/g);
|
|
19
|
+
|
|
20
|
+
if (paramMatches) {
|
|
21
|
+
const params = paramMatches.map((_match, index) => `param${index + 1}`);
|
|
22
|
+
if (params.length === 1) params[0] = 'param';
|
|
23
|
+
let paramIndex = 0;
|
|
24
|
+
|
|
25
|
+
t.title = t.title.replace(/: \[(.*?)\]/g, () => {
|
|
26
|
+
if (params.length < 2) return `\${param}`
|
|
27
|
+
const paramName = params[paramIndex] || `param${paramIndex + 1}`;
|
|
28
|
+
paramIndex++;
|
|
29
|
+
return `\${${paramName}}`;
|
|
30
|
+
});
|
|
31
|
+
const example = {};
|
|
32
|
+
paramMatches.forEach((match, index) => { example[params[index]] = match.replace(/[[\]]/g, '') });
|
|
33
|
+
t.example = example;
|
|
34
|
+
}
|
|
35
|
+
|
|
18
36
|
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
|
|
19
37
|
return t;
|
|
20
38
|
}
|
|
21
39
|
|
|
22
|
-
formatStack(t) {
|
|
23
|
-
|
|
40
|
+
// formatStack(t) {
|
|
41
|
+
// const stack = super.formatStack(t);
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
// const file = t.suite_title.split('.');
|
|
26
44
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
45
|
+
// const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
|
|
46
|
+
// const regexp = new RegExp(fileLine,"g")
|
|
47
|
+
// return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
|
|
48
|
+
// }
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
function namespaceToFileName(fileName) {
|
package/lib/pipe/csv.js
CHANGED
package/lib/pipe/github.js
CHANGED
|
@@ -6,7 +6,6 @@ const merge = require('lodash.merge');
|
|
|
6
6
|
const { Octokit } = require('@octokit/rest');
|
|
7
7
|
const { APP_PREFIX } = require('../constants');
|
|
8
8
|
const { ansiRegExp, isSameTest } = require('../util');
|
|
9
|
-
const { statusEmoji, fullName } = require('./misc');
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* @typedef {import('../../types').Pipe} Pipe
|
|
@@ -38,8 +37,6 @@ class GitHubPipe {
|
|
|
38
37
|
debug('GitHub Pipe: Enabled');
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
async prepareRun(opts) {}
|
|
42
|
-
|
|
43
40
|
async createRun() {}
|
|
44
41
|
|
|
45
42
|
addTest(test) {
|
|
@@ -185,6 +182,21 @@ class GitHubPipe {
|
|
|
185
182
|
}
|
|
186
183
|
}
|
|
187
184
|
|
|
185
|
+
function statusEmoji(status) {
|
|
186
|
+
if (status === 'passed') return '🟢';
|
|
187
|
+
if (status === 'failed') return '🔴';
|
|
188
|
+
if (status === 'skipped') return '🟡';
|
|
189
|
+
return '';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function fullName(t) {
|
|
193
|
+
let line = '';
|
|
194
|
+
if (t.suite_title) line = `${t.suite_title}: `;
|
|
195
|
+
line += `**${t.title}**`;
|
|
196
|
+
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
197
|
+
return line;
|
|
198
|
+
}
|
|
199
|
+
|
|
188
200
|
async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
|
|
189
201
|
if (process.env.GH_KEEP_OUTDATED_REPORTS) return;
|
|
190
202
|
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -6,7 +6,6 @@ const merge = require('lodash.merge');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { APP_PREFIX } = require('../constants');
|
|
8
8
|
const { ansiRegExp, isSameTest } = require('../util');
|
|
9
|
-
const { statusEmoji, fullName } = require('./misc');
|
|
10
9
|
|
|
11
10
|
//! GITLAB_PAT environment variable is required for this functionality to work
|
|
12
11
|
//! and your pipeline trigger should be merge_request
|
|
@@ -47,8 +46,6 @@ class GitLabPipe {
|
|
|
47
46
|
debug('GitLab Pipe: Enabled');
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
async prepareRun(opts) {}
|
|
51
|
-
|
|
52
49
|
async createRun() {}
|
|
53
50
|
|
|
54
51
|
addTest(test) {
|
|
@@ -182,6 +179,21 @@ class GitLabPipe {
|
|
|
182
179
|
updateRun() {}
|
|
183
180
|
}
|
|
184
181
|
|
|
182
|
+
function statusEmoji(status) {
|
|
183
|
+
if (status === 'passed') return '🟢';
|
|
184
|
+
if (status === 'failed') return '🔴';
|
|
185
|
+
if (status === 'skipped') return '🟡';
|
|
186
|
+
return '';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function fullName(t) {
|
|
190
|
+
let line = '';
|
|
191
|
+
if (t.suite_title) line = `${t.suite_title}: `;
|
|
192
|
+
line += `**${t.title}**`;
|
|
193
|
+
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
194
|
+
return line;
|
|
195
|
+
}
|
|
196
|
+
|
|
185
197
|
async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
|
|
186
198
|
if (process.env.GITLAB_KEEP_OUTDATED_REPORTS) return;
|
|
187
199
|
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -3,8 +3,8 @@ const chalk = require('chalk');
|
|
|
3
3
|
const axios = require('axios');
|
|
4
4
|
const JsonCycle = require('json-cycle');
|
|
5
5
|
const { APP_PREFIX, STATUS } = require('../constants');
|
|
6
|
-
const { isValidUrl
|
|
7
|
-
const {
|
|
6
|
+
const { isValidUrl } = require('../util');
|
|
7
|
+
const { resetConfig } = require('../fileUploader');
|
|
8
8
|
|
|
9
9
|
const { TESTOMATIO_RUN } = process.env;
|
|
10
10
|
if (TESTOMATIO_RUN) {
|
|
@@ -33,12 +33,7 @@ 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
|
-
|
|
37
|
-
this.axios = axios.create({
|
|
38
|
-
baseURL: `${this.url.trim()}`,
|
|
39
|
-
timeout: 20000
|
|
40
|
-
});
|
|
41
|
-
|
|
36
|
+
this.axios = axios.create();
|
|
42
37
|
this.isEnabled = true;
|
|
43
38
|
// do not finish this run (for parallel testing)
|
|
44
39
|
this.proceed = process.env.TESTOMATIO_PROCEED;
|
|
@@ -52,50 +47,6 @@ class TestomatioPipe {
|
|
|
52
47
|
}
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
/**
|
|
56
|
-
* Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
|
|
57
|
-
* @param {Object} opts - The options for preparing the test grepList.
|
|
58
|
-
* @returns {Promise<string[]>} - An array containing the retrieved
|
|
59
|
-
* test grepList, or an empty array if no tests are found or the request is disabled.
|
|
60
|
-
* @throws {Error} - Throws an error if there was a problem while making the request.
|
|
61
|
-
*/
|
|
62
|
-
async prepareRun(opts) {
|
|
63
|
-
if (!this.isEnabled) return [];
|
|
64
|
-
|
|
65
|
-
const { type, id } = parseFilterParams(opts);
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
const q = generateFilterRequestParams({
|
|
69
|
-
type,
|
|
70
|
-
id,
|
|
71
|
-
apiKey: this.apiKey.trim()
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
if (!q) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const resp = await this.axios.get('/api/test_grep', q);
|
|
79
|
-
const { data } = resp;
|
|
80
|
-
|
|
81
|
-
if (Array.isArray(data?.tests) && data?.tests?.length > 0) {
|
|
82
|
-
foundedTestLog(APP_PREFIX, data.tests);
|
|
83
|
-
return data.tests;
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
console.error(
|
|
92
|
-
APP_PREFIX,
|
|
93
|
-
`🚩 Error getting Testomat.io test grepList: ${err}`,
|
|
94
|
-
);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
50
|
/**
|
|
100
51
|
* @returns Promise<void>
|
|
101
52
|
*/
|
|
@@ -133,13 +84,13 @@ class TestomatioPipe {
|
|
|
133
84
|
);
|
|
134
85
|
|
|
135
86
|
if (this.runId) {
|
|
136
|
-
const resp = await this.axios.put(
|
|
87
|
+
const resp = await this.axios.put(`${this.url.trim()}/api/reporter/${this.runId}`, runParams);
|
|
137
88
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
138
89
|
return;
|
|
139
90
|
}
|
|
140
91
|
|
|
141
92
|
try {
|
|
142
|
-
const resp = await this.axios.post(
|
|
93
|
+
const resp = await this.axios.post(`${this.url.trim()}/api/reporter`, runParams, {
|
|
143
94
|
maxContentLength: Infinity,
|
|
144
95
|
maxBodyLength: Infinity,
|
|
145
96
|
});
|
|
@@ -170,7 +121,7 @@ class TestomatioPipe {
|
|
|
170
121
|
data.create = this.createNewTests;
|
|
171
122
|
const json = JsonCycle.stringify(data);
|
|
172
123
|
|
|
173
|
-
return this.axios.post(
|
|
124
|
+
return this.axios.post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
|
|
174
125
|
maxContentLength: Infinity,
|
|
175
126
|
maxBodyLength: Infinity,
|
|
176
127
|
headers: {
|
|
@@ -218,7 +169,7 @@ class TestomatioPipe {
|
|
|
218
169
|
|
|
219
170
|
try {
|
|
220
171
|
if (this.runId && !this.proceed) {
|
|
221
|
-
await this.axios.put(
|
|
172
|
+
await this.axios.put(`${this.url}/api/reporter/${this.runId}`, {
|
|
222
173
|
api_key: this.apiKey,
|
|
223
174
|
status_event,
|
|
224
175
|
tests: params.tests,
|
|
@@ -258,3 +209,18 @@ class TestomatioPipe {
|
|
|
258
209
|
}
|
|
259
210
|
|
|
260
211
|
module.exports = TestomatioPipe;
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
function setS3Credentials(artifacts) {
|
|
215
|
+
if (!Object.keys(artifacts).length) return;
|
|
216
|
+
|
|
217
|
+
console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
|
|
218
|
+
|
|
219
|
+
if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
|
|
220
|
+
if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
|
|
221
|
+
if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
|
|
222
|
+
if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
|
|
223
|
+
if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
224
|
+
if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
225
|
+
resetConfig();
|
|
226
|
+
}
|
package/lib/util.js
CHANGED
|
@@ -192,15 +192,51 @@ const fileSystem = {
|
|
|
192
192
|
}
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
195
|
+
const humanize = (text) => {
|
|
196
|
+
text = decamelize(text);
|
|
197
|
+
return text.replace(/_./g, match => ` ${ match.charAt(1).toUpperCase()}`)
|
|
198
|
+
.trim()
|
|
199
|
+
.replace(/^(.)|\s(.)/g, ($1) => $1.toUpperCase()).trim()
|
|
200
|
+
.replace(/\sA\s/g, ' a ') // replace a|the
|
|
201
|
+
.replace(/\sThe\s/g, ' the ') // replace a|the
|
|
202
|
+
.replace(/^Test\s/, '')
|
|
203
|
+
.replace(/^Should\s/, '')
|
|
202
204
|
}
|
|
203
205
|
|
|
206
|
+
/**
|
|
207
|
+
* From https://github.com/sindresorhus/decamelize/blob/main/index.js
|
|
208
|
+
* @param {*} text
|
|
209
|
+
* @returns
|
|
210
|
+
*/
|
|
211
|
+
const decamelize = (text) => {
|
|
212
|
+
const separator = '_';
|
|
213
|
+
const replacement = `$1${separator}$2`;
|
|
214
|
+
|
|
215
|
+
// Split lowercase sequences followed by uppercase character.
|
|
216
|
+
// `dataForUSACounties` → `data_For_USACounties`
|
|
217
|
+
// `myURLstring → `my_URLstring`
|
|
218
|
+
let decamelized = text.replace(
|
|
219
|
+
/([\p{Lowercase_Letter}\d])(\p{Uppercase_Letter})/gu,
|
|
220
|
+
replacement,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
// Lowercase all single uppercase characters. As we
|
|
224
|
+
// want to preserve uppercase sequences, we cannot
|
|
225
|
+
// simply lowercase the separated string at the end.
|
|
226
|
+
// `data_For_USACounties` → `data_for_USACounties`
|
|
227
|
+
decamelized = decamelized.replace(
|
|
228
|
+
/((?<![\p{Uppercase_Letter}\d])[\p{Uppercase_Letter}\d](?![\p{Uppercase_Letter}\d]))/gu,
|
|
229
|
+
$0 => $0.toLowerCase(),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Remaining uppercase sequences will be separated from lowercase sequences.
|
|
233
|
+
// `data_For_USACounties` → `data_for_USA_counties`
|
|
234
|
+
return decamelized.replace(
|
|
235
|
+
/(\p{Uppercase_Letter}+)(\p{Uppercase_Letter}\p{Lowercase_Letter}+)/gu,
|
|
236
|
+
(_, $1, $2) => $1 + separator + $2.toLowerCase(),
|
|
237
|
+
);
|
|
238
|
+
};
|
|
239
|
+
|
|
204
240
|
module.exports = {
|
|
205
241
|
isSameTest,
|
|
206
242
|
fetchSourceCode,
|
|
@@ -213,5 +249,5 @@ module.exports = {
|
|
|
213
249
|
ansiRegExp,
|
|
214
250
|
parseTest,
|
|
215
251
|
parseSuite,
|
|
216
|
-
|
|
217
|
-
}
|
|
252
|
+
humanize,
|
|
253
|
+
};
|
package/lib/xmlReader.js
CHANGED
|
@@ -8,7 +8,7 @@ const { fetchFilesFromStackTrace, fetchSourceCode, fetchSourceCodeFromStackTrace
|
|
|
8
8
|
const upload = require('./fileUploader');
|
|
9
9
|
const pipesFactory = require('./pipe');
|
|
10
10
|
const adapterFactory = require('./junit-adapter');
|
|
11
|
-
|
|
11
|
+
const { humanize } = require('./util')
|
|
12
12
|
|
|
13
13
|
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || "https://app.testomat.io";
|
|
14
14
|
const TESTOMATIO = process.env.TESTOMATIO; // key?
|
|
@@ -35,7 +35,7 @@ class XmlReader {
|
|
|
35
35
|
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
36
36
|
};
|
|
37
37
|
this.runId = opts.runId || TESTOMATIO_RUN;
|
|
38
|
-
this.adapter = adapterFactory(
|
|
38
|
+
this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts)
|
|
39
39
|
if (!this.adapter) throw new Error('XML adapter for this format not found');
|
|
40
40
|
|
|
41
41
|
this.opts = opts || {};
|
|
@@ -53,8 +53,12 @@ class XmlReader {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
connectAdapter() {
|
|
56
|
-
if (this.opts.javaTests)
|
|
57
|
-
|
|
56
|
+
if (this.opts.javaTests) {
|
|
57
|
+
this.adapter = adapterFactory('java', this.opts);
|
|
58
|
+
return this.adapter;
|
|
59
|
+
}
|
|
60
|
+
this.adapter = adapterFactory(this.stats.language, this.opts);
|
|
61
|
+
return this.adapter;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
parse(fileName) {
|
|
@@ -70,6 +74,8 @@ class XmlReader {
|
|
|
70
74
|
return this.processTRX(jsonResult);
|
|
71
75
|
} else if (jsonResult['test-run']) {
|
|
72
76
|
return this.processNUnit(jsonResult['test-run']);
|
|
77
|
+
} else if (jsonResult.assemblies) {
|
|
78
|
+
return this.processXUnit(jsonResult.assemblies);
|
|
73
79
|
} else {
|
|
74
80
|
console.log(jsonResult)
|
|
75
81
|
throw new Error("Format can't be parsed")
|
|
@@ -190,6 +196,79 @@ class XmlReader {
|
|
|
190
196
|
};
|
|
191
197
|
}
|
|
192
198
|
|
|
199
|
+
processXUnit(assemblies) {
|
|
200
|
+
const tests = [];
|
|
201
|
+
|
|
202
|
+
assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
|
|
203
|
+
|
|
204
|
+
assemblies.forEach(assembly => {
|
|
205
|
+
const { collection } = assembly;
|
|
206
|
+
|
|
207
|
+
const suites = Array.isArray(collection) ? collection : [collection];
|
|
208
|
+
|
|
209
|
+
suites.forEach(suite => {
|
|
210
|
+
const { test } = suite;
|
|
211
|
+
if (!test) return;
|
|
212
|
+
const cases = Array.isArray(test) ? test : [test];
|
|
213
|
+
cases.forEach(testCase => {
|
|
214
|
+
const { type, time, result } = testCase;
|
|
215
|
+
|
|
216
|
+
let message = '';
|
|
217
|
+
let stack = '';
|
|
218
|
+
|
|
219
|
+
if (testCase.failure) {
|
|
220
|
+
message = testCase.failure.message;
|
|
221
|
+
stack = testCase.failure['stack-trace']
|
|
222
|
+
}
|
|
223
|
+
if (testCase.reason) {
|
|
224
|
+
message = testCase.reason.message;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let status = STATUS.PASSED;
|
|
228
|
+
if (result === 'Pass') status = STATUS.PASSED;
|
|
229
|
+
if (result === 'Fail') status = STATUS.FAILED;
|
|
230
|
+
if (result === 'Skip') status = STATUS.SKIPPED;
|
|
231
|
+
|
|
232
|
+
const pathParts = type.split('.');
|
|
233
|
+
const suite_title = pathParts[pathParts.length - 1];
|
|
234
|
+
const file = pathParts.slice(0, -1).join('/');
|
|
235
|
+
const title = testCase.method || testCase.name.split('.').pop();
|
|
236
|
+
const run_time = parseFloat(time) * 1000;
|
|
237
|
+
|
|
238
|
+
tests.push({
|
|
239
|
+
create: true,
|
|
240
|
+
stack,
|
|
241
|
+
message,
|
|
242
|
+
file,
|
|
243
|
+
status,
|
|
244
|
+
title,
|
|
245
|
+
suite_title,
|
|
246
|
+
run_time,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const hasFailures = tests.filter(t => t.status === STATUS.FAILED).length > 0;
|
|
254
|
+
const status = hasFailures ? STATUS.FAILED : STATUS.PASSED;
|
|
255
|
+
|
|
256
|
+
this.tests = tests;
|
|
257
|
+
|
|
258
|
+
debug(tests);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
status,
|
|
262
|
+
create_tests: true,
|
|
263
|
+
name: 'xUnit',
|
|
264
|
+
tests_count: tests.length,
|
|
265
|
+
passed_count: tests.filter(t => t.status === STATUS.PASSED).length,
|
|
266
|
+
failed_count: tests.filter(t => t.status === STATUS.FAILED).length,
|
|
267
|
+
skipped_count: tests.filter(t => t.status === STATUS.SKIPPED).length,
|
|
268
|
+
tests,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
193
272
|
calculateStats() {
|
|
194
273
|
this.stats = {
|
|
195
274
|
...this.stats,
|
|
@@ -241,18 +320,7 @@ class XmlReader {
|
|
|
241
320
|
|
|
242
321
|
this.adapter.formatTest(t)
|
|
243
322
|
|
|
244
|
-
t.title = t.title
|
|
245
|
-
// insert a space before all caps
|
|
246
|
-
.replace(/([A-Z])/g, ' $1')
|
|
247
|
-
// _ chars to spaces
|
|
248
|
-
.replace(/_/g, ' ')
|
|
249
|
-
// uppercase the first character
|
|
250
|
-
.replace(/^(.)|\s(.)/g, $1 => $1.toLowerCase())
|
|
251
|
-
|
|
252
|
-
// remove standard prefixes
|
|
253
|
-
.replace(/^test\s/, '')
|
|
254
|
-
.replace(/^should\s/, '')
|
|
255
|
-
.trim();
|
|
323
|
+
t.title = humanize(t.title);
|
|
256
324
|
});
|
|
257
325
|
}
|
|
258
326
|
|
package/package.json
CHANGED
package/lib/pipe/misc.js
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
const { resetConfig } = require('../fileUploader');
|
|
2
|
-
const { APP_PREFIX } = require('../constants');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Set S3 credentials from the provided artifacts object.
|
|
6
|
-
* @param {Object} artifacts - The artifacts object containing S3 credentials.
|
|
7
|
-
*/
|
|
8
|
-
function setS3Credentials(artifacts) {
|
|
9
|
-
if (!Object.keys(artifacts).length) return;
|
|
10
|
-
|
|
11
|
-
console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
|
|
12
|
-
|
|
13
|
-
if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
|
|
14
|
-
if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
|
|
15
|
-
if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
|
|
16
|
-
if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
|
|
17
|
-
if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
18
|
-
if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
19
|
-
resetConfig();
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Generates mode request parameters based on the input params.
|
|
23
|
-
* @param {Object} params - The input parameters for the request.
|
|
24
|
-
* @param {string} params.type - The type of the request (e.g., "tag").
|
|
25
|
-
* @param {string} params.id - The ID associated with the request.
|
|
26
|
-
* @param {string} params.apiKey - The API key for authentication.
|
|
27
|
-
* @returns {Object|null} - An object containing the generated request parameters, or null if the type is invalid.
|
|
28
|
-
*/
|
|
29
|
-
function generateFilterRequestParams(params) {
|
|
30
|
-
const { type, id, apiKey } = params;
|
|
31
|
-
|
|
32
|
-
if (!type) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!id) {
|
|
37
|
-
console.error(APP_PREFIX, `Please make sure your settings "${type.toUpperCase()}"= "${id}" is correct!`);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
params: {
|
|
43
|
-
type,
|
|
44
|
-
id,
|
|
45
|
-
api_key: apiKey
|
|
46
|
-
},
|
|
47
|
-
responseType: "json"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Parse filter parameters from a string in the format "type=id".
|
|
53
|
-
* @param {string} opts - The input string containing the filter parameters.
|
|
54
|
-
* @returns {Object} An object containing the parsed filter parameters.
|
|
55
|
-
* The object has properties "type" and "id".
|
|
56
|
-
*/
|
|
57
|
-
function parseFilterParams(opts) {
|
|
58
|
-
const [type, id] = opts.split("=");
|
|
59
|
-
const validType = updateFilterType(type);
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
type: validType,
|
|
63
|
-
id
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Update and validate the filter type.
|
|
69
|
-
* @param {string} type - The original filter type.
|
|
70
|
-
* @returns {string|undefined} The updated and validated filter type.
|
|
71
|
-
* Returns undefined if the type is not valid.
|
|
72
|
-
*/
|
|
73
|
-
function updateFilterType(type) {
|
|
74
|
-
const typeLowerCase = type.toLowerCase();
|
|
75
|
-
|
|
76
|
-
const filterTypes = [
|
|
77
|
-
"tag-name",
|
|
78
|
-
"plan-id",
|
|
79
|
-
"label-name"
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
const filterApi = [
|
|
83
|
-
"tag",
|
|
84
|
-
"plan",
|
|
85
|
-
"label",
|
|
86
|
-
// "issue", //TODO: WIP
|
|
87
|
-
// "jira" //TODO: WIP
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
if (!filterTypes.includes(typeLowerCase)) {
|
|
91
|
-
console.log(APP_PREFIX, `❗❗❗ Invalid "filter=${type}" start settings! Available option list: ${filterTypes}`);
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const index = filterTypes.indexOf(typeLowerCase);
|
|
96
|
-
|
|
97
|
-
return (index !== -1)
|
|
98
|
-
? filterApi[index]
|
|
99
|
-
: undefined
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Return an emoji based on the provided status.
|
|
104
|
-
* @param {string} status - The status value ('passed', 'failed', or 'skipped').
|
|
105
|
-
* @returns {string} - An emoji corresponding to the provided status.
|
|
106
|
-
*/
|
|
107
|
-
function statusEmoji(status) {
|
|
108
|
-
if (status === 'passed') return '🟢';
|
|
109
|
-
if (status === 'failed') return '🔴';
|
|
110
|
-
if (status === 'skipped') return '🟡';
|
|
111
|
-
return '';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Generate a full name string based on the provided test object.
|
|
116
|
-
* @param {object} t - The test object.
|
|
117
|
-
* @returns {string} - A formatted full name string for the test object.
|
|
118
|
-
*/
|
|
119
|
-
function fullName(t) {
|
|
120
|
-
let line = '';
|
|
121
|
-
if (t.suite_title) line = `${t.suite_title}: `;
|
|
122
|
-
line += `**${t.title}**`;
|
|
123
|
-
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
124
|
-
return line;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
module.exports = {
|
|
128
|
-
updateFilterType,
|
|
129
|
-
parseFilterParams,
|
|
130
|
-
generateFilterRequestParams,
|
|
131
|
-
setS3Credentials,
|
|
132
|
-
statusEmoji,
|
|
133
|
-
fullName
|
|
134
|
-
};
|