@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
package/lib/bin/startTest.js
CHANGED
|
@@ -13,9 +13,11 @@ 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
|
-
.
|
|
16
|
+
.option("--filter <filter>", "Additional execution filter")
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
const { launch, finish, filter } = opts;
|
|
19
|
+
let { command } = opts;
|
|
17
20
|
|
|
18
|
-
const { command, launch, finish } = opts;
|
|
19
21
|
if (opts.envFile) require('dotenv').config(opts.envFile); // eslint-disable-line
|
|
20
22
|
|
|
21
23
|
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || process.env.TESTOMATIO;
|
|
@@ -51,6 +53,27 @@ program
|
|
|
51
53
|
|
|
52
54
|
let exitCode = 0;
|
|
53
55
|
|
|
56
|
+
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
57
|
+
|
|
58
|
+
if(filter) {
|
|
59
|
+
const [pipe, ...optsArray] = filter.split(":");
|
|
60
|
+
const pipeOptions = optsArray.join(":");
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const tests = await client.prepareRun({pipe, pipeOptions});
|
|
64
|
+
|
|
65
|
+
if(!tests || tests.length === 0) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const grep = ` --grep (${tests.join('|')})`;
|
|
70
|
+
command += grep;
|
|
71
|
+
}
|
|
72
|
+
catch(err) {
|
|
73
|
+
console.log(APP_PREFIX, err);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
if (!command.split) {
|
|
55
78
|
process.exitCode = 255;
|
|
56
79
|
console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
|
|
@@ -73,8 +96,6 @@ program
|
|
|
73
96
|
return;
|
|
74
97
|
}
|
|
75
98
|
|
|
76
|
-
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
77
|
-
|
|
78
99
|
client.createRun().then(() => {
|
|
79
100
|
const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
|
|
80
101
|
|
package/lib/client.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
const debug = require('debug')('@testomatio/reporter:client');
|
|
2
2
|
const createCallsiteRecord = require('callsite-record');
|
|
3
3
|
const { sep, join } = require('path');
|
|
4
|
+
const { minimatch } = require('minimatch')
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const chalk = require('chalk');
|
|
6
7
|
const { randomUUID } = require('crypto');
|
|
7
8
|
const upload = require('./fileUploader');
|
|
8
9
|
const { APP_PREFIX } = require('./constants');
|
|
9
10
|
const pipesFactory = require('./pipe');
|
|
10
|
-
const
|
|
11
|
+
const artifactStorage = require('./storages/artifact-storage');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @typedef {import('../types').TestData} TestData
|
|
14
15
|
* @typedef {import('../types').RunStatus} RunStatus
|
|
15
|
-
* @typedef {import('../types').PipeResult} PipeResult
|
|
16
|
+
* @typedef {import('../types').PipeResult} PipeResult
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
class Client {
|
|
@@ -22,39 +23,85 @@ class Client {
|
|
|
22
23
|
* @param {*} params
|
|
23
24
|
*/
|
|
24
25
|
constructor(params = {}) {
|
|
25
|
-
this.parallel = params.parallel;
|
|
26
26
|
const store = {};
|
|
27
27
|
this.uuid = randomUUID();
|
|
28
28
|
this.pipes = pipesFactory(params, store);
|
|
29
29
|
this.queue = Promise.resolve();
|
|
30
30
|
this.totalUploaded = 0;
|
|
31
31
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
32
|
+
this.executionList = Promise.resolve();
|
|
33
|
+
|
|
32
34
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Asynchronously prepares the execution list for running tests through various pipes.
|
|
39
|
+
* Each pipe in the client is checked for enablement,
|
|
40
|
+
* and if all pipes are disabled, the function returns a resolved Promise.
|
|
41
|
+
* Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
|
|
42
|
+
* The results are then filtered to remove any undefined values.
|
|
43
|
+
* If no valid results are found, the function returns undefined.
|
|
44
|
+
* Otherwise, it returns the first non-empty array from the filtered results.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} params - The options for preparing the test execution list.
|
|
47
|
+
* @param {string} params.pipe - Name of the executed pipe.
|
|
48
|
+
* @param {string} params.pipeOptions - Filter option.
|
|
49
|
+
* @returns {Promise<any>} - A Promise that resolves to an
|
|
50
|
+
* array containing the prepared execution list,
|
|
51
|
+
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
52
|
+
*/
|
|
53
|
+
async prepareRun(params) {
|
|
54
|
+
const { pipe, pipeOptions } = params;
|
|
55
|
+
// all pipes disabled, skipping
|
|
56
|
+
if (!this.pipes.some(p => p.isEnabled)) {
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
|
|
62
|
+
|
|
63
|
+
if (!filterPipe.isEnabled) {
|
|
64
|
+
// TODO:for the future for the another pipes
|
|
65
|
+
console.warn(
|
|
66
|
+
APP_PREFIX,
|
|
67
|
+
`At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`
|
|
68
|
+
);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const results = await Promise.all(this.pipes.map(async p =>
|
|
73
|
+
({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })
|
|
74
|
+
));
|
|
75
|
+
|
|
76
|
+
const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
|
|
77
|
+
|
|
78
|
+
if (!result || result.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
debug('Execution tests list', result);
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(APP_PREFIX, err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
35
90
|
/**
|
|
36
91
|
* Used to create a new Test run
|
|
37
92
|
*
|
|
38
93
|
* @returns {Promise<any>} - resolves to Run id which should be used to update / add test
|
|
39
94
|
*/
|
|
40
95
|
createRun() {
|
|
96
|
+
debug('Creating run...');
|
|
41
97
|
// all pipes disabled, skipping
|
|
42
|
-
if (!this.pipes
|
|
43
|
-
|
|
44
|
-
const runParams = {
|
|
45
|
-
title: this.title,
|
|
46
|
-
parallel: this.parallel,
|
|
47
|
-
env: this.env,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
global.testomatioArtifacts = [];
|
|
51
|
-
// TODO: create global storage
|
|
98
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
52
99
|
|
|
53
100
|
this.queue = this.queue
|
|
54
|
-
.then(() => Promise.all(this.pipes.map(p => p.createRun(
|
|
101
|
+
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
55
102
|
.catch(err => console.log(APP_PREFIX, err))
|
|
56
103
|
.then(() => undefined); // fixes return type
|
|
57
|
-
debug('Run', this.queue);
|
|
104
|
+
// debug('Run', this.queue);
|
|
58
105
|
return this.queue;
|
|
59
106
|
}
|
|
60
107
|
|
|
@@ -63,12 +110,11 @@ class Client {
|
|
|
63
110
|
*
|
|
64
111
|
* @param {string|undefined} status
|
|
65
112
|
* @param {TestData} [testData]
|
|
66
|
-
* @param {string[]} [storeArtifacts]
|
|
67
113
|
* @returns {Promise<PipeResult[]>}
|
|
68
114
|
*/
|
|
69
|
-
async addTestRun(status, testData
|
|
115
|
+
async addTestRun(status, testData) {
|
|
70
116
|
// all pipes disabled, skipping
|
|
71
|
-
if (!this.pipes
|
|
117
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
72
118
|
|
|
73
119
|
if (!testData)
|
|
74
120
|
testData = {
|
|
@@ -103,20 +149,23 @@ class Client {
|
|
|
103
149
|
stack = this.formatSteps(stack, steps);
|
|
104
150
|
}
|
|
105
151
|
|
|
152
|
+
stack += testData.stack || '';
|
|
153
|
+
|
|
154
|
+
// ATTACH LOGS from storage
|
|
155
|
+
// in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
|
|
156
|
+
const logger = require('./storages/logger');
|
|
106
157
|
const testLogs = logger.getLogs(test_id);
|
|
107
|
-
debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
108
|
-
stack +=
|
|
158
|
+
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
159
|
+
if (stack) stack += '\n\n';
|
|
160
|
+
stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
|
|
109
161
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
162
|
+
// GET ARTIFACTS from storage
|
|
163
|
+
const artifactFiles = artifactStorage.get(test_id);
|
|
164
|
+
if (artifactFiles) files.push(...artifactFiles);
|
|
114
165
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
global.testomatioArtifacts = [];
|
|
119
|
-
}
|
|
166
|
+
// GET KEY-VALUEs from storage
|
|
167
|
+
const keyValueStorage = require('./storages/key-value-storage');
|
|
168
|
+
const keyValues = keyValueStorage.get(test_id);
|
|
120
169
|
|
|
121
170
|
for (const file of files) {
|
|
122
171
|
uploadedFiles.push(upload.uploadFileByPath(file, this.uuid));
|
|
@@ -129,7 +178,7 @@ class Client {
|
|
|
129
178
|
|
|
130
179
|
const artifacts = await Promise.all(uploadedFiles);
|
|
131
180
|
|
|
132
|
-
global.testomatioArtifacts = [];
|
|
181
|
+
// global.testomatioArtifacts = [];
|
|
133
182
|
|
|
134
183
|
this.totalUploaded += uploadedFiles.filter(n => n).length;
|
|
135
184
|
|
|
@@ -147,19 +196,22 @@ class Client {
|
|
|
147
196
|
message,
|
|
148
197
|
run_time: parseFloat(time),
|
|
149
198
|
artifacts,
|
|
199
|
+
meta: keyValues,
|
|
150
200
|
};
|
|
151
201
|
|
|
152
|
-
this.queue = this.queue
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
202
|
+
this.queue = this.queue.then(() =>
|
|
203
|
+
Promise.all(
|
|
204
|
+
this.pipes.map(async p => {
|
|
205
|
+
try {
|
|
206
|
+
const result = await p.addTest(data);
|
|
207
|
+
return { pipe: p.toString(), result };
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.log(APP_PREFIX, p.toString(), err);
|
|
210
|
+
}
|
|
211
|
+
}),
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
|
|
163
215
|
return this.queue;
|
|
164
216
|
}
|
|
165
217
|
|
|
@@ -171,22 +223,25 @@ class Client {
|
|
|
171
223
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
172
224
|
*/
|
|
173
225
|
updateRunStatus(status, isParallel = false) {
|
|
226
|
+
debug('Updating run status...');
|
|
174
227
|
// all pipes disabled, skipping
|
|
175
|
-
if (!this.pipes
|
|
228
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
176
229
|
|
|
177
230
|
const runParams = { status, parallel: isParallel };
|
|
178
231
|
|
|
179
232
|
this.queue = this.queue
|
|
180
233
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
181
234
|
.then(() => {
|
|
182
|
-
debug('TOTAL
|
|
235
|
+
debug('TOTAL artifacts', this.totalUploaded);
|
|
236
|
+
if (this.totalUploaded && !upload.isArtifactsEnabled())
|
|
237
|
+
debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
|
|
183
238
|
|
|
184
|
-
if (
|
|
239
|
+
if (this.totalUploaded && upload.isArtifactsEnabled()) {
|
|
185
240
|
console.log(
|
|
186
241
|
APP_PREFIX,
|
|
187
|
-
`🗄️
|
|
242
|
+
`🗄️ ${this.totalUploaded} artifacts ${
|
|
188
243
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
189
|
-
} uploaded to S3 bucket
|
|
244
|
+
} uploaded to S3 bucket`,
|
|
190
245
|
);
|
|
191
246
|
}
|
|
192
247
|
})
|
|
@@ -213,17 +268,21 @@ class Client {
|
|
|
213
268
|
stack += '\n\n';
|
|
214
269
|
}
|
|
215
270
|
|
|
271
|
+
const customFilter = process.env.TESTOMATIO_STACK_IGNORE;
|
|
272
|
+
|
|
216
273
|
try {
|
|
274
|
+
let hasFrame = false;
|
|
217
275
|
const record = createCallsiteRecord({
|
|
218
276
|
forError: error,
|
|
277
|
+
isCallsiteFrame: frame => {
|
|
278
|
+
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
279
|
+
if (hasFrame) return false;
|
|
280
|
+
if (isNotInternalFrame(frame)) hasFrame = true;
|
|
281
|
+
return hasFrame;
|
|
282
|
+
}
|
|
219
283
|
});
|
|
220
284
|
if (record && !record.filename.startsWith('http')) {
|
|
221
|
-
stack += record.renderSync({
|
|
222
|
-
stackFilter: frame =>
|
|
223
|
-
frame.getFileName().indexOf(sep) > -1 &&
|
|
224
|
-
frame.getFileName().indexOf('node_modules') < 0 &&
|
|
225
|
-
frame.getFileName().indexOf('internal') < 0,
|
|
226
|
-
});
|
|
285
|
+
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
227
286
|
}
|
|
228
287
|
return stack;
|
|
229
288
|
} catch (e) {
|
|
@@ -232,4 +291,10 @@ class Client {
|
|
|
232
291
|
}
|
|
233
292
|
}
|
|
234
293
|
|
|
294
|
+
function isNotInternalFrame(frame) {
|
|
295
|
+
return frame.getFileName().includes(sep) &&
|
|
296
|
+
!frame.getFileName().includes('node_modules') &&
|
|
297
|
+
!frame.getFileName().includes('internal')
|
|
298
|
+
}
|
|
299
|
+
|
|
235
300
|
module.exports = Client;
|
package/lib/constants.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
2
4
|
|
|
3
5
|
const APP_PREFIX = chalk.gray('[TESTOMATIO]');
|
|
4
|
-
const TESTOMAT_ARTIFACT_SUFFIX = "testomatio_artifact_";
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
-
mainDir: "testomatio_tmp",
|
|
8
|
-
}
|
|
7
|
+
const TESTOMAT_TMP_STORAGE_DIR = path.join(os.tmpdir(), 'testomatio_tmp');
|
|
9
8
|
|
|
10
9
|
const CSV_HEADERS = [
|
|
11
10
|
{ id: 'suite_title', title: 'Suite_title' },
|
|
@@ -24,8 +23,7 @@ const STATUS = {
|
|
|
24
23
|
|
|
25
24
|
module.exports = {
|
|
26
25
|
APP_PREFIX,
|
|
27
|
-
|
|
28
|
-
TESTOMAT_TMP_STORAGE,
|
|
26
|
+
TESTOMAT_TMP_STORAGE_DIR,
|
|
29
27
|
CSV_HEADERS,
|
|
30
28
|
STATUS,
|
|
31
29
|
}
|
|
@@ -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
|
@@ -4,7 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const csvWriter = require('csv-writer');
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
const merge = require('lodash.merge');
|
|
7
|
-
const { isSameTest, getCurrentDateTime } = require('../
|
|
7
|
+
const { isSameTest, getCurrentDateTime } = require('../utils/utils');
|
|
8
8
|
const { CSV_HEADERS } = require('../constants');
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -40,6 +40,9 @@ class CsvPipe {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
44
|
+
async prepareRun() {}
|
|
45
|
+
|
|
43
46
|
async createRun() {
|
|
44
47
|
// empty
|
|
45
48
|
}
|
package/lib/pipe/github.js
CHANGED
|
@@ -5,7 +5,8 @@ const humanizeDuration = require('humanize-duration');
|
|
|
5
5
|
const merge = require('lodash.merge');
|
|
6
6
|
const { Octokit } = require('@octokit/rest');
|
|
7
7
|
const { APP_PREFIX } = require('../constants');
|
|
8
|
-
const { ansiRegExp, isSameTest } = require('../
|
|
8
|
+
const { ansiRegExp, isSameTest } = require('../utils/utils');
|
|
9
|
+
const { statusEmoji, fullName } = require('../utils/pipe_utils');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @typedef {import('../../types').Pipe} Pipe
|
|
@@ -21,7 +22,8 @@ class GitHubPipe {
|
|
|
21
22
|
this.token = params.GH_PAT || process.env.GH_PAT;
|
|
22
23
|
this.ref = process.env.GITHUB_REF;
|
|
23
24
|
this.repo = process.env.GITHUB_REPOSITORY;
|
|
24
|
-
this.
|
|
25
|
+
this.jobKey = `${process.env.GITHUB_WORKFLOW || ''} / ${process.env.GITHUB_JOB || ''}`;
|
|
26
|
+
this.hiddenCommentData = `<!--- testomat.io report ${this.jobKey} -->`;
|
|
25
27
|
|
|
26
28
|
debug('GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', 'Ref:', this.ref, 'Repo:', this.repo);
|
|
27
29
|
|
|
@@ -36,6 +38,9 @@ class GitHubPipe {
|
|
|
36
38
|
debug('GitHub Pipe: Enabled');
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
42
|
+
async prepareRun() {}
|
|
43
|
+
|
|
39
44
|
async createRun() {}
|
|
40
45
|
|
|
41
46
|
addTest(test) {
|
|
@@ -73,35 +78,32 @@ class GitHubPipe {
|
|
|
73
78
|
let summary = `${this.hiddenCommentData}
|
|
74
79
|
|
|
75
80
|
| [](https://testomat.io) | ${
|
|
76
|
-
statusEmoji(runParams.status,)} ${
|
|
81
|
+
statusEmoji(runParams.status,)} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()} |
|
|
77
82
|
| --- | --- |
|
|
78
83
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
79
|
-
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
84
|
+
| Summary | ${failedCount ? `${statusEmoji('failed')} **${failedCount}** failed; ` : ''} ${statusEmoji(
|
|
80
85
|
'passed',
|
|
81
86
|
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
82
|
-
| Duration | 🕐 **${humanizeDuration(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
| Duration | 🕐 **${humanizeDuration(
|
|
88
|
+
parseInt(
|
|
89
|
+
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
|
90
|
+
10,
|
|
91
|
+
),
|
|
92
|
+
{
|
|
93
|
+
maxDecimalPoints: 0,
|
|
94
|
+
},
|
|
95
|
+
)}** |`;
|
|
96
|
+
|
|
86
97
|
if (this.store.runUrl) {
|
|
87
|
-
summary +=
|
|
98
|
+
summary += `\n| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
|
|
88
99
|
}
|
|
89
100
|
if (process.env.GITHUB_WORKFLOW) {
|
|
90
|
-
summary +=
|
|
101
|
+
summary += `\n| Job | 🗂️ [${this.jobKey}](${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
|
|
102
|
+
this.repo
|
|
103
|
+
}/actions/runs/${process.env.GITHUB_RUN_ID}) | `;
|
|
91
104
|
}
|
|
92
105
|
if (process.env.RUNNER_OS) {
|
|
93
|
-
summary +=
|
|
94
|
-
}
|
|
95
|
-
if (process.env.GITHUB_HEAD_REF) {
|
|
96
|
-
summary += `| Branch | 🌳 \`${process.env.GITHUB_HEAD_REF}\` | `;
|
|
97
|
-
}
|
|
98
|
-
if (process.env.GITHUB_RUN_ATTEMPT) {
|
|
99
|
-
summary += `| Run Attempt | 🌒 \`${process.env.GITHUB_RUN_ATTEMPT}\` | `;
|
|
100
|
-
}
|
|
101
|
-
if (process.env.GITHUB_RUN_ID) {
|
|
102
|
-
summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
|
|
103
|
-
this.repo
|
|
104
|
-
}/actions/runs/${process.env.GITHUB_RUN_ID} | `;
|
|
106
|
+
summary += `\n| Operating System | 🖥️ \`${process.env.RUNNER_OS}\` ${process.env.RUNNER_ARCH || ''} | `;
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
const failures = this.tests
|
|
@@ -184,21 +186,6 @@ class GitHubPipe {
|
|
|
184
186
|
}
|
|
185
187
|
}
|
|
186
188
|
|
|
187
|
-
function statusEmoji(status) {
|
|
188
|
-
if (status === 'passed') return '🟢';
|
|
189
|
-
if (status === 'failed') return '🔴';
|
|
190
|
-
if (status === 'skipped') return '🟡';
|
|
191
|
-
return '';
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function fullName(t) {
|
|
195
|
-
let line = '';
|
|
196
|
-
if (t.suite_title) line = `${t.suite_title}: `;
|
|
197
|
-
line += `**${t.title}**`;
|
|
198
|
-
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
199
|
-
return line;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
189
|
async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
|
|
203
190
|
if (process.env.GH_KEEP_OUTDATED_REPORTS) return;
|
|
204
191
|
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -5,7 +5,8 @@ const humanizeDuration = require('humanize-duration');
|
|
|
5
5
|
const merge = require('lodash.merge');
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { APP_PREFIX } = require('../constants');
|
|
8
|
-
const { ansiRegExp, isSameTest } = require('../
|
|
8
|
+
const { ansiRegExp, isSameTest } = require('../utils/utils');
|
|
9
|
+
const { statusEmoji, fullName } = require('../utils/pipe_utils');
|
|
9
10
|
|
|
10
11
|
//! GITLAB_PAT environment variable is required for this functionality to work
|
|
11
12
|
//! and your pipeline trigger should be merge_request
|
|
@@ -46,6 +47,9 @@ class GitLabPipe {
|
|
|
46
47
|
debug('GitLab Pipe: Enabled');
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
51
|
+
async prepareRun() {}
|
|
52
|
+
|
|
49
53
|
async createRun() {}
|
|
50
54
|
|
|
51
55
|
addTest(test) {
|
|
@@ -179,21 +183,6 @@ class GitLabPipe {
|
|
|
179
183
|
updateRun() {}
|
|
180
184
|
}
|
|
181
185
|
|
|
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
|
-
|
|
197
186
|
async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
|
|
198
187
|
if (process.env.GITLAB_KEEP_OUTDATED_REPORTS) return;
|
|
199
188
|
|