@testomatio/reporter 1.0.11 → 1.0.12

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.
@@ -2,8 +2,8 @@
2
2
  const { DataStorage } = require('./dataStorage');
3
3
 
4
4
  class ArtifactStorage {
5
- constructor(params = {}) {
6
- this.dataStorage = new DataStorage('artifact', params);
5
+ constructor() {
6
+ this.dataStorage = new DataStorage('artifact');
7
7
 
8
8
  // singleton
9
9
  if (!ArtifactStorage.instance) {
@@ -13,21 +13,15 @@ class DataStorage {
13
13
  * Creates data storage instance for specific data type.
14
14
  * Stores data to global variable or to file depending on what is applicable for current test runner
15
15
  * (running environment).
16
- * dataType: 'log' | 'artifact' | ...
17
16
  * @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
18
17
  */
19
18
  constructor(dataType) {
20
- // if (!dataType) throw new Error('Data type is required when creating data storage');
21
19
  this.dataType = dataType || 'data';
22
- this.dataDirName = this.dataType;
23
20
  this.isFileStorage = true;
24
- this._refreshStorageType();
21
+ this.#refreshStorageType();
25
22
 
26
- // dir is created in any case (not to recheck its existence every time)
27
- this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataDirName);
23
+ this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataType);
28
24
  fileSystem.createDir(this.dataDirPath);
29
-
30
- debug(`Data storage mode: ${this.isFileStorage ? 'file' : 'memory'}`);
31
25
  }
32
26
 
33
27
  /**
@@ -38,18 +32,14 @@ class DataStorage {
38
32
  // jest
39
33
  if (process.env.JEST_WORKER_ID) return 'jest';
40
34
 
41
- // codeceptjs
42
- // @ts-expect-error codeceptjs is defined only in codeceptjs environment
43
35
  if (global.codeceptjs) return 'codeceptjs';
44
36
 
45
- // others
46
37
  // 'cucumber:current', 'cucumber:legacy'
47
38
  if (global.testomatioRunningEnvironment) return global.testomatioRunningEnvironment;
48
39
 
49
- if (process.env.PLAYWRIGHT_TEST_BASE_URL) return 'playwright';
40
+ if (process.env.PLAYWRIGHT_TEST_BASE_URL || process.env.TEST_WORKER_INDEX) return 'playwright';
50
41
 
51
42
  // mocha - can't detect
52
-
53
43
  return null;
54
44
  }
55
45
 
@@ -60,18 +50,19 @@ class DataStorage {
60
50
  * @returns
61
51
  */
62
52
  putData(data, context = null) {
63
- this._refreshStorageType();
53
+ this.#refreshStorageType();
64
54
 
65
- let testId = this._tryToRetrieveTestId(context) || null;
55
+ let testId = this.#tryToRetrieveTestId(context) || null;
66
56
 
67
57
  if (this.runningEnvironment === 'codeceptjs') {
68
58
  this.isFileStorage = false;
69
59
  testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
70
60
  }
61
+
71
62
  if (this.runningEnvironment === 'jest') {
72
63
  testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
73
- this.isFileStorage = true;
74
64
  }
65
+
75
66
  // logs in playwright are gathered by pw framework itself
76
67
  if (this.runningEnvironment === 'playwright' && this.dataType === 'log') return;
77
68
 
@@ -80,18 +71,17 @@ class DataStorage {
80
71
  testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
81
72
 
82
73
  if (!testId && global?.currentlyRunningTestTitle)
83
- testId = this._tryToRetrieveTestId(global.currentlyRunningTestTitle);
74
+ testId = this.#tryToRetrieveTestId(global.currentlyRunningTestTitle);
84
75
 
85
- // if testId is not provided, data is be saved to `{dataType}_other` file;
86
76
  if (!testId) {
87
77
  debug(`No test id provided for ${this.dataType} data: ${data}`);
88
78
  return;
89
79
  }
90
80
 
91
81
  if (this.isFileStorage) {
92
- this._putDataToFile(data, testId);
82
+ this.#putDataToFile(data, testId);
93
83
  } else {
94
- this._putDataToGlobalVar(data, testId);
84
+ this.#putDataToGlobalVar(data, testId);
95
85
  }
96
86
  }
97
87
 
@@ -105,25 +95,25 @@ class DataStorage {
105
95
  * @returns
106
96
  */
107
97
  getData(context) {
108
- this._refreshStorageType();
98
+ this.#refreshStorageType();
109
99
 
110
100
  let testId = null;
111
101
  if (typeof context === 'string') {
112
- testId = context;
102
+ testId = this.#tryToRetrieveTestId(context) || context;
113
103
  } else {
114
104
  // TODO: derive testId from context
115
105
  // testId = context...
116
106
  }
117
107
 
118
108
  if (!testId) {
119
- debug(`Cannot get test id from passed context:\n}`, context);
109
+ debug(`Cannot get test id from passed context:`, context);
120
110
  return null;
121
111
  }
122
112
 
123
113
  let testData = '';
124
114
 
125
115
  if (global?.testomatioDataStore) {
126
- testData = this._getDataFromGlobalVar(testId);
116
+ testData = this.#getDataFromGlobalVar(testId);
127
117
  // these frameworks use global variable storage
128
118
  if (testData && ['cucumber', 'codeceptjs'].includes(this.runningEnvironment)) return testData;
129
119
  }
@@ -132,17 +122,15 @@ class DataStorage {
132
122
  mocha has created a global storage, but just in some cases, generally it is not available
133
123
  */
134
124
  // if (this.isFileStorage || !testData) {
135
- // testData = this._getDataFromFile(testId);
125
+ // testData = this.#getDataFromFile(testId);
136
126
  // if (testData) return testData;
137
127
  // }
138
128
 
139
- const testDataFromFile = this._getDataFromFile(testId);
129
+ const testDataFromFile = this.#getDataFromFile(testId);
140
130
  testData += testDataFromFile || '';
141
131
 
142
- if (testData) return testData;
143
-
144
132
  debug(`No ${this.dataType} data for test id ${testId} in both file and global variable`);
145
- return testData;
133
+ return testData || '';
146
134
  }
147
135
 
148
136
  /**
@@ -151,9 +139,9 @@ class DataStorage {
151
139
  * Or it could represent any other entity (which does not contain test id).
152
140
  * @param {*} context
153
141
  */
154
- _tryToRetrieveTestId(context) {
142
+ #tryToRetrieveTestId(context) {
155
143
  if (!context) return null;
156
- this._refreshStorageType();
144
+ this.#refreshStorageType();
157
145
 
158
146
  if (this.runningEnvironment === 'playwright' || context?.title) {
159
147
  // context is testInfo
@@ -174,28 +162,19 @@ class DataStorage {
174
162
  * This method should be run on each attempt to put or get data from/to storage
175
163
  * Because storage instance is created before the test runner is started. And storage type could be changed
176
164
  */
177
- _refreshStorageType() {
178
- /*
179
- FYI:
180
- If this storage instance is used within test runner, it works fine.
181
- But if, for example, we create storage instance inside the testomatio client (to get stored data),
182
- the environment could differs (not already a test runner environment).
183
- Thus, checking environment is only reasonable when you put data to starage
184
- (to define where to put data - to global variable or to file)
185
- and potentially useless when getting data from storage
186
- */
165
+ #refreshStorageType() {
187
166
  this.runningEnvironment = this.getRunningEnviroment();
188
167
 
189
168
  // some test frameworks do not persist global variables, thus file storage is used for them (by default)
190
169
  if (['playwright', 'codeceptjs'].includes(this.runningEnvironment)) this.isFileStorage = false;
191
170
  }
192
171
 
193
- _getDataFromGlobalVar(testId) {
172
+ #getDataFromGlobalVar(testId) {
194
173
  try {
195
- if (global?.testomatioDataStore[this.dataDirName]) {
196
- const testData = global.testomatioDataStore[this.dataDirName][testId];
174
+ if (global?.testomatioDataStore[this.dataType]) {
175
+ const testData = global.testomatioDataStore[this.dataType][testId];
197
176
  debug(`Data for test id ${testId}:\n${testData}`);
198
- return testData;
177
+ return testData || '';
199
178
  }
200
179
  debug(`No ${this.dataType} data for test id ${testId} in <global> storage`);
201
180
  return '';
@@ -204,13 +183,13 @@ class DataStorage {
204
183
  }
205
184
  }
206
185
 
207
- _getDataFromFile(testId) {
186
+ #getDataFromFile(testId) {
208
187
  try {
209
188
  const filepath = join(this.dataDirPath, `${this.dataType}_${testId}`);
210
189
  if (fs.existsSync(filepath)) {
211
190
  const testData = fs.readFileSync(filepath, 'utf-8');
212
191
  debug(`Data for test id ${testId}:\n${testData}`);
213
- return testData;
192
+ return testData || '';
214
193
  }
215
194
  debug(`No ${this.dataType} data for test id ${testId} in <file> storage`);
216
195
  return '';
@@ -220,18 +199,20 @@ class DataStorage {
220
199
  return '';
221
200
  }
222
201
 
223
- _putDataToGlobalVar(data, testId) {
202
+ #putDataToGlobalVar(data, testId) {
224
203
  debug('Saving data to global variable for test', testId, ':\n', data, '\n');
225
- if (!global.testomatioDataStore[this.dataDirName]) global.testomatioDataStore[this.dataDirName] = {};
226
- global.testomatioDataStore[this.dataDirName][testId] // eslint-disable-line no-unused-expressions
227
- ? (global.testomatioDataStore[this.dataDirName][testId] += `\n${data}`)
228
- : (global.testomatioDataStore[this.dataDirName][testId] = data);
204
+ if (!global.testomatioDataStore) global.testomatioDataStore = {};
205
+ if (!global.testomatioDataStore?.[this.dataType]) global.testomatioDataStore[this.dataType] = {};
206
+ global.testomatioDataStore?.[this.dataType][testId] // eslint-disable-line no-unused-expressions
207
+ ? (global.testomatioDataStore[this.dataType][testId] += `\n${data}`)
208
+ : (global.testomatioDataStore[this.dataType][testId] = data);
229
209
  }
230
210
 
231
- _putDataToFile(data, testId) {
211
+ #putDataToFile(data, testId) {
232
212
  if (typeof data !== 'string') data = JSON.stringify(data);
233
213
  const filename = `${this.dataType}_${testId}`;
234
214
  const filepath = join(this.dataDirPath, filename);
215
+ if (!fs.existsSync(this.dataDirPath)) fileSystem.createDir(this.dataDirPath);
235
216
  debug('Saving data to file for test', testId, 'to', filepath, ':\n', data, '\n');
236
217
 
237
218
  // TODO: handle multiple invocations of JSON.stringify.
@@ -249,7 +230,3 @@ module.exports.DataStorage = DataStorage;
249
230
  // TODO: try to define adapter inside client
250
231
  // TODO: use .env
251
232
  // TODO: ability to intercept multiple loggers (upd: no need)
252
-
253
- /* Cypress
254
- Parallelization is only available if using Cypress Dashboard??
255
- */
package/lib/logger.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const chalk = require('chalk');
2
2
  const debug = require('debug')('@testomatio/reporter:logger');
3
- const _ = require('lodash');
4
3
  const { DataStorage } = require('./dataStorage');
5
4
 
6
5
  const LOG_METHODS = ['assert', 'debug', 'error', 'info', 'log', 'trace', 'warn'];
@@ -15,26 +14,23 @@ const LEVELS = {
15
14
  ERROR: { severity: 15, color: 'red' },
16
15
  };
17
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
+
18
20
  /**
19
- * Logger allows to:
20
- * 1. Intercept logs from user logger (console.log, etc) and store them.
21
- * 2. Output logs to console (actually, logger functionality).
22
- * 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.
23
24
  */
24
25
  class Logger {
25
- // _originalUserLogger used to output logs to console by the user logger
26
- // _loggerToIntercept intercepted and reassigned immediately when added
27
-
28
- constructor() {
29
- // set default logger to be used in log, warn, error, etc methods
30
- this._originalUserLogger = { ...console };
26
+ // set default logger to be used in log, warn, error, etc methods
27
+ #originalUserLogger = { ...console };
31
28
 
32
- this.dataStorage = new DataStorage('log');
33
- this.logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
29
+ #dataStorage = new DataStorage('log');
34
30
 
35
- // commented because prefer to use "intercept" method
36
- // if (params?.logger) this._loggerToIntercept = params.logger;
31
+ logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
37
32
 
33
+ constructor() {
38
34
  // intercept console by default
39
35
  this.intercept(console);
40
36
 
@@ -43,22 +39,11 @@ class Logger {
43
39
  Logger.instance = this;
44
40
  }
45
41
 
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
- };
56
-
57
42
  // add beforeEach hook for mocha. it does not override existing hook, just add new one
58
43
  try {
59
44
  // @ts-ignore
60
45
  if (!beforeEach) return;
61
- // @ts-ignore
46
+ // @ts-ignore-next-line
62
47
  beforeEach(function () {
63
48
  if (this.currentTest?.__mocha_id__) {
64
49
  global.testTitle = this.currentTest.fullTitle();
@@ -76,6 +61,9 @@ class Logger {
76
61
  * @param {...any} values
77
62
  */
78
63
  step(strings, ...values) {
64
+ // get testId for mocha
65
+ const context = global.testTitle ?? null;
66
+
79
67
  let logs = '';
80
68
  for (let i = 0; i < strings.length; i++) {
81
69
  logs += strings[i];
@@ -84,7 +72,7 @@ class Logger {
84
72
  }
85
73
  }
86
74
  logs = chalk.blue(`> ${logs}`);
87
- this.dataStorage.putData(logs);
75
+ this.#dataStorage.putData(logs, context);
88
76
  }
89
77
 
90
78
  /**
@@ -93,11 +81,11 @@ class Logger {
93
81
  * @returns
94
82
  */
95
83
  getLogs(context) {
96
- const logs = this.dataStorage.getData(context);
84
+ const logs = this.#dataStorage.getData(context);
97
85
  return logs || '';
98
86
  }
99
87
 
100
- _stringifyLogs(...args) {
88
+ #stringifyLogs(...args) {
101
89
  const logs = [];
102
90
  // stringify everything except strings
103
91
  for (const arg of args) {
@@ -122,27 +110,19 @@ class Logger {
122
110
 
123
111
  /**
124
112
  * Tagget template literal. Allows to use different syntaxes:
125
- * 1. Tagget template: $`text ${someVar}`
126
- * 2. Standard: $(`text ${someVar}`)
127
- * 3. Standard with multiple arguments: $('text', someVar)
113
+ * 1. Tagget template: log`text ${someVar}`
114
+ * 2. Standard: log(`text ${someVar}`)
115
+ * 3. Standard with multiple arguments: log('text', someVar)
128
116
  */
129
- _log(strings, ...args) {
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 !== '');
130
120
  // entity which is used to define testId
131
121
  let context = null;
132
122
 
133
123
  // get testId for mocha
134
124
  context = global.testTitle ?? null;
135
125
 
136
- // last argument could contain testId
137
- const testId = this._helpers.parseLastArgToGetTestId(...args);
138
- if (testId) {
139
- // last arg is test id, do not log it
140
- context = args.pop();
141
- }
142
-
143
- // filter empty strings
144
- strings = strings.filter(item => item !== '');
145
-
146
126
  let logs;
147
127
  // this block means tagged template is used (syntax like $`text ${someVar}`)
148
128
  if (Array.isArray(strings) && strings.length === args.length + 1) {
@@ -155,18 +135,18 @@ class Logger {
155
135
  (args[index] !== undefined // eslint-disable-line no-nested-ternary
156
136
  ? typeof args[index] === 'string'
157
137
  ? args[index] // add arg as it is
158
- : this._stringifyLogs(args[index]) // stringify arg
159
- : ' '), // add space if no arg after string
138
+ : this.#stringifyLogs(args[index]) // stringify arg
139
+ : ''),
160
140
  // initial accumulator value
161
141
  '',
162
142
  );
163
143
  } else {
164
144
  // this block means arguments syntax is used (syntax like $('text', someVar))
165
145
  // in this case strings represents just a first argument
166
- logs = this._stringifyLogs(strings, ...args);
146
+ logs = this.#stringifyLogs(strings, ...args);
167
147
  }
168
- this._originalUserLogger.log(logs);
169
- this.dataStorage.putData(logs, context);
148
+ this.#originalUserLogger.log(logs);
149
+ this.#dataStorage.putData(logs, context);
170
150
  }
171
151
 
172
152
  /**
@@ -175,7 +155,7 @@ class Logger {
175
155
  * @param {*} level
176
156
  * @returns
177
157
  */
178
- _logWrapper(argsArray, level) {
158
+ #logWrapper(argsArray, level) {
179
159
  if (!argsArray.length) return;
180
160
 
181
161
  // entity which is used to define testId
@@ -184,117 +164,92 @@ class Logger {
184
164
  // get context for mocha
185
165
  context = global.testTitle ?? null;
186
166
 
187
- // last argument could contain testId
188
- const testId = this._helpers.parseLastArgToGetTestId(...argsArray);
189
- if (testId) {
190
- // last arg is test id, do not log it
191
- context = argsArray.pop();
192
- }
193
-
194
167
  const severity = LEVELS[level].severity;
195
168
  if (severity < LEVELS[this.logLevel]?.severity) return;
196
169
 
197
- const logs = this._stringifyLogs(...argsArray);
170
+ const logs = this.#stringifyLogs(...argsArray);
198
171
  const colorizedLogs = chalk[LEVELS[level].color](logs);
199
- this.dataStorage.putData(colorizedLogs, context);
172
+ this.#dataStorage.putData(colorizedLogs, context);
200
173
  try {
201
174
  // level.toLowerCase() represents method name (log, warn, error, etc)
202
- this._originalUserLogger[level.toLowerCase()](colorizedLogs);
175
+ this.#originalUserLogger[level.toLowerCase()](colorizedLogs);
203
176
  } catch (e) {
204
177
  // method could be unexisting, ignore error
205
178
  }
206
179
  }
207
180
 
208
181
  assert(...args) {
209
- // sometimes user invokes logger without any arguments passed
210
- if (!args.length) return;
211
-
212
- const level = 'ERROR';
213
- const severity = LEVELS[level].severity;
214
- if (severity < LEVELS[this.logLevel]?.severity) return;
215
-
216
- const logs = this._stringifyLogs(...args);
217
- this.dataStorage.putData(logs);
218
- try {
219
- this._originalUserLogger.log(`Assertion result: `, ...args);
220
- } catch (e) {
221
- // method could be unexisting, ignore error
222
- }
182
+ this.#logWrapper(args, 'ERROR');
223
183
  }
224
184
 
225
185
  debug(...args) {
226
- this._logWrapper(args, 'DEBUG');
186
+ this.#logWrapper(args, 'DEBUG');
227
187
  }
228
188
 
229
189
  error(...args) {
230
- this._logWrapper(args, 'ERROR');
190
+ this.#logWrapper(args, 'ERROR');
231
191
  }
232
192
 
233
193
  info(...args) {
234
- this._logWrapper(args, 'INFO');
194
+ this.#logWrapper(args, 'INFO');
235
195
  }
236
196
 
237
197
  log(...args) {
238
- this._logWrapper(args, 'LOG');
198
+ this.#logWrapper(args, 'LOG');
239
199
  }
240
200
 
241
201
  trace(...args) {
242
- this._logWrapper(args, 'TRACE');
202
+ this.#logWrapper(args, 'TRACE');
243
203
  }
244
204
 
245
205
  warn(...args) {
246
- this._logWrapper(args, 'WARN');
206
+ this.#logWrapper(args, 'WARN');
247
207
  }
248
208
 
249
209
  /**
250
210
  * Intercepts user logger messages.
251
- * When call this method, Logger start to control the user logger,
252
- * but almost nothing is changed for user regarding the console output (like log level set by user)
253
- * (until multiple loggers are intercepted,
254
- * 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
255
212
  * @param {*} userLogger
256
213
  */
257
214
  intercept(userLogger) {
258
- if (!userLogger) return;
259
-
260
- /* prevent multiple console interceptions (cause infinite loop)
215
+ /* prevent multiple console interceptions (cause of infinite loop)
261
216
  actual only for "console", because its used as default output and is intercepted by default */
262
217
  const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
263
218
  if (isUserLoggerConsole && process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED) {
264
219
  debug(`Try to intercept console, but it is already intercepted`);
265
220
  return;
266
221
  }
267
- // prevent other loggers multiple interceptions
268
- if (_.isEqual(userLogger, this._originalUserLogger)) {
269
- debug(`Try to intercept user logger, but it is already intercepted`);
270
- return;
271
- }
272
222
 
273
223
  process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED = 'true';
274
224
  debug(isUserLoggerConsole ? 'console intercepted' : 'User logger intercepted');
275
225
 
276
- /* logs could be intercepted for playwright only if console provides output, thus adding this exception.
277
- it means default _originalUserLogger (console by default) will be used to provide output
278
- (for other frameworks user logger will be passed for output) */
279
- if (this.dataStorage.runningEnvironment !== 'playwright') {
280
- // save original user logger to use it for logging (last intercepted will be used for console output)
281
- //! TODO: temporary don't oeverride user logger to prevent recursion; console will be used for output
282
- // this._originalUserLogger = { ...userLogger };
283
- }
284
- this._loggerToIntercept = userLogger;
285
-
286
- /*
287
- override user logger (any, e.g. console) methods to intercept log messages
288
- this._loggerToIntercept = this; could be used, but decided to override only output methods
289
- */
226
+ // override user logger (any, e.g. console) methods to intercept log messages
290
227
  for (const method of LOG_METHODS) {
291
228
  /*
292
- decided to comment next code line because its better to create method even if it does not exist in user logger;
229
+ its better to create method even if it does not exist in user logger;
293
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
294
232
  */
295
233
  // if (!this._loggerToIntercept[method]) continue;
296
- this._loggerToIntercept[method] = (...args) => this[method](...args);
234
+ userLogger[method] = (...args) => this[method](...args);
297
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
+ */
298
253
  }
299
254
 
300
255
  /**
@@ -307,7 +262,7 @@ class Logger {
307
262
  */
308
263
  configure(config = {}) {
309
264
  if (!config) return;
310
- if (config.prettyObjects) this.prettyObjects = config.prettyObjects;
265
+ if (config.prettyObjects === false || config.prettyObjects === true) this.prettyObjects = config.prettyObjects;
311
266
  if (config.logLevel) this.logLevel = config.logLevel.toUpperCase();
312
267
  }
313
268
  }
@@ -358,4 +313,7 @@ Finally, in the test it will look like:
358
313
  .get('element)
359
314
  .type('text')
360
315
  .click()
316
+
317
+
318
+ Parallelization in Cypress is only available if using Cypress Dashboard??
361
319
  */
package/lib/reporter.js CHANGED
@@ -3,11 +3,12 @@ const TestomatClient = require('./client');
3
3
  const TRConstants = require('./constants');
4
4
  const TRArtifacts = require('./_ArtifactStorageOld');
5
5
 
6
- const log = logger._log.bind(logger);
6
+ const log = logger.templateLiteralLog.bind(logger);
7
7
  const step = logger.step.bind(logger);
8
8
 
9
9
  module.exports = {
10
10
  logger,
11
+ testomatioLogger: logger,
11
12
  log,
12
13
  step,
13
14
  TestomatClient,
package/lib/util.js CHANGED
@@ -12,9 +12,9 @@ const debug = require('debug')('@testomatio/reporter:util');
12
12
  */
13
13
  const parseTest = testTitle => {
14
14
  if (!testTitle) return null;
15
-
15
+
16
16
  const captures = testTitle.match(/@T([\w\d]+)/);
17
-
17
+
18
18
  if (captures) {
19
19
  return captures[1];
20
20
  }
@@ -87,11 +87,13 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
87
87
 
88
88
  if (!source) return '';
89
89
 
90
- return source.split('\n')
90
+ return source
91
+ .split('\n')
91
92
  .map((l, i) => {
92
93
  if (i === prepend) return `${line} > ${chalk.bold(l)}`;
93
- return `${line - prepend + i} | ${l}`
94
- }).join('\n');
94
+ return `${line - prepend + i} | ${l}`;
95
+ })
96
+ .join('\n');
95
97
  };
96
98
 
97
99
  const fetchSourceCode = (contents, opts = {}) => {
@@ -115,7 +117,7 @@ const fetchSourceCode = (contents, opts = {}) => {
115
117
 
116
118
  if (lineIndex) {
117
119
  const result = [];
118
- for (let i = lineIndex; i < (lineIndex + limit); i++) {
120
+ for (let i = lineIndex; i < lineIndex + limit; i++) {
119
121
  if (lines[i] === undefined) continue;
120
122
 
121
123
  if (i > lineIndex + 2 && !opts.prepend) {
@@ -189,54 +191,63 @@ const fileSystem = {
189
191
  } else {
190
192
  debug(`Trying to delete ${dirPath} but it doesn't exist`);
191
193
  }
192
- }
194
+ },
193
195
  };
194
196
 
195
- const humanize = (text) => {
197
+ const humanize = text => {
196
198
  text = decamelize(text);
197
- return text.replace(/_./g, match => ` ${ match.charAt(1).toUpperCase()}`)
198
- .trim()
199
- .replace(/^(.)|\s(.)/g, ($1) => $1.toUpperCase()).trim()
200
- .replace(/\sA\s/g, ' a ') // replace a|the
201
- .replace(/\sThe\s/g, ' the ') // replace a|the
202
- .replace(/^Test\s/, '')
203
- .replace(/^Should\s/, '')
204
- }
199
+ return text
200
+ .replace(/_./g, match => ` ${match.charAt(1).toUpperCase()}`)
201
+ .trim()
202
+ .replace(/^(.)|\s(.)/g, $1 => $1.toUpperCase())
203
+ .trim()
204
+ .replace(/\sA\s/g, ' a ') // replace a|the
205
+ .replace(/\sThe\s/g, ' the ') // replace a|the
206
+ .replace(/^Test\s/, '')
207
+ .replace(/^Should\s/, '');
208
+ };
205
209
 
206
210
  /**
207
211
  * From https://github.com/sindresorhus/decamelize/blob/main/index.js
208
- * @param {*} text
209
- * @returns
212
+ * @param {*} text
213
+ * @returns
210
214
  */
211
- const decamelize = (text) => {
215
+ const decamelize = text => {
212
216
  const separator = '_';
213
- const replacement = `$1${separator}$2`;
214
-
215
- // Split lowercase sequences followed by uppercase character.
216
- // `dataForUSACounties` → `data_For_USACounties`
217
- // `myURLstring → `my_URLstring`
218
- let decamelized = text.replace(
219
- /([\p{Lowercase_Letter}\d])(\p{Uppercase_Letter})/gu,
220
- replacement,
221
- );
222
-
223
- // Lowercase all single uppercase characters. As we
224
- // want to preserve uppercase sequences, we cannot
225
- // simply lowercase the separated string at the end.
226
- // `data_For_USACounties` → `data_for_USACounties`
227
- decamelized = decamelized.replace(
228
- /((?<![\p{Uppercase_Letter}\d])[\p{Uppercase_Letter}\d](?![\p{Uppercase_Letter}\d]))/gu,
229
- $0 => $0.toLowerCase(),
230
- );
231
-
232
- // Remaining uppercase sequences will be separated from lowercase sequences.
233
- // `data_For_USACounties` `data_for_USA_counties`
234
- return decamelized.replace(
235
- /(\p{Uppercase_Letter}+)(\p{Uppercase_Letter}\p{Lowercase_Letter}+)/gu,
236
- (_, $1, $2) => $1 + separator + $2.toLowerCase(),
237
- );
217
+ const replacement = `$1${separator}$2`;
218
+
219
+ // Split lowercase sequences followed by uppercase character.
220
+ // `dataForUSACounties` → `data_For_USACounties`
221
+ // `myURLstring → `my_URLstring`
222
+ let decamelized = text.replace(/([\p{Lowercase_Letter}\d])(\p{Uppercase_Letter})/gu, replacement);
223
+
224
+ // Lowercase all single uppercase characters. As we
225
+ // want to preserve uppercase sequences, we cannot
226
+ // simply lowercase the separated string at the end.
227
+ // `data_For_USACounties` `data_for_USACounties`
228
+ decamelized = decamelized.replace(
229
+ /((?<![\p{Uppercase_Letter}\d])[\p{Uppercase_Letter}\d](?![\p{Uppercase_Letter}\d]))/gu,
230
+ $0 => $0.toLowerCase(),
231
+ );
232
+
233
+ // Remaining uppercase sequences will be separated from lowercase sequences.
234
+ // `data_For_USACounties` → `data_for_USA_counties`
235
+ return decamelized.replace(
236
+ /(\p{Uppercase_Letter}+)(\p{Uppercase_Letter}\p{Lowercase_Letter}+)/gu,
237
+ (_, $1, $2) => $1 + separator + $2.toLowerCase(),
238
+ );
238
239
  };
239
240
 
241
+ /**
242
+ * Used to remove color codes
243
+ * @param {*} input
244
+ * @returns
245
+ */
246
+ function removeColorCodes(input) {
247
+ // eslint-disable-next-line no-control-regex
248
+ return input.replace(/\x1b\[[0-9;]*m/g, '');
249
+ }
250
+
240
251
  module.exports = {
241
252
  isSameTest,
242
253
  fetchSourceCode,
@@ -250,4 +261,5 @@ module.exports = {
250
261
  parseTest,
251
262
  parseSuite,
252
263
  humanize,
264
+ removeColorCodes,
253
265
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",
@@ -25,7 +25,6 @@
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",
29
28
  "lodash.memoize": "^4.1.2",
30
29
  "lodash.merge": "^4.6.2",
31
30
  "uuid": "^9.0.0"
@@ -42,14 +41,14 @@
42
41
  "lint:fix": "eslint lib --fix",
43
42
  "test": "mocha tests/**",
44
43
  "init": "cd ./tests/adapter/examples/cucumber && npm i",
45
- "test:unit": "mocha tests",
46
44
  "test:adapter": "mocha './tests/adapter/index.test.js'",
47
45
  "test:pipes": "mocha './tests/pipes/*_test.js'",
48
46
  "test:adapter:jest:example": "jest './tests/adapter/examples/jest/index.test.js' --config='./tests/adapter/examples/jest/jest.config.js'",
49
47
  "test:adapter:mocha:example": "mocha './tests/adapter/examples/mocha/index.test.js' --config='./tests/adapter/examples/mocha/mocha.config.js'",
50
48
  "test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
51
49
  "test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
52
- "test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
50
+ "test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
51
+ "test:storage": "npx mocha ./tests-storage/**"
53
52
  },
54
53
  "devDependencies": {
55
54
  "@cucumber/cucumber": "^9.3.0",
@@ -66,6 +65,7 @@
66
65
  "jest": "^27.4.7",
67
66
  "mocha": "^9.2.0",
68
67
  "mock-http-server": "^1.4.5",
68
+ "pino": "^8.15.0",
69
69
  "prettier": "2.5.1",
70
70
  "puppeteer": "^13.1.2"
71
71
  },