@testomatio/reporter 1.0.1 → 1.0.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 +269 -0
- package/lib/adapter/codecept.js +57 -39
- package/lib/adapter/cucumber/current.js +95 -59
- package/lib/adapter/cucumber/legacy.js +25 -11
- package/lib/adapter/jest.js +7 -2
- package/lib/adapter/playwright.js +17 -10
- package/lib/client.js +25 -27
- package/lib/dataStorage.js +113 -49
- package/lib/logger.js +134 -111
- package/lib/pipe/testomatio.js +2 -0
- package/lib/util.js +2 -0
- package/package.json +4 -2
package/lib/dataStorage.js
CHANGED
|
@@ -6,6 +6,7 @@ const { join } = require('path');
|
|
|
6
6
|
const JestReporter = require('./adapter/jest');
|
|
7
7
|
const { TESTOMAT_TMP_STORAGE } = require('./constants');
|
|
8
8
|
const { fileSystem } = require('./util');
|
|
9
|
+
const getTestIdFromTestTitle = require('./util').parseTest;
|
|
9
10
|
|
|
10
11
|
class DataStorage {
|
|
11
12
|
/**
|
|
@@ -13,52 +14,48 @@ class DataStorage {
|
|
|
13
14
|
* Stores data to global variable or to file depending on what is applicable for current test runner.
|
|
14
15
|
* dataType: 'log' | 'artifact' | ...
|
|
15
16
|
* @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
|
|
16
|
-
* @param {*} params
|
|
17
17
|
*/
|
|
18
|
-
constructor(dataType
|
|
19
|
-
if (!dataType) throw new Error('Data type is required when creating data storage');
|
|
18
|
+
constructor(dataType) {
|
|
19
|
+
// if (!dataType) throw new Error('Data type is required when creating data storage');
|
|
20
20
|
this.dataType = dataType || 'data';
|
|
21
|
-
this.dataDirName =
|
|
22
|
-
this.isFileStorage = params?.isFileStorage ?? !global.testomatioDataStore;
|
|
21
|
+
this.dataDirName = this.dataType;
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
FYI:
|
|
26
|
-
If this storage instance is used within test runner, it works fine.
|
|
27
|
-
But if, for example, we create storage instance inside the testomatio client (to get stored data),
|
|
28
|
-
the environment could be different (not already a test runner env).
|
|
29
|
-
Thus, checking environment is only reasonable when you put data to starage
|
|
30
|
-
and potentially useless when get data from storage
|
|
31
|
-
*/
|
|
32
|
-
this.runningEnvironment = this.getRunningEnviroment();
|
|
33
|
-
if (this.runningEnvironment === 'jest') this.isFileStorage = true;
|
|
23
|
+
this._refreshStorageType();
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
25
|
+
// dir is created in any case (not to recheck its existence every time)
|
|
26
|
+
this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataDirName);
|
|
27
|
+
fileSystem.createDir(this.dataDirPath);
|
|
39
28
|
|
|
40
29
|
debug(`Data storage mode: ${this.isFileStorage ? 'file' : 'memory'}`);
|
|
41
30
|
}
|
|
42
31
|
|
|
43
|
-
// TODO: implement
|
|
44
|
-
getTestIdFromContext(context) { // eslint-disable-line
|
|
45
|
-
// eslint-disable-line
|
|
46
|
-
// return testId;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
32
|
/**
|
|
50
33
|
* Try to define the running environment. Not 100% reliable and used as additional check
|
|
51
34
|
* @returns jest | mocha | ...
|
|
52
35
|
*/
|
|
53
36
|
getRunningEnviroment() {
|
|
37
|
+
// jest
|
|
54
38
|
if (process.env.JEST_WORKER_ID) return 'jest';
|
|
39
|
+
|
|
40
|
+
// mocha
|
|
55
41
|
try {
|
|
56
42
|
// @ts-expect-error mocha is defined only in mocha environment
|
|
57
43
|
if (typeof mocha !== 'undefined') return 'mocha';
|
|
58
44
|
} catch (e) {
|
|
59
45
|
// ignore
|
|
60
46
|
}
|
|
61
|
-
|
|
47
|
+
|
|
48
|
+
// codeceptjs
|
|
49
|
+
// @ts-expect-error codeceptjs is defined only in codeceptjs environment
|
|
50
|
+
if (global.codeceptjs) return 'codeceptjs';
|
|
51
|
+
|
|
52
|
+
// others
|
|
53
|
+
// 'cucumber:current', 'cucumber:legacy'
|
|
54
|
+
if (global.testomatioRunningEnvironment) return global.testomatioRunningEnvironment;
|
|
55
|
+
|
|
56
|
+
if (process.env.PLAYWRIGHT_TEST_BASE_URL) return 'playwright';
|
|
57
|
+
|
|
58
|
+
return null;
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
/**
|
|
@@ -68,21 +65,24 @@ class DataStorage {
|
|
|
68
65
|
* @returns
|
|
69
66
|
*/
|
|
70
67
|
putData(data, context = null) {
|
|
71
|
-
|
|
68
|
+
this._refreshStorageType();
|
|
72
69
|
|
|
73
|
-
let testId = null;
|
|
74
|
-
if (typeof context === 'string') {
|
|
75
|
-
testId = context;
|
|
76
|
-
} else {
|
|
77
|
-
// TODO: derive testId from context
|
|
78
|
-
// testId = context...
|
|
79
|
-
}
|
|
70
|
+
let testId = this._tryToRetrieveTestId(context) || null;
|
|
80
71
|
|
|
81
|
-
|
|
72
|
+
if (this.runningEnvironment === 'codeceptjs') testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
|
|
82
73
|
if (this.runningEnvironment === 'jest') testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
|
|
74
|
+
// logs in playwright are gathered by pw framwork itself
|
|
75
|
+
if (this.runningEnvironment === 'playwright' && this.dataType === 'log') return;
|
|
76
|
+
|
|
77
|
+
// get id from global store
|
|
78
|
+
if (global.testomatioDataStore && global.testomatioDataStore.currentlyRunningTestId)
|
|
79
|
+
testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
|
|
83
80
|
|
|
84
81
|
// if testId is not provided, data is be saved to `{dataType}_other` file;
|
|
85
|
-
if (!testId)
|
|
82
|
+
if (!testId) {
|
|
83
|
+
debug(`No test id provided for ${this.dataType} data: ${data}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
86
|
|
|
87
87
|
if (this.isFileStorage) {
|
|
88
88
|
this._putDataToFile(data, testId);
|
|
@@ -92,13 +92,17 @@ class DataStorage {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* Returns data, stored for specific testId (or data which was stored without test id specified)
|
|
95
|
+
* Returns data, stored for specific testId (or data which was stored without test id specified).
|
|
96
|
+
* This method will get data from global variable. But if it is not available, it will try to get data from file.
|
|
97
|
+
* Thus, good approach is to remove file storage folder before each test run (and after, for sure).
|
|
96
98
|
*
|
|
97
|
-
*
|
|
99
|
+
* Defining the execution environment is not guaranteed! Is used only as additional check.
|
|
98
100
|
* @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
|
|
99
101
|
* @returns
|
|
100
102
|
*/
|
|
101
103
|
getData(context) {
|
|
104
|
+
this._refreshStorageType();
|
|
105
|
+
|
|
102
106
|
let testId = null;
|
|
103
107
|
if (typeof context === 'string') {
|
|
104
108
|
testId = context;
|
|
@@ -109,18 +113,71 @@ class DataStorage {
|
|
|
109
113
|
|
|
110
114
|
if (!testId) {
|
|
111
115
|
debug(`Cannot get test id from passed context:\n}`, context);
|
|
116
|
+
return null;
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
119
|
+
let testData = null;
|
|
120
|
+
|
|
117
121
|
if (global?.testomatioDataStore) {
|
|
118
|
-
|
|
122
|
+
testData = this._getDataFromGlobalVar(testId);
|
|
123
|
+
if (testData) return testData;
|
|
119
124
|
}
|
|
125
|
+
|
|
126
|
+
if (this.isFileStorage || !testData) {
|
|
127
|
+
testData = this._getDataFromFile(testId);
|
|
128
|
+
if (testData) return testData;
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
debug(`No ${this.dataType} data for test id ${testId} in both file and global variable`);
|
|
132
|
+
return testData;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* This method is named as "try" because it does not guarantee that test id could be retrieved.
|
|
137
|
+
* Context could be anything (test, suite, string, etc) which is used to define testId.
|
|
138
|
+
* Or it could represent any other entity (which does not contain test id).
|
|
139
|
+
* @param {*} context
|
|
140
|
+
*/
|
|
141
|
+
_tryToRetrieveTestId(context) {
|
|
142
|
+
if (!context) return null;
|
|
143
|
+
this._refreshStorageType();
|
|
144
|
+
|
|
145
|
+
if (this.runningEnvironment === 'playwright' || context?.title) {
|
|
146
|
+
// context is testInfo
|
|
147
|
+
const testId = getTestIdFromTestTitle(context.title);
|
|
148
|
+
if (testId) return testId;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (typeof context === 'string') {
|
|
152
|
+
const testId = getTestIdFromTestTitle(context);
|
|
153
|
+
if (testId) return testId;
|
|
154
|
+
}
|
|
155
|
+
|
|
121
156
|
return null;
|
|
122
157
|
}
|
|
123
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Refreshes storage type (file or global variable) depending on current environment
|
|
161
|
+
* This method should be run on each attempt to put or get data from/to storage
|
|
162
|
+
* Because storage instance is created before the test runner is started. And storage type could be changed
|
|
163
|
+
*/
|
|
164
|
+
_refreshStorageType() {
|
|
165
|
+
this.isFileStorage = !global.testomatioDataStore;
|
|
166
|
+
/*
|
|
167
|
+
FYI:
|
|
168
|
+
If this storage instance is used within test runner, it works fine.
|
|
169
|
+
But if, for example, we create storage instance inside the testomatio client (to get stored data),
|
|
170
|
+
the environment could differs (not already a test runner environment).
|
|
171
|
+
Thus, checking environment is only reasonable when you put data to starage
|
|
172
|
+
(to define where to put data - to global variable or to file)
|
|
173
|
+
and potentially useless when getting data from storage
|
|
174
|
+
*/
|
|
175
|
+
this.runningEnvironment = this.getRunningEnviroment();
|
|
176
|
+
|
|
177
|
+
// some test frameworks do not persist global variables, thus file storage is used for them
|
|
178
|
+
if (this.runningEnvironment === 'jest') this.isFileStorage = true;
|
|
179
|
+
}
|
|
180
|
+
|
|
124
181
|
_getDataFromGlobalVar(testId) {
|
|
125
182
|
try {
|
|
126
183
|
if (global?.testomatioDataStore[this.dataDirName]) {
|
|
@@ -128,7 +185,7 @@ class DataStorage {
|
|
|
128
185
|
debug(`Data for test id ${testId}:\n${testData}`);
|
|
129
186
|
return testData;
|
|
130
187
|
}
|
|
131
|
-
debug(`No ${this.dataType} data for test id ${testId}`);
|
|
188
|
+
debug(`No ${this.dataType} data for test id ${testId} in <global> storage`);
|
|
132
189
|
return null;
|
|
133
190
|
} catch (e) {
|
|
134
191
|
// there could be no data, ignore
|
|
@@ -137,13 +194,13 @@ class DataStorage {
|
|
|
137
194
|
|
|
138
195
|
_getDataFromFile(testId) {
|
|
139
196
|
try {
|
|
140
|
-
const filepath = join(
|
|
197
|
+
const filepath = join(this.dataDirPath, `${this.dataType}_${testId}`);
|
|
141
198
|
if (fs.existsSync(filepath)) {
|
|
142
199
|
const testData = fs.readFileSync(filepath, 'utf-8');
|
|
143
200
|
debug(`Data for test id ${testId}:\n${testData}`);
|
|
144
201
|
return testData;
|
|
145
202
|
}
|
|
146
|
-
debug(`No ${this.dataType} data for test id ${testId}`);
|
|
203
|
+
debug(`No ${this.dataType} data for test id ${testId} in <file> storage`);
|
|
147
204
|
return null;
|
|
148
205
|
} catch (e) {
|
|
149
206
|
// there could be no data, ignore
|
|
@@ -153,14 +210,16 @@ class DataStorage {
|
|
|
153
210
|
|
|
154
211
|
_putDataToGlobalVar(data, testId) {
|
|
155
212
|
debug('Saving data to global variable for test', testId, ':\n', data, '\n');
|
|
156
|
-
global.testomatioDataStore[this.dataDirName] = {};
|
|
157
|
-
global.testomatioDataStore[this.dataDirName][testId]
|
|
213
|
+
if (!global.testomatioDataStore[this.dataDirName]) global.testomatioDataStore[this.dataDirName] = {};
|
|
214
|
+
global.testomatioDataStore[this.dataDirName][testId] // eslint-disable-line no-unused-expressions
|
|
215
|
+
? (global.testomatioDataStore[this.dataDirName][testId] += `\n${data}`)
|
|
216
|
+
: (global.testomatioDataStore[this.dataDirName][testId] = data);
|
|
158
217
|
}
|
|
159
218
|
|
|
160
219
|
_putDataToFile(data, testId) {
|
|
161
220
|
if (typeof data !== 'string') data = JSON.stringify(data);
|
|
162
221
|
const filename = `${this.dataType}_${testId}`;
|
|
163
|
-
const filepath = join(
|
|
222
|
+
const filepath = join(this.dataDirPath, filename);
|
|
164
223
|
debug('Saving data to file for test', testId, 'to', filepath, ':\n', data, '\n');
|
|
165
224
|
|
|
166
225
|
// TODO: handle multiple invocations of JSON.stringify.
|
|
@@ -177,4 +236,9 @@ module.exports.DataStorage = DataStorage;
|
|
|
177
236
|
// TODO: rewrite client - everything regarding storing artifacts
|
|
178
237
|
// TODO: try to define adapter inside client
|
|
179
238
|
// TODO: use .env
|
|
180
|
-
// TODO: ability to intercept multiple loggers
|
|
239
|
+
// TODO: ability to intercept multiple loggers (upd: no need)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
/* Cypress
|
|
243
|
+
Parallelization is only available if using Cypress Dashboard??
|
|
244
|
+
*/
|
package/lib/logger.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const debug = require('debug')('@testomatio/reporter:logger');
|
|
3
|
+
const _ = require('lodash');
|
|
3
4
|
const { DataStorage } = require('./dataStorage');
|
|
4
5
|
|
|
5
6
|
const LOG_METHODS = ['assert', 'debug', 'error', 'info', 'log', 'trace', 'warn'];
|
|
@@ -24,61 +25,41 @@ class Logger {
|
|
|
24
25
|
// _originalUserLogger used to output logs to console by the user logger
|
|
25
26
|
// _loggerToIntercept intercepted and reassigned immediately when added
|
|
26
27
|
|
|
27
|
-
constructor(
|
|
28
|
+
constructor() {
|
|
28
29
|
// set default logger to be used in log, warn, error, etc methods
|
|
29
30
|
this._originalUserLogger = { ...console };
|
|
30
31
|
|
|
31
|
-
this.dataStorage = new DataStorage('log'
|
|
32
|
+
this.dataStorage = new DataStorage('log');
|
|
32
33
|
this.logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
|
|
33
34
|
|
|
34
35
|
// commented because prefer to use "intercept" method
|
|
35
36
|
// if (params?.logger) this._loggerToIntercept = params.logger;
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
// intercept console by default
|
|
39
|
+
this.intercept(console);
|
|
38
40
|
|
|
39
41
|
// singleton
|
|
40
42
|
if (!Logger.instance) {
|
|
41
43
|
Logger.instance = this;
|
|
42
44
|
}
|
|
43
|
-
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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);
|
|
46
|
+
this._helpers = {
|
|
47
|
+
parseLastArgToGetTestId: (...args) => {
|
|
48
|
+
try {
|
|
49
|
+
return this.dataStorage._tryToRetrieveTestId(args.at(-1));
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// node 14 support
|
|
52
|
+
return this.dataStorage._tryToRetrieveTestId(args[args.length - 1]);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
75
56
|
}
|
|
76
57
|
|
|
77
58
|
/**
|
|
78
59
|
* Allows you to define a step inside a test. Step name is attached to the report and
|
|
79
60
|
* helps to understand the test flow.
|
|
80
|
-
* @param {*} strings
|
|
81
|
-
* @param {...any} values
|
|
61
|
+
* @param {*} strings
|
|
62
|
+
* @param {...any} values
|
|
82
63
|
*/
|
|
83
64
|
step(strings, ...values) {
|
|
84
65
|
let logs = '';
|
|
@@ -98,15 +79,20 @@ class Logger {
|
|
|
98
79
|
* @returns
|
|
99
80
|
*/
|
|
100
81
|
getLogs(context) {
|
|
101
|
-
|
|
82
|
+
const logs = this.dataStorage.getData(context);
|
|
83
|
+
return logs || '';
|
|
102
84
|
}
|
|
103
85
|
|
|
104
|
-
|
|
86
|
+
_stringifyLogs(...args) {
|
|
105
87
|
const logs = [];
|
|
106
88
|
// stringify everything except strings
|
|
107
89
|
for (const arg of args) {
|
|
90
|
+
// ignore empty strings
|
|
91
|
+
if (arg === '') continue;
|
|
108
92
|
if (typeof arg === 'string') {
|
|
109
93
|
logs.push(arg);
|
|
94
|
+
} else if (Array.isArray(arg)) {
|
|
95
|
+
logs.push(arg.join(' '));
|
|
110
96
|
} else {
|
|
111
97
|
try {
|
|
112
98
|
// eslint-disable-next-line no-unused-expressions
|
|
@@ -120,123 +106,159 @@ class Logger {
|
|
|
120
106
|
return logs.join(' ');
|
|
121
107
|
}
|
|
122
108
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Tagget template literal. Allows to use different syntaxes:
|
|
111
|
+
* 1. Tagget template: $`text ${someVar}`
|
|
112
|
+
* 2. Standard: $(`text ${someVar}`)
|
|
113
|
+
* 3. Standard with multiple arguments: $('text', someVar)
|
|
114
|
+
*/
|
|
115
|
+
_log(strings, ...args) {
|
|
116
|
+
// entity which is used to define testId
|
|
117
|
+
let context = null;
|
|
118
|
+
// last argument could contain testId
|
|
119
|
+
const testId = this._helpers.parseLastArgToGetTestId(...args);
|
|
120
|
+
if (testId) {
|
|
121
|
+
// last arg is test id, do not log it
|
|
122
|
+
context = args.pop();
|
|
123
|
+
}
|
|
127
124
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
// filter empty strings
|
|
126
|
+
strings = strings.filter(item => item !== '');
|
|
127
|
+
|
|
128
|
+
let logs;
|
|
129
|
+
// this block means tagged template is used (syntax like $`text ${someVar}`)
|
|
130
|
+
if (Array.isArray(strings) && strings.length === args.length + 1) {
|
|
131
|
+
logs = strings.reduce(
|
|
132
|
+
(result, current, index) =>
|
|
133
|
+
result +
|
|
134
|
+
current +
|
|
135
|
+
// strings are splitted by args when use tagged template, thus we add arg after each string
|
|
136
|
+
// it looks like: `string1 arg1 string2 arg2 string3`
|
|
137
|
+
(args[index] !== undefined // eslint-disable-line no-nested-ternary
|
|
138
|
+
? typeof args[index] === 'string'
|
|
139
|
+
? args[index] // add arg as it is
|
|
140
|
+
: this._stringifyLogs(args[index]) // stringify arg
|
|
141
|
+
: ' '), // add space if no arg after string
|
|
142
|
+
// initial accumulator value
|
|
143
|
+
'',
|
|
144
|
+
);
|
|
145
|
+
} else {
|
|
146
|
+
// this block means arguments syntax is used (syntax like $('text', someVar))
|
|
147
|
+
// in this case strings represents just a first argument
|
|
148
|
+
logs = this._stringifyLogs(strings, ...args);
|
|
134
149
|
}
|
|
150
|
+
this._originalUserLogger.log(logs);
|
|
151
|
+
this.dataStorage.putData(logs, context);
|
|
135
152
|
}
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
154
|
+
/**
|
|
155
|
+
* This function is a wrapper for all logging methods (not to repeat the same code)
|
|
156
|
+
* @param {*} argsArray
|
|
157
|
+
* @param {*} level
|
|
158
|
+
* @returns
|
|
159
|
+
*/
|
|
160
|
+
_logWrapper(argsArray, level) {
|
|
161
|
+
if (!argsArray.length) return;
|
|
162
|
+
|
|
163
|
+
// entity which is used to define testId
|
|
164
|
+
let context = null;
|
|
165
|
+
// last argument could contain testId
|
|
166
|
+
const testId = this._helpers.parseLastArgToGetTestId(...argsArray);
|
|
167
|
+
if (testId) {
|
|
168
|
+
// last arg is test id, do not log it
|
|
169
|
+
context = argsArray.pop();
|
|
170
|
+
}
|
|
171
|
+
|
|
139
172
|
const severity = LEVELS[level].severity;
|
|
140
173
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
141
174
|
|
|
142
|
-
|
|
143
|
-
const logs = this._strinfifyLogs(...args);
|
|
175
|
+
const logs = this._stringifyLogs(...argsArray);
|
|
144
176
|
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
145
|
-
this.dataStorage.putData(colorizedLogs);
|
|
177
|
+
this.dataStorage.putData(colorizedLogs, context);
|
|
146
178
|
try {
|
|
147
|
-
|
|
179
|
+
// level.toLowerCase() represents method name (log, warn, error, etc)
|
|
180
|
+
this._originalUserLogger[level.toLowerCase()](colorizedLogs);
|
|
148
181
|
} catch (e) {
|
|
149
182
|
// method could be unexisting, ignore error
|
|
150
183
|
}
|
|
151
184
|
}
|
|
152
185
|
|
|
153
|
-
|
|
186
|
+
assert(...args) {
|
|
187
|
+
// sometimes user invokes logger without any arguments passed
|
|
188
|
+
if (!args.length) return;
|
|
189
|
+
|
|
154
190
|
const level = 'ERROR';
|
|
155
191
|
const severity = LEVELS[level].severity;
|
|
156
192
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
157
193
|
|
|
158
|
-
const logs = this.
|
|
159
|
-
|
|
160
|
-
this.dataStorage.putData(colorizedLogs);
|
|
194
|
+
const logs = this._stringifyLogs(...args);
|
|
195
|
+
this.dataStorage.putData(logs);
|
|
161
196
|
try {
|
|
162
|
-
this._originalUserLogger.
|
|
197
|
+
this._originalUserLogger.log(`Assertion result: `, ...args);
|
|
163
198
|
} catch (e) {
|
|
164
199
|
// method could be unexisting, ignore error
|
|
165
200
|
}
|
|
166
201
|
}
|
|
167
202
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
203
|
+
debug(...args) {
|
|
204
|
+
this._logWrapper(args, 'DEBUG');
|
|
205
|
+
}
|
|
172
206
|
|
|
173
|
-
|
|
174
|
-
this.
|
|
175
|
-
try {
|
|
176
|
-
this._originalUserLogger.info(...args);
|
|
177
|
-
} catch (e) {
|
|
178
|
-
// method could be unexisting, ignore error
|
|
179
|
-
}
|
|
207
|
+
error(...args) {
|
|
208
|
+
this._logWrapper(args, 'ERROR');
|
|
180
209
|
}
|
|
181
210
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
211
|
+
info(...args) {
|
|
212
|
+
this._logWrapper(args, 'INFO');
|
|
213
|
+
}
|
|
186
214
|
|
|
187
|
-
|
|
188
|
-
this.
|
|
189
|
-
try {
|
|
190
|
-
this._originalUserLogger.log(...args);
|
|
191
|
-
} catch (e) {
|
|
192
|
-
// method could be unexisting, ignore error
|
|
193
|
-
}
|
|
215
|
+
log(...args) {
|
|
216
|
+
this._logWrapper(args, 'LOG');
|
|
194
217
|
}
|
|
195
218
|
|
|
196
219
|
trace(...args) {
|
|
197
|
-
|
|
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
|
-
}
|
|
220
|
+
this._logWrapper(args, 'TRACE');
|
|
209
221
|
}
|
|
210
222
|
|
|
211
223
|
warn(...args) {
|
|
212
|
-
|
|
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
|
+
this._logWrapper(args, 'WARN');
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
/**
|
|
227
228
|
* Intercepts user logger messages.
|
|
228
229
|
* When call this method, Logger start to control the user logger,
|
|
229
|
-
* but almost nothing is changed for user
|
|
230
|
+
* but almost nothing is changed for user regarding the console output (like log level set by user)
|
|
230
231
|
* (until multiple loggers are intercepted,
|
|
231
232
|
* in this case only the last intercepted logger will be used as user console output).
|
|
232
233
|
* @param {*} userLogger
|
|
233
234
|
*/
|
|
234
235
|
intercept(userLogger) {
|
|
235
236
|
if (!userLogger) return;
|
|
236
|
-
debug(`Intercepting user logger`);
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
/* prevent multiple console interceptions (cause infinite loop)
|
|
239
|
+
actual only for "console", because its used as default output and is intercepted by default */
|
|
240
|
+
const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
|
|
241
|
+
if (isUserLoggerConsole && process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED) {
|
|
242
|
+
debug(`Try to intercept console, but it is already intercepted`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// prevent other loggers multiple interceptions
|
|
246
|
+
if (_.isEqual(userLogger, this._originalUserLogger)) {
|
|
247
|
+
debug(`Try to intercept user logger, but it is already intercepted`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED = 'true';
|
|
252
|
+
debug(isUserLoggerConsole ? 'console intercepted' : 'User logger intercepted');
|
|
253
|
+
|
|
254
|
+
/* logs could be intercepted for playwright only if console provides output, thus adding this exception.
|
|
255
|
+
it means default _originalUserLogger (console by default) will be used to provide output
|
|
256
|
+
(for other frameworks user logger will be passed for output) */
|
|
257
|
+
if (this.dataStorage.runningEnvironment !== 'playwright') {
|
|
258
|
+
// save original user logger to use it for logging (last intercepted will be used for console output)
|
|
259
|
+
//! TODO: temporary don't oeverride user logger to prevent recursion; console will be used for output
|
|
260
|
+
// this._originalUserLogger = { ...userLogger };
|
|
261
|
+
}
|
|
240
262
|
this._loggerToIntercept = userLogger;
|
|
241
263
|
|
|
242
264
|
/*
|
|
@@ -271,8 +293,9 @@ class Logger {
|
|
|
271
293
|
Logger.instance = null;
|
|
272
294
|
|
|
273
295
|
// module.exports.Logger = Logger;
|
|
274
|
-
|
|
296
|
+
const logger = new Logger();
|
|
297
|
+
|
|
298
|
+
module.exports = logger;
|
|
275
299
|
|
|
276
300
|
// TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
|
|
277
301
|
// upd: did not face such loggers, but still could be useful
|
|
278
|
-
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -27,6 +27,7 @@ class TestomatioPipe {
|
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
debug('Testomatio Pipe: Enabled');
|
|
30
|
+
this.parallel = params.parallel;
|
|
30
31
|
this.store = store || {};
|
|
31
32
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
32
33
|
this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
|
|
@@ -53,6 +54,7 @@ class TestomatioPipe {
|
|
|
53
54
|
|
|
54
55
|
const runParams = Object.fromEntries(
|
|
55
56
|
Object.entries({
|
|
57
|
+
parallel: this.parallel,
|
|
56
58
|
api_key: this.apiKey.trim(),
|
|
57
59
|
group_title: this.groupTitle,
|
|
58
60
|
env: this.env,
|
package/lib/util.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"humanize-duration": "^3.27.3",
|
|
26
26
|
"is-valid-path": "^0.1.1",
|
|
27
27
|
"json-cycle": "^1.3.0",
|
|
28
|
+
"lodash": "^4.17.21",
|
|
28
29
|
"lodash.memoize": "^4.1.2",
|
|
29
30
|
"lodash.merge": "^4.6.2",
|
|
30
31
|
"uuid": "^9.0.0"
|
|
@@ -51,11 +52,12 @@
|
|
|
51
52
|
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
|
-
"@cucumber/cucumber": "^
|
|
55
|
+
"@cucumber/cucumber": "^9.3.0",
|
|
55
56
|
"@redocly/cli": "^1.0.0-beta.125",
|
|
56
57
|
"@wdio/reporter": "^7.16.13",
|
|
57
58
|
"chai": "^4.3.6",
|
|
58
59
|
"codeceptjs": "^3.2.3",
|
|
60
|
+
"cucumber": "^6.0.7",
|
|
59
61
|
"eslint": "^8.7.0",
|
|
60
62
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
61
63
|
"eslint-config-prettier": "^8.3.0",
|