@testomatio/reporter 1.0.0-beta.4 → 1.0.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 +88 -540
- package/lib/{ArtifactStorage.js → _ArtifactStorageOld.js} +15 -4
- package/lib/adapter/codecept.js +29 -19
- package/lib/adapter/cucumber/current.js +1 -1
- package/lib/adapter/jest.js +16 -2
- package/lib/adapter/mocha.js +1 -1
- package/lib/adapter/playwright.js +1 -1
- package/lib/artifactStorage.js +25 -0
- package/lib/client.js +25 -16
- package/lib/constants.js +5 -0
- package/lib/dataStorage.js +180 -0
- package/lib/logger.js +278 -0
- package/lib/pipe/github.js +2 -2
- package/lib/pipe/testomatio.js +2 -1
- package/lib/reporter.js +10 -2
- package/lib/util.js +54 -33
- package/package.json +1 -1
- package/Changelog.md +0 -257
package/lib/logger.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const debug = require('debug')('@testomatio/reporter:logger');
|
|
3
|
+
const { DataStorage } = require('./dataStorage');
|
|
4
|
+
|
|
5
|
+
const LOG_METHODS = ['assert', 'debug', 'error', 'info', 'log', 'trace', 'warn'];
|
|
6
|
+
const LEVELS = {
|
|
7
|
+
ALL: { severity: 1, color: '' },
|
|
8
|
+
VERBOSE: { severity: 3, color: 'grey' },
|
|
9
|
+
TRACE: { severity: 5, color: 'grey' },
|
|
10
|
+
DEBUG: { severity: 7, color: 'cyan' },
|
|
11
|
+
INFO: { severity: 9, color: 'black' },
|
|
12
|
+
LOG: { severity: 11, color: 'black' },
|
|
13
|
+
WARN: { severity: 13, color: 'yellow' },
|
|
14
|
+
ERROR: { severity: 15, color: 'red' },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Logger allows to:
|
|
19
|
+
* 1. Intercept logs from user logger (console.log, etc) and store them.
|
|
20
|
+
* 2. Output logs to console (actually, logger functionality).
|
|
21
|
+
* 3. Varied syntax.
|
|
22
|
+
*/
|
|
23
|
+
class Logger {
|
|
24
|
+
// _originalUserLogger used to output logs to console by the user logger
|
|
25
|
+
// _loggerToIntercept intercepted and reassigned immediately when added
|
|
26
|
+
|
|
27
|
+
constructor(params = {}) {
|
|
28
|
+
// set default logger to be used in log, warn, error, etc methods
|
|
29
|
+
this._originalUserLogger = { ...console };
|
|
30
|
+
|
|
31
|
+
this.dataStorage = new DataStorage('log', params);
|
|
32
|
+
this.logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
|
|
33
|
+
|
|
34
|
+
// commented because prefer to use "intercept" method
|
|
35
|
+
// if (params?.logger) this._loggerToIntercept = params.logger;
|
|
36
|
+
|
|
37
|
+
this.intercept(this._loggerToIntercept);
|
|
38
|
+
|
|
39
|
+
// singleton
|
|
40
|
+
if (!Logger.instance) {
|
|
41
|
+
Logger.instance = this;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tagget template literal. Allows to use different syntaxes:
|
|
47
|
+
* 1. Tagget template: $`text ${someVar}`
|
|
48
|
+
* 2. Standard: $(`text ${someVar}`)
|
|
49
|
+
* 3. Standard with multiple arguments: $('text', someVar)
|
|
50
|
+
*/
|
|
51
|
+
_log(strings, ...args) {
|
|
52
|
+
let logs;
|
|
53
|
+
// this block means tagged template is used (syntax like $`text ${someVar}`)
|
|
54
|
+
if (Array.isArray(strings) && strings.length === args.length + 1) {
|
|
55
|
+
logs = strings.reduce(
|
|
56
|
+
(result, current, index) =>
|
|
57
|
+
result +
|
|
58
|
+
current +
|
|
59
|
+
// strings are splitted by args when use tagged template, thus we add arg after each string
|
|
60
|
+
// it looks like: `string1 arg1 string2 arg2 string3`
|
|
61
|
+
(args[index] !== undefined // eslint-disable-line no-nested-ternary
|
|
62
|
+
? typeof args[index] === 'string'
|
|
63
|
+
? args[index] // add arg as it is
|
|
64
|
+
: this._strinfifyLogs(args[index]) // stringify arg
|
|
65
|
+
: ' '), // add space if no arg after string
|
|
66
|
+
// initial accumulator value
|
|
67
|
+
'',
|
|
68
|
+
);
|
|
69
|
+
} else {
|
|
70
|
+
// this block means arguments syntax is used (syntax like $('text', someVar))
|
|
71
|
+
// in this case strings represents just a first argument
|
|
72
|
+
logs = this._strinfifyLogs(strings, ...args);
|
|
73
|
+
}
|
|
74
|
+
this.dataStorage.putData(logs);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Allows you to define a step inside a test. Step name is attached to the report and
|
|
79
|
+
* helps to understand the test flow.
|
|
80
|
+
* @param {*} strings
|
|
81
|
+
* @param {...any} values
|
|
82
|
+
*/
|
|
83
|
+
step(strings, ...values) {
|
|
84
|
+
let logs = '';
|
|
85
|
+
for (let i = 0; i < strings.length; i++) {
|
|
86
|
+
logs += strings[i];
|
|
87
|
+
if (i < values.length) {
|
|
88
|
+
logs += values[i];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
logs = chalk.blue(`> ${logs}`);
|
|
92
|
+
this.dataStorage.putData(logs);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
*
|
|
97
|
+
* @param {*} context testId or test context from test runner
|
|
98
|
+
* @returns
|
|
99
|
+
*/
|
|
100
|
+
getLogs(context) {
|
|
101
|
+
return this.dataStorage.getData(context);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_strinfifyLogs(...args) {
|
|
105
|
+
const logs = [];
|
|
106
|
+
// stringify everything except strings
|
|
107
|
+
for (const arg of args) {
|
|
108
|
+
if (typeof arg === 'string') {
|
|
109
|
+
logs.push(arg);
|
|
110
|
+
} else {
|
|
111
|
+
try {
|
|
112
|
+
// eslint-disable-next-line no-unused-expressions
|
|
113
|
+
this.prettyObjects ? logs.push(JSON.stringify(arg, null, 2)) : logs.push(JSON.stringify(arg));
|
|
114
|
+
} catch (e) {
|
|
115
|
+
debug('Error while stringify object', e);
|
|
116
|
+
logs.push(arg);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return logs.join(' ');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
assert(...args) {
|
|
124
|
+
const level = 'ERROR';
|
|
125
|
+
const severity = LEVELS[level].severity;
|
|
126
|
+
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
127
|
+
|
|
128
|
+
const logs = this._strinfifyLogs(...args);
|
|
129
|
+
this.dataStorage.putData(logs);
|
|
130
|
+
try {
|
|
131
|
+
this._originalUserLogger.log(`Assertion result: `, ...args);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
// method could be unexisting, ignore error
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
debug(...args) {
|
|
138
|
+
const level = 'DEBUG';
|
|
139
|
+
const severity = LEVELS[level].severity;
|
|
140
|
+
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
141
|
+
|
|
142
|
+
if (this.logLevel === 'error' || this.logLevel === 'warn') return;
|
|
143
|
+
const logs = this._strinfifyLogs(...args);
|
|
144
|
+
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
145
|
+
this.dataStorage.putData(colorizedLogs);
|
|
146
|
+
try {
|
|
147
|
+
this._originalUserLogger.debug(...args);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
// method could be unexisting, ignore error
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
error(...args) {
|
|
154
|
+
const level = 'ERROR';
|
|
155
|
+
const severity = LEVELS[level].severity;
|
|
156
|
+
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
157
|
+
|
|
158
|
+
const logs = this._strinfifyLogs(...args);
|
|
159
|
+
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
160
|
+
this.dataStorage.putData(colorizedLogs);
|
|
161
|
+
try {
|
|
162
|
+
this._originalUserLogger.error(...args);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// method could be unexisting, ignore error
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
info(...args) {
|
|
169
|
+
const level = 'INFO';
|
|
170
|
+
const severity = LEVELS[level].severity;
|
|
171
|
+
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
172
|
+
|
|
173
|
+
const logs = this._strinfifyLogs(...args);
|
|
174
|
+
this.dataStorage.putData(logs);
|
|
175
|
+
try {
|
|
176
|
+
this._originalUserLogger.info(...args);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
// method could be unexisting, ignore error
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
log(...args) {
|
|
183
|
+
const level = 'INFO';
|
|
184
|
+
const severity = LEVELS[level].severity;
|
|
185
|
+
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
186
|
+
|
|
187
|
+
const logs = this._strinfifyLogs(...args);
|
|
188
|
+
this.dataStorage.putData(logs);
|
|
189
|
+
try {
|
|
190
|
+
this._originalUserLogger.log(...args);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
// method could be unexisting, ignore error
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
trace(...args) {
|
|
197
|
+
const level = 'TRACE';
|
|
198
|
+
const severity = LEVELS[level].severity;
|
|
199
|
+
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
200
|
+
|
|
201
|
+
const logs = this._strinfifyLogs(...args);
|
|
202
|
+
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
203
|
+
this.dataStorage.putData(colorizedLogs);
|
|
204
|
+
try {
|
|
205
|
+
this._originalUserLogger.trace(...args);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
// method could be unexisting, ignore error
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
warn(...args) {
|
|
212
|
+
const level = 'WARN';
|
|
213
|
+
const severity = LEVELS[level].severity;
|
|
214
|
+
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
215
|
+
|
|
216
|
+
const logs = this._strinfifyLogs(...args);
|
|
217
|
+
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
218
|
+
this.dataStorage.putData(colorizedLogs);
|
|
219
|
+
try {
|
|
220
|
+
this._originalUserLogger.warn(...args);
|
|
221
|
+
} catch (e) {
|
|
222
|
+
// method could be unexisting, ignore error
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Intercepts user logger messages.
|
|
228
|
+
* When call this method, Logger start to control the user logger,
|
|
229
|
+
* but almost nothing is changed for user ragarding the console output (like log level set by user)
|
|
230
|
+
* (until multiple loggers are intercepted,
|
|
231
|
+
* in this case only the last intercepted logger will be used as user console output).
|
|
232
|
+
* @param {*} userLogger
|
|
233
|
+
*/
|
|
234
|
+
intercept(userLogger) {
|
|
235
|
+
if (!userLogger) return;
|
|
236
|
+
debug(`Intercepting user logger`);
|
|
237
|
+
|
|
238
|
+
// save original user logger to use it for logging (last intercepted will be used for console output)
|
|
239
|
+
this._originalUserLogger = { ...userLogger };
|
|
240
|
+
this._loggerToIntercept = userLogger;
|
|
241
|
+
|
|
242
|
+
/*
|
|
243
|
+
override user logger (any, e.g. console) methods to intercept log messages
|
|
244
|
+
this._loggerToIntercept = this; could be used, but decided to override only output methods
|
|
245
|
+
*/
|
|
246
|
+
for (const method of LOG_METHODS) {
|
|
247
|
+
/*
|
|
248
|
+
decided to comment next code line because its better to create method even if it does not exist in user logger;
|
|
249
|
+
on method invocation, we will store the data anyway and catch block will prevent potential errors
|
|
250
|
+
*/
|
|
251
|
+
// if (!this._loggerToIntercept[method]) continue;
|
|
252
|
+
this._loggerToIntercept[method] = (...args) => this[method](...args);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Allows to configure logger. Make sure you do it before the logger usage in your code.
|
|
258
|
+
*
|
|
259
|
+
* @param {Object} [config={}] - The configuration object.
|
|
260
|
+
* @param {string} [config.logLevel] - The desired log level. Valid values are 'DEBUG', 'INFO', 'WARN', and 'ERROR'.
|
|
261
|
+
* @param {boolean} [config.prettyObjects] - Specifies whether to enable pretty printing of objects.
|
|
262
|
+
* @returns {void}
|
|
263
|
+
*/
|
|
264
|
+
configure(config = {}) {
|
|
265
|
+
if (!config) return;
|
|
266
|
+
if (config.prettyObjects) this.prettyObjects = config.prettyObjects;
|
|
267
|
+
if (config.logLevel) this.logLevel = config.logLevel.toUpperCase();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
Logger.instance = null;
|
|
272
|
+
|
|
273
|
+
// module.exports.Logger = Logger;
|
|
274
|
+
module.exports = new Logger();
|
|
275
|
+
|
|
276
|
+
// TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
|
|
277
|
+
// upd: did not face such loggers, but still could be useful
|
|
278
|
+
|
package/lib/pipe/github.js
CHANGED
|
@@ -39,8 +39,8 @@ class GitHubPipe {
|
|
|
39
39
|
async createRun() {}
|
|
40
40
|
|
|
41
41
|
addTest(test) {
|
|
42
|
-
debug('Adding test:', test);
|
|
43
42
|
if (!this.isEnabled) return;
|
|
43
|
+
debug('Adding test:', test);
|
|
44
44
|
|
|
45
45
|
const index = this.tests.findIndex(t => isSameTest(t, test));
|
|
46
46
|
// update if they were already added
|
|
@@ -200,7 +200,7 @@ function fullName(t) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
|
|
203
|
-
if (process.env.
|
|
203
|
+
if (process.env.GH_KEEP_OUTDATED_REPORTS) return;
|
|
204
204
|
|
|
205
205
|
// get comments
|
|
206
206
|
let comments = [];
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -169,6 +169,7 @@ class TestomatioPipe {
|
|
|
169
169
|
|
|
170
170
|
module.exports = TestomatioPipe;
|
|
171
171
|
|
|
172
|
+
|
|
172
173
|
function setS3Credentials(artifacts) {
|
|
173
174
|
if (!Object.keys(artifacts).length) return;
|
|
174
175
|
|
|
@@ -181,4 +182,4 @@ function setS3Credentials(artifacts) {
|
|
|
181
182
|
if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
182
183
|
if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
183
184
|
resetConfig();
|
|
184
|
-
}
|
|
185
|
+
}
|
package/lib/reporter.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
const logger = require('./logger');
|
|
1
2
|
const TestomatClient = require('./client');
|
|
2
3
|
const TRConstants = require('./constants');
|
|
3
|
-
const TRArtifacts = require('./
|
|
4
|
+
const TRArtifacts = require('./_ArtifactStorageOld');
|
|
5
|
+
|
|
6
|
+
const log = logger._log.bind(logger);
|
|
7
|
+
const step = logger.step.bind(logger);
|
|
4
8
|
|
|
5
9
|
module.exports = {
|
|
10
|
+
logger,
|
|
11
|
+
log,
|
|
12
|
+
step,
|
|
6
13
|
TestomatClient,
|
|
7
14
|
TRConstants,
|
|
8
|
-
TRArtifacts
|
|
15
|
+
TRArtifacts,
|
|
16
|
+
addArtifact: TRArtifacts.artifact,
|
|
9
17
|
};
|
package/lib/util.js
CHANGED
|
@@ -3,6 +3,7 @@ const { sep, basename } = require('path');
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const isValid = require('is-valid-path');
|
|
6
|
+
const debug = require('debug')('@testomatio/reporter:util');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* @param {String} testTitle - Test title
|
|
@@ -10,6 +11,7 @@ const isValid = require('is-valid-path');
|
|
|
10
11
|
* @returns {String|null} testId
|
|
11
12
|
*/
|
|
12
13
|
const parseTest = testTitle => {
|
|
14
|
+
if (!testTitle) return null;
|
|
13
15
|
const captures = testTitle.match(/@T([\w\d]+)/);
|
|
14
16
|
if (captures) {
|
|
15
17
|
return captures[1];
|
|
@@ -32,7 +34,6 @@ const parseSuite = suiteTitle => {
|
|
|
32
34
|
return null;
|
|
33
35
|
};
|
|
34
36
|
|
|
35
|
-
|
|
36
37
|
const ansiRegExp = () => {
|
|
37
38
|
const pattern = [
|
|
38
39
|
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
@@ -54,11 +55,14 @@ const isValidUrl = s => {
|
|
|
54
55
|
|
|
55
56
|
const fetchFilesFromStackTrace = (stack = '') => {
|
|
56
57
|
const files = stack.matchAll(/file:?\/(\/.*?\.(png|avi|webm|jpg|html|txt))/g);
|
|
57
|
-
return Array.from(files)
|
|
58
|
-
|
|
58
|
+
return Array.from(files)
|
|
59
|
+
.map(f => f[1])
|
|
60
|
+
.filter(f => fs.existsSync(f));
|
|
61
|
+
};
|
|
59
62
|
|
|
60
63
|
const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
61
|
-
const stackLines = stack
|
|
64
|
+
const stackLines = stack
|
|
65
|
+
.split('\n')
|
|
62
66
|
.filter(l => l.includes(':'))
|
|
63
67
|
// .map(l => l.match(/\[(.*?)\]/)?.[1] || l) // minitest format
|
|
64
68
|
// .map(l => l.split(':')[0])
|
|
@@ -67,17 +71,17 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
67
71
|
.filter(l => isValid(l?.split(':')[0]))
|
|
68
72
|
|
|
69
73
|
// // filter out 3rd party libs
|
|
70
|
-
.filter(l => !l?.includes(`vendor${
|
|
71
|
-
.filter(l => !l?.includes(`node_modules${
|
|
74
|
+
.filter(l => !l?.includes(`vendor${sep}`))
|
|
75
|
+
.filter(l => !l?.includes(`node_modules${sep}`))
|
|
72
76
|
.filter(l => fs.existsSync(l.split(':')[0]))
|
|
73
|
-
.filter(l => fs.lstatSync(l.split(':')[0]).isFile())
|
|
77
|
+
.filter(l => fs.lstatSync(l.split(':')[0]).isFile());
|
|
74
78
|
|
|
75
79
|
if (!stackLines.length) return '';
|
|
76
80
|
|
|
77
81
|
const [file, line] = stackLines[0].split(':');
|
|
78
82
|
|
|
79
83
|
const prepend = 3;
|
|
80
|
-
const source = fetchSourceCode(fs.readFileSync(file).toString(), { line, prepend, limit: 7 })
|
|
84
|
+
const source = fetchSourceCode(fs.readFileSync(file).toString(), { line, prepend, limit: 7 });
|
|
81
85
|
|
|
82
86
|
if (!source) return '';
|
|
83
87
|
|
|
@@ -85,22 +89,22 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
85
89
|
.map((l, i) => {
|
|
86
90
|
if (i === prepend) return `${line} > ${chalk.bold(l)}`;
|
|
87
91
|
return `${line - prepend + i} | ${l}`
|
|
88
|
-
}).join('\n')
|
|
89
|
-
}
|
|
92
|
+
}).join('\n');
|
|
93
|
+
};
|
|
90
94
|
|
|
91
95
|
const fetchSourceCode = (contents, opts = {}) => {
|
|
92
96
|
if (!opts.title && !opts.line) return '';
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
// code fragment is 20 lines
|
|
95
99
|
const limit = opts.limit || 50;
|
|
96
100
|
let lineIndex;
|
|
97
101
|
if (opts.line) lineIndex = opts.line - 1;
|
|
98
|
-
const lines = contents.split('\n')
|
|
102
|
+
const lines = contents.split('\n');
|
|
99
103
|
|
|
100
104
|
// remove special chars from title
|
|
101
105
|
if (!lineIndex && opts.title) {
|
|
102
|
-
const title = opts.title.replace(/[([@].*/g, '')
|
|
103
|
-
lineIndex = lines.findIndex(l => l.includes(title))
|
|
106
|
+
const title = opts.title.replace(/[([@].*/g, '');
|
|
107
|
+
lineIndex = lines.findIndex(l => l.includes(title));
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
if (opts.prepend) {
|
|
@@ -133,52 +137,69 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
133
137
|
if (opts.lang === 'java' && lines[i].includes(' public void ')) break;
|
|
134
138
|
if (opts.lang === 'java' && lines[i].includes(' class ')) break;
|
|
135
139
|
}
|
|
136
|
-
result.push(lines[i])
|
|
140
|
+
result.push(lines[i]);
|
|
137
141
|
}
|
|
138
142
|
return result.join('\n');
|
|
139
143
|
}
|
|
140
|
-
}
|
|
144
|
+
};
|
|
141
145
|
|
|
142
|
-
const isSameTest = (test, t) =>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
const isSameTest = (test, t) =>
|
|
147
|
+
typeof t === 'object' &&
|
|
148
|
+
typeof test === 'object' &&
|
|
149
|
+
t.title === test.title &&
|
|
150
|
+
t.suite_title === test.suite_title &&
|
|
151
|
+
Object.values(t.example || {}) === Object.values(test.example || {}) &&
|
|
152
|
+
t.test_id === test.test_id;
|
|
148
153
|
|
|
149
154
|
const getCurrentDateTime = () => {
|
|
150
155
|
const today = new Date();
|
|
151
|
-
|
|
152
|
-
return `${today.getFullYear()
|
|
153
|
-
|
|
154
|
-
}
|
|
156
|
+
|
|
157
|
+
return `${today.getFullYear()}_${
|
|
158
|
+
today.getMonth() + 1
|
|
159
|
+
}_${today.getDate()}_${today.getHours()}_${today.getMinutes()}_${today.getSeconds()}`;
|
|
160
|
+
};
|
|
155
161
|
|
|
156
162
|
/**
|
|
157
163
|
* @param {Object} test - Test adapter object
|
|
158
164
|
*
|
|
159
|
-
* @returns {String|null} testInfo as one string
|
|
165
|
+
* @returns {String|null} testInfo as one string
|
|
160
166
|
*/
|
|
161
167
|
const specificTestInfo = test => {
|
|
162
168
|
// TODO: afterEach has another context.... need to add specific handler, maybe...
|
|
163
169
|
if (test?.title && test?.file) {
|
|
164
|
-
|
|
165
|
-
return `${basename(test.file).split(".").join("#")
|
|
166
|
-
}#${
|
|
167
|
-
test.title.split(" ").join("#")}`;
|
|
170
|
+
return `${basename(test.file).split('.').join('#')}#${test.title.split(' ').join('#')}`;
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
return null;
|
|
171
174
|
};
|
|
172
175
|
|
|
176
|
+
const fileSystem = {
|
|
177
|
+
createDir(dirPath) {
|
|
178
|
+
if (!fs.existsSync(dirPath)) {
|
|
179
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
180
|
+
debug('Created dir: ', dirPath);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
clearDir(dirPath) {
|
|
184
|
+
if (fs.existsSync(dirPath)) {
|
|
185
|
+
fs.rmSync(dirPath, { recursive: true });
|
|
186
|
+
debug(`Dir ${dirPath} was deleted`);
|
|
187
|
+
} else {
|
|
188
|
+
debug(`Trying to delete ${dirPath} but it doesn't exist`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
173
193
|
module.exports = {
|
|
174
194
|
isSameTest,
|
|
175
195
|
fetchSourceCode,
|
|
176
196
|
fetchSourceCodeFromStackTrace,
|
|
177
197
|
fetchFilesFromStackTrace,
|
|
198
|
+
fileSystem,
|
|
178
199
|
getCurrentDateTime,
|
|
179
200
|
specificTestInfo,
|
|
180
201
|
isValidUrl,
|
|
181
202
|
ansiRegExp,
|
|
182
203
|
parseTest,
|
|
183
|
-
parseSuite
|
|
184
|
-
}
|
|
204
|
+
parseSuite,
|
|
205
|
+
};
|