@testomatio/reporter 1.2.0-beta → 1.2.0-beta-1
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/README.md +13 -9
- package/lib/_ArtifactStorageOld.js +1 -1
- 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 +18 -3
- package/lib/adapter/mocha.js +23 -23
- package/lib/adapter/playwright.js +18 -11
- package/lib/adapter/webdriver.js +1 -1
- package/lib/artifactStorage.js +2 -2
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +102 -34
- package/lib/constants.js +7 -0
- package/lib/dataStorage.js +132 -72
- package/lib/junit-adapter/java.js +27 -9
- package/lib/logger.js +182 -164
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +24 -37
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/html.js +187 -0
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.js +138 -34
- package/lib/reporter.js +2 -1
- package/lib/template/real-data-example.js +47 -0
- package/lib/template/template.hbs +249 -0
- package/lib/template/testomatio.hbs +720 -0
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/{util.js → utils/utils.js} +116 -10
- package/lib/xmlReader.js +132 -44
- package/package.json +10 -6
package/lib/logger.js
CHANGED
|
@@ -14,93 +14,56 @@ const LEVELS = {
|
|
|
14
14
|
ERROR: { severity: 15, color: 'red' },
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
// ! DON'T use console.log, console.warn, etc in this file, because it will lead to infinite loop
|
|
18
|
+
// use debug() instead
|
|
19
|
+
|
|
17
20
|
/**
|
|
18
|
-
* Logger allows to
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* 3. Varied syntax.
|
|
21
|
+
* Logger allows to intercept logs from any logger (console.log, tracer, pino, etc)
|
|
22
|
+
* and save in the testomatio reporter.
|
|
23
|
+
* Supports different syntaxes to satisfy any user preferences.
|
|
22
24
|
*/
|
|
23
25
|
class Logger {
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
constructor(params = {}) {
|
|
28
|
-
// set default logger to be used in log, warn, error, etc methods
|
|
29
|
-
this._originalUserLogger = { ...console };
|
|
26
|
+
// set default logger to be used in log, warn, error, etc methods
|
|
27
|
+
#originalUserLogger = { ...console };
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
this.logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
|
|
29
|
+
#dataStorage = new DataStorage('log');
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
// if (params?.logger) this._loggerToIntercept = params.logger;
|
|
31
|
+
logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
constructor() {
|
|
34
|
+
// intercept console by default
|
|
35
|
+
this.intercept(console);
|
|
38
36
|
|
|
39
37
|
// singleton
|
|
40
38
|
if (!Logger.instance) {
|
|
41
39
|
Logger.instance = this;
|
|
42
40
|
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Returns the string (logs) and the context
|
|
47
|
-
* This is required because loggers take multiple arguments
|
|
48
|
-
* @param {...any} args
|
|
49
|
-
*/
|
|
50
|
-
/*
|
|
51
|
-
TODO: its difficult to distinguish context from log artument if its not a string,
|
|
52
|
-
e.g. user can use something like console.log('some log', { key: 'value' });
|
|
53
|
-
consider not to use this method at all and don't expect context from user inside default log methods;
|
|
54
|
-
instead, use custom method like $`some log` and parse it
|
|
55
|
-
*/
|
|
56
|
-
// _getLogStringAndContextFromArgs(...args) {
|
|
57
|
-
// let context = '';
|
|
58
|
-
// if (args.length > 1 && typeof args[args.length - 1] !== 'string') {
|
|
59
|
-
// context = args.pop();
|
|
60
|
-
// }
|
|
61
|
-
// const logs = args.join(' ');
|
|
62
|
-
// return { logs, context };
|
|
63
|
-
// }
|
|
64
41
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
result +
|
|
78
|
-
current +
|
|
79
|
-
// strings are splitted by args when use tagged template, thus we add arg after each string
|
|
80
|
-
// it looks like: `string1 arg1 string2 arg2 string3`
|
|
81
|
-
(args[index] !== undefined // eslint-disable-line no-nested-ternary
|
|
82
|
-
? typeof args[index] === 'string'
|
|
83
|
-
? args[index] // add arg as it is
|
|
84
|
-
: this._strinfifyLogs(args[index]) // stringify arg
|
|
85
|
-
: ' '), // add space if no arg after string
|
|
86
|
-
// initial accumulator value
|
|
87
|
-
'',
|
|
88
|
-
);
|
|
89
|
-
} else {
|
|
90
|
-
// this block means arguments syntax is used (syntax like $('text', someVar))
|
|
91
|
-
// in this case strings represents just a first argument
|
|
92
|
-
logs = this._strinfifyLogs(strings, ...args);
|
|
42
|
+
// add beforeEach hook for mocha. it does not override existing hook, just add new one
|
|
43
|
+
try {
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
if (!beforeEach) return;
|
|
46
|
+
// @ts-ignore-next-line
|
|
47
|
+
beforeEach(function () {
|
|
48
|
+
if (this.currentTest?.__mocha_id__) {
|
|
49
|
+
global.testTitle = this.currentTest.fullTitle();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// ignore
|
|
93
54
|
}
|
|
94
|
-
this.dataStorage.putData(logs);
|
|
95
55
|
}
|
|
96
56
|
|
|
97
57
|
/**
|
|
98
58
|
* Allows you to define a step inside a test. Step name is attached to the report and
|
|
99
59
|
* helps to understand the test flow.
|
|
100
|
-
* @param {*} strings
|
|
101
|
-
* @param {...any} values
|
|
60
|
+
* @param {*} strings
|
|
61
|
+
* @param {...any} values
|
|
102
62
|
*/
|
|
103
63
|
step(strings, ...values) {
|
|
64
|
+
// get testId for mocha
|
|
65
|
+
const context = global.testTitle ?? null;
|
|
66
|
+
|
|
104
67
|
let logs = '';
|
|
105
68
|
for (let i = 0; i < strings.length; i++) {
|
|
106
69
|
logs += strings[i];
|
|
@@ -109,7 +72,7 @@ class Logger {
|
|
|
109
72
|
}
|
|
110
73
|
}
|
|
111
74
|
logs = chalk.blue(`> ${logs}`);
|
|
112
|
-
this
|
|
75
|
+
this.#dataStorage.putData(logs, context);
|
|
113
76
|
}
|
|
114
77
|
|
|
115
78
|
/**
|
|
@@ -118,15 +81,20 @@ class Logger {
|
|
|
118
81
|
* @returns
|
|
119
82
|
*/
|
|
120
83
|
getLogs(context) {
|
|
121
|
-
|
|
84
|
+
const logs = this.#dataStorage.getData(context);
|
|
85
|
+
return logs || '';
|
|
122
86
|
}
|
|
123
87
|
|
|
124
|
-
|
|
88
|
+
#stringifyLogs(...args) {
|
|
125
89
|
const logs = [];
|
|
126
90
|
// stringify everything except strings
|
|
127
91
|
for (const arg of args) {
|
|
92
|
+
// ignore empty strings
|
|
93
|
+
if (arg === '') continue;
|
|
128
94
|
if (typeof arg === 'string') {
|
|
129
95
|
logs.push(arg);
|
|
96
|
+
} else if (Array.isArray(arg)) {
|
|
97
|
+
logs.push(arg.join(' '));
|
|
130
98
|
} else {
|
|
131
99
|
try {
|
|
132
100
|
// eslint-disable-next-line no-unused-expressions
|
|
@@ -140,137 +108,148 @@ class Logger {
|
|
|
140
108
|
return logs.join(' ');
|
|
141
109
|
}
|
|
142
110
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Tagget template literal. Allows to use different syntaxes:
|
|
113
|
+
* 1. Tagget template: log`text ${someVar}`
|
|
114
|
+
* 2. Standard: log(`text ${someVar}`)
|
|
115
|
+
* 3. Standard with multiple arguments: log('text', someVar)
|
|
116
|
+
*/
|
|
117
|
+
templateLiteralLog(strings, ...args) {
|
|
118
|
+
if (Array.isArray(strings)) strings = strings.filter(item => item !== '').map(item => item.trim());
|
|
119
|
+
if (Array.isArray(args)) args = args.filter(item => item !== '');
|
|
120
|
+
// entity which is used to define testId
|
|
121
|
+
let context = null;
|
|
147
122
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
123
|
+
// get testId for mocha
|
|
124
|
+
context = global.testTitle ?? null;
|
|
125
|
+
|
|
126
|
+
let logs;
|
|
127
|
+
// this block means tagged template is used (syntax like $`text ${someVar}`)
|
|
128
|
+
if (Array.isArray(strings) && strings.length === args.length + 1) {
|
|
129
|
+
logs = strings.reduce(
|
|
130
|
+
(result, current, index) =>
|
|
131
|
+
result +
|
|
132
|
+
current +
|
|
133
|
+
// strings are splitted by args when use tagged template, thus we add arg after each string
|
|
134
|
+
// it looks like: `string1 arg1 string2 arg2 string3`
|
|
135
|
+
(args[index] !== undefined // eslint-disable-line no-nested-ternary
|
|
136
|
+
? typeof args[index] === 'string'
|
|
137
|
+
? args[index] // add arg as it is
|
|
138
|
+
: this.#stringifyLogs(args[index]) // stringify arg
|
|
139
|
+
: ''),
|
|
140
|
+
// initial accumulator value
|
|
141
|
+
'',
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
// this block means arguments syntax is used (syntax like $('text', someVar))
|
|
145
|
+
// in this case strings represents just a first argument
|
|
146
|
+
logs = this.#stringifyLogs(strings, ...args);
|
|
154
147
|
}
|
|
148
|
+
this.#originalUserLogger.log(logs);
|
|
149
|
+
this.#dataStorage.putData(logs, context);
|
|
155
150
|
}
|
|
156
151
|
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
/**
|
|
153
|
+
* This function is a wrapper for each logging methods (log, warn, error etc) (not to repeat the same code)
|
|
154
|
+
* @param {*} argsArray
|
|
155
|
+
* @param {*} level
|
|
156
|
+
* @returns
|
|
157
|
+
*/
|
|
158
|
+
#logWrapper(argsArray, level) {
|
|
159
|
+
if (!argsArray.length) return;
|
|
160
|
+
|
|
161
|
+
// entity which is used to define testId
|
|
162
|
+
let context = null;
|
|
163
|
+
|
|
164
|
+
// get context for mocha
|
|
165
|
+
context = global.testTitle ?? null;
|
|
166
|
+
|
|
159
167
|
const severity = LEVELS[level].severity;
|
|
160
168
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
161
169
|
|
|
162
|
-
|
|
163
|
-
const logs = this._strinfifyLogs(...args);
|
|
170
|
+
const logs = this.#stringifyLogs(...argsArray);
|
|
164
171
|
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
165
|
-
this
|
|
172
|
+
this.#dataStorage.putData(colorizedLogs, context);
|
|
166
173
|
try {
|
|
167
|
-
|
|
174
|
+
// level.toLowerCase() represents method name (log, warn, error, etc)
|
|
175
|
+
this.#originalUserLogger[level.toLowerCase()](colorizedLogs);
|
|
168
176
|
} catch (e) {
|
|
169
177
|
// method could be unexisting, ignore error
|
|
170
178
|
}
|
|
171
179
|
}
|
|
172
180
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
181
|
+
assert(...args) {
|
|
182
|
+
this.#logWrapper(args, 'ERROR');
|
|
183
|
+
}
|
|
177
184
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.dataStorage.putData(colorizedLogs);
|
|
181
|
-
try {
|
|
182
|
-
this._originalUserLogger.error(...args);
|
|
183
|
-
} catch (e) {
|
|
184
|
-
// method could be unexisting, ignore error
|
|
185
|
-
}
|
|
185
|
+
debug(...args) {
|
|
186
|
+
this.#logWrapper(args, 'DEBUG');
|
|
186
187
|
}
|
|
187
188
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
189
|
+
error(...args) {
|
|
190
|
+
this.#logWrapper(args, 'ERROR');
|
|
191
|
+
}
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
this
|
|
195
|
-
try {
|
|
196
|
-
this._originalUserLogger.info(...args);
|
|
197
|
-
} catch (e) {
|
|
198
|
-
// method could be unexisting, ignore error
|
|
199
|
-
}
|
|
193
|
+
info(...args) {
|
|
194
|
+
this.#logWrapper(args, 'INFO');
|
|
200
195
|
}
|
|
201
196
|
|
|
202
197
|
log(...args) {
|
|
203
|
-
|
|
204
|
-
const severity = LEVELS[level].severity;
|
|
205
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
206
|
-
|
|
207
|
-
const logs = this._strinfifyLogs(...args);
|
|
208
|
-
this.dataStorage.putData(logs);
|
|
209
|
-
try {
|
|
210
|
-
this._originalUserLogger.log(...args);
|
|
211
|
-
} catch (e) {
|
|
212
|
-
// method could be unexisting, ignore error
|
|
213
|
-
}
|
|
198
|
+
this.#logWrapper(args, 'LOG');
|
|
214
199
|
}
|
|
215
200
|
|
|
216
201
|
trace(...args) {
|
|
217
|
-
|
|
218
|
-
const severity = LEVELS[level].severity;
|
|
219
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
220
|
-
|
|
221
|
-
const logs = this._strinfifyLogs(...args);
|
|
222
|
-
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
223
|
-
this.dataStorage.putData(colorizedLogs);
|
|
224
|
-
try {
|
|
225
|
-
this._originalUserLogger.trace(...args);
|
|
226
|
-
} catch (e) {
|
|
227
|
-
// method could be unexisting, ignore error
|
|
228
|
-
}
|
|
202
|
+
this.#logWrapper(args, 'TRACE');
|
|
229
203
|
}
|
|
230
204
|
|
|
231
205
|
warn(...args) {
|
|
232
|
-
|
|
233
|
-
const severity = LEVELS[level].severity;
|
|
234
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
235
|
-
|
|
236
|
-
const logs = this._strinfifyLogs(...args);
|
|
237
|
-
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
238
|
-
this.dataStorage.putData(colorizedLogs);
|
|
239
|
-
try {
|
|
240
|
-
this._originalUserLogger.warn(...args);
|
|
241
|
-
} catch (e) {
|
|
242
|
-
// method could be unexisting, ignore error
|
|
243
|
-
}
|
|
206
|
+
this.#logWrapper(args, 'WARN');
|
|
244
207
|
}
|
|
245
208
|
|
|
246
209
|
/**
|
|
247
210
|
* Intercepts user logger messages.
|
|
248
|
-
* When call this method, Logger start to control the user logger
|
|
249
|
-
* but almost nothing is changed for user ragarding the console output (like log level set by user)
|
|
250
|
-
* (until multiple loggers are intercepted,
|
|
251
|
-
* in this case only the last intercepted logger will be used as user console output).
|
|
211
|
+
* When call this method, Logger start to control the user logger
|
|
252
212
|
* @param {*} userLogger
|
|
253
213
|
*/
|
|
254
214
|
intercept(userLogger) {
|
|
255
|
-
|
|
256
|
-
|
|
215
|
+
/* prevent multiple console interceptions (cause of infinite loop)
|
|
216
|
+
actual only for "console", because its used as default output and is intercepted by default */
|
|
217
|
+
const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
|
|
218
|
+
if (isUserLoggerConsole && process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED) {
|
|
219
|
+
debug(`Try to intercept console, but it is already intercepted`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
257
222
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
this._loggerToIntercept = userLogger;
|
|
223
|
+
process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED = 'true';
|
|
224
|
+
debug(isUserLoggerConsole ? 'console intercepted' : 'User logger intercepted');
|
|
261
225
|
|
|
262
|
-
|
|
263
|
-
override user logger (any, e.g. console) methods to intercept log messages
|
|
264
|
-
this._loggerToIntercept = this; could be used, but decided to override only output methods
|
|
265
|
-
*/
|
|
226
|
+
// override user logger (any, e.g. console) methods to intercept log messages
|
|
266
227
|
for (const method of LOG_METHODS) {
|
|
267
228
|
/*
|
|
268
|
-
|
|
229
|
+
its better to create method even if it does not exist in user logger;
|
|
269
230
|
on method invocation, we will store the data anyway and catch block will prevent potential errors
|
|
231
|
+
while trying to output the message to terminal
|
|
270
232
|
*/
|
|
271
233
|
// if (!this._loggerToIntercept[method]) continue;
|
|
272
|
-
|
|
234
|
+
userLogger[method] = (...args) => this[method](...args);
|
|
273
235
|
}
|
|
236
|
+
|
|
237
|
+
/* Playwright
|
|
238
|
+
Playwright intercepts console messages by default. Thus when we intercept any logger and provide output by console,
|
|
239
|
+
messages are intercepted by Playwright.
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
/*
|
|
243
|
+
Initial idea was to intercept any logger (tracer, pino, etc),
|
|
244
|
+
intercept message and provide output by the same logger.
|
|
245
|
+
But reality brings some problems: the same messages are intercepted multiple times
|
|
246
|
+
(because of multiple loggers are created at the same terminal process).
|
|
247
|
+
Also its difficult to understand (actually did not find the way to do it) if logger was already intercepted or not.
|
|
248
|
+
Thus, decided to intercept only console by default and provide output by default console.
|
|
249
|
+
It means, if user uses his own logger, its messages will be intercepted,
|
|
250
|
+
but the output will be provided by console.
|
|
251
|
+
TODO: try to implement the providing output to terminal by user logger
|
|
252
|
+
*/
|
|
274
253
|
}
|
|
275
254
|
|
|
276
255
|
/**
|
|
@@ -283,7 +262,7 @@ class Logger {
|
|
|
283
262
|
*/
|
|
284
263
|
configure(config = {}) {
|
|
285
264
|
if (!config) return;
|
|
286
|
-
if (config.prettyObjects) this.prettyObjects = config.prettyObjects;
|
|
265
|
+
if (config.prettyObjects === false || config.prettyObjects === true) this.prettyObjects = config.prettyObjects;
|
|
287
266
|
if (config.logLevel) this.logLevel = config.logLevel.toUpperCase();
|
|
288
267
|
}
|
|
289
268
|
}
|
|
@@ -291,11 +270,50 @@ class Logger {
|
|
|
291
270
|
Logger.instance = null;
|
|
292
271
|
|
|
293
272
|
// module.exports.Logger = Logger;
|
|
294
|
-
|
|
273
|
+
const logger = new Logger();
|
|
274
|
+
|
|
275
|
+
module.exports = logger;
|
|
295
276
|
|
|
296
|
-
// TODO: consider using fs promises instead of writeSync/appendFileSync to prevent blocking and improve performance
|
|
297
277
|
// TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
|
|
298
278
|
// upd: did not face such loggers, but still could be useful
|
|
299
279
|
|
|
300
|
-
|
|
301
|
-
|
|
280
|
+
/* Cypress
|
|
281
|
+
There is no listener like "after:test" in cypress, only "after:spec" is available.
|
|
282
|
+
Thus, cannot separate logs even when I gather them (because I don't know when the test is done, just know about suite).
|
|
283
|
+
Also there is no easy way to access the message from cy.log() function.
|
|
284
|
+
(Using testomatio logger – logger.log() is not convenient because Cypress chains its commands,
|
|
285
|
+
thus such command will interrupt the chain.)
|
|
286
|
+
|
|
287
|
+
(Could not implement intercepting of cy.log('message'));
|
|
288
|
+
I found the only ability to get any logs using .task('log', 'message') (this is custom, not default cypress command)
|
|
289
|
+
and then intercept it with:
|
|
290
|
+
on('task', {
|
|
291
|
+
log (message) {
|
|
292
|
+
console.log(message)
|
|
293
|
+
return null
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
but:
|
|
298
|
+
1) it does not solve problem with getting current running testId;
|
|
299
|
+
2) leads to warning "Warning: Multiple attempts to register the following task(s):".)
|
|
300
|
+
|
|
301
|
+
My way to get test id:
|
|
302
|
+
add cypress command to save test title to file)):
|
|
303
|
+
Cypress.Commands.add('writeTestTitleToFile', () => {
|
|
304
|
+
const testTitle = cy.state('runnable').title;
|
|
305
|
+
cy.writeFile('testomatio_test_title', testTitle);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
Finally, in the test it will look like:
|
|
309
|
+
cy
|
|
310
|
+
.writeTestTitleToFile() // <<<
|
|
311
|
+
.task('log', 'This is a log message from the test') // <<<
|
|
312
|
+
|
|
313
|
+
.get('element)
|
|
314
|
+
.type('text')
|
|
315
|
+
.click()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
Parallelization in Cypress is only available if using Cypress Dashboard??
|
|
319
|
+
*/
|
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
|
|