@testomatio/reporter 1.2.4-beta → 1.3.0-beta

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.
@@ -1,4 +1,4 @@
1
- // eslint-disable-next-line global-require, import/no-extraneous-dependencies
1
+ // eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
2
2
  const { Formatter, formatterHelpers } = require('@cucumber/cucumber');
3
3
  const chalk = require('chalk');
4
4
  const fs = require('fs');
@@ -29,7 +29,7 @@ class CucumberReporter extends Formatter {
29
29
  this.failures = [];
30
30
  this.cases = [];
31
31
 
32
- this.client = new TestomatClient();
32
+ this.client = new TestomatClient({ apiKey: options.apiKey || process.env.TESTOMATIO });
33
33
  this.status = STATUS.PASSED;
34
34
 
35
35
  global.testomatioRunningEnvironment = 'cucumber:current';
@@ -125,8 +125,8 @@ const createTestomatFormatter = apiKey => {
125
125
 
126
126
  if (!scenario.name) return;
127
127
 
128
- let message = '';
129
- let cliMessage = `- ${scenario.name}: ${chalk.bold(status.toUpperCase())}`;
128
+ const message = '';
129
+ const cliMessage = `- ${scenario.name}: ${chalk.bold(status.toUpperCase())}`;
130
130
 
131
131
  // if (event.result.status === 'undefined') {
132
132
  // cliMessage += chalk.yellow(
@@ -7,11 +7,10 @@ const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE } = require('../constan
7
7
  const TestomatioClient = require('../client');
8
8
  const { isArtifactsEnabled } = require('../fileUploader');
9
9
  const { parseTest, fileSystem } = require('../util');
10
- const { setCurrentlyRunningTestId } = require('../helpers');
11
10
 
12
11
  const reportTestPromises = [];
13
12
 
14
- class TestomatioReporter {
13
+ class PlaywrightReporter {
15
14
  constructor(config = {}) {
16
15
  this.client = new TestomatioClient({ apiKey: config?.apiKey });
17
16
 
@@ -19,24 +18,18 @@ class TestomatioReporter {
19
18
  }
20
19
 
21
20
  onBegin(_config, suite) {
21
+ // clean data storage
22
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
22
23
  if (!this.client) return;
23
24
  this.suite = suite;
24
25
  this.client.createRun();
25
-
26
- // for data storage
27
- // fileSystem.createDir(TESTOMAT_TMP_STORAGE.mainDir);
28
26
  }
29
27
 
30
- onTestBegin(test, result) {
31
- const testId = parseTest(test.title);
32
- setCurrentlyRunningTestId(testId);
33
- // global.testomatioDataStore.currentlyRunningTestId = testId;
34
- // TestomatioReporter.currentlyRunningTestId = testId;
35
- }
28
+ // onTestBegin(test) {
29
+ // const testId = parseTest(test.title);
30
+ // }
36
31
 
37
32
  onTestEnd(test, result) {
38
- // reset currently running test id
39
- // setCurrentlyRunningTestId('');
40
33
  if (!this.client) return;
41
34
 
42
35
  let testId = parseTest(test.title);
@@ -51,25 +44,23 @@ class TestomatioReporter {
51
44
  appendStep(step, steps);
52
45
  }
53
46
 
54
- const reportTestPromise = this.client
55
- .addTestRun(checkStatus(result.status), {
56
- error,
57
- test_id: testId,
58
- suite_title,
59
- title,
60
- steps: steps.join('\n'),
61
- time: duration,
62
- })
63
- .then(pipes => {
64
- testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
65
-
66
- this.uploads.push({
67
- testId,
68
- title,
69
- suite_title,
70
- files: result.attachments.filter(a => a.body || a.path),
71
- });
47
+ const logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
48
+
49
+ const reportTestPromise = this.client.addTestRun(checkStatus(result.status), {
50
+ error,
51
+ test_id: testId,
52
+ suite_title,
53
+ title,
54
+ steps: steps.join('\n'),
55
+ time: duration,
56
+ stack: logs,
57
+ }).then(pipes => {
58
+ testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
59
+
60
+ this.uploads.push({
61
+ testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
72
62
  });
63
+ });
73
64
 
74
65
  reportTestPromises.push(reportTestPromise);
75
66
  }
@@ -140,4 +131,4 @@ function tmpFile(prefix = 'tmp.') {
140
131
  return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
141
132
  }
142
133
 
143
- module.exports = TestomatioReporter;
134
+ module.exports = PlaywrightReporter;
package/lib/client.js CHANGED
@@ -38,7 +38,7 @@ class Client {
38
38
  */
39
39
  createRun() {
40
40
  // all pipes disabled, skipping
41
- if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
41
+ if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
42
42
 
43
43
  const runParams = {
44
44
  title: this.title,
@@ -67,7 +67,7 @@ class Client {
67
67
  */
68
68
  async addTestRun(status, testData, storeArtifacts = []) {
69
69
  // all pipes disabled, skipping
70
- if (!this.pipes.filter(p => p.isEnabled).length) return;
70
+ if (!this.pipes?.filter(p => p.isEnabled).length) return [];
71
71
 
72
72
  if (!testData)
73
73
  testData = {
@@ -92,7 +92,7 @@ class Client {
92
92
 
93
93
  const uploadedFiles = [];
94
94
 
95
- let stack = testData.stack || '';
95
+ let stack = '';
96
96
 
97
97
  if (error) {
98
98
  stack = this.formatError(error) || '';
@@ -102,8 +102,10 @@ class Client {
102
102
  stack += this.formatSteps(stack, steps);
103
103
  }
104
104
 
105
+ stack += testData.stack || '';
106
+
105
107
  // in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
106
- const logger = require('./logger');
108
+ const logger = require('./logger');
107
109
  const testLogs = logger.getLogs(test_id);
108
110
  // debug(`Test logs for ${test_id}:\n`, testLogs);
109
111
  if (stack) stack += '\n\n';
@@ -151,17 +153,19 @@ class Client {
151
153
  artifacts,
152
154
  };
153
155
 
154
- this.queue = this.queue
155
- .then(() => Promise.all(this.pipes.map(async p => {
156
- try {
157
- const result = await p.addTest(data);
158
- return { pipe: p.toString(), result };
159
- } catch (err) {
160
- console.log(APP_PREFIX, p.toString(), err);
161
- }
162
- }
163
- )));
164
-
156
+ this.queue = this.queue.then(() =>
157
+ Promise.all(
158
+ this.pipes.map(async p => {
159
+ try {
160
+ const result = await p.addTest(data);
161
+ return { pipe: p.toString(), result };
162
+ } catch (err) {
163
+ console.log(APP_PREFIX, p.toString(), err);
164
+ }
165
+ }),
166
+ ),
167
+ );
168
+
165
169
  return this.queue;
166
170
  }
167
171
 
@@ -174,7 +178,7 @@ class Client {
174
178
  */
175
179
  updateRunStatus(status, isParallel = false) {
176
180
  // all pipes disabled, skipping
177
- if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
181
+ if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
178
182
 
179
183
  const runParams = { status, parallel: isParallel };
180
184
 
@@ -6,7 +6,8 @@ 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 { getCurrentlyRunningTestId } = require('./helpers');
9
+ const getTestIdFromTestTitle = require('./util').parseTest;
10
+
10
11
  class DataStorage {
11
12
  /**
12
13
  * Creates data storage instance for specific data type.
@@ -28,34 +29,6 @@ class DataStorage {
28
29
  debug(`Data storage mode: ${this.isFileStorage ? 'file' : 'memory'}`);
29
30
  }
30
31
 
31
- // TODO: implement
32
- // eslint-disable-next-line no-unused-vars
33
- getTestIdFromContext(context) {
34
- // return testId;
35
- }
36
-
37
- /**
38
- * Refreshes storage type (file or global variable) depending on current environment
39
- * This method should be run on each attempt to put or get data from/to storage
40
- * Because storage instance is created before the test runner is started
41
- */
42
- _refreshStorageType() {
43
- this.isFileStorage = !global.testomatioDataStore;
44
- /*
45
- FYI:
46
- If this storage instance is used within test runner, it works fine.
47
- But if, for example, we create storage instance inside the testomatio client (to get stored data),
48
- the environment could be different (not already a test runner env).
49
- Thus, checking environment is only reasonable when you put data to starage
50
- and potentially useless when get data from storage
51
- */
52
- this.runningEnvironment = this.getRunningEnviroment();
53
-
54
- // jest does not persist global variables, thus file storage is used
55
- if (this.runningEnvironment === 'jest') this.isFileStorage = true;
56
- if (this.runningEnvironment === 'playwright') this.isFileStorage = true;
57
- }
58
-
59
32
  /**
60
33
  * Try to define the running environment. Not 100% reliable and used as additional check
61
34
  * @returns jest | mocha | ...
@@ -94,18 +67,13 @@ class DataStorage {
94
67
  putData(data, context = null) {
95
68
  this._refreshStorageType();
96
69
 
97
- let testId = null;
98
- if (typeof context === 'string') {
99
- testId = context;
100
- } else {
101
- // TODO: derive testId from context
102
- // testId = context...
103
- }
70
+ let testId = this._tryToRetrieveTestId(context) || null;
104
71
 
105
- // try to get testId for Jest
106
- if (this.runningEnvironment === 'jest') testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
107
72
  if (this.runningEnvironment === 'codeceptjs') testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
108
- // if (this.runningEnvironment === 'playwright') testId = testId ?? getCurrentlyRunningTestId();
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
+
109
77
  // get id from global store
110
78
  if (global.testomatioDataStore && global.testomatioDataStore.currentlyRunningTestId)
111
79
  testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
@@ -164,6 +132,52 @@ class DataStorage {
164
132
  return testData;
165
133
  }
166
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
+
156
+ return null;
157
+ }
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
+
167
181
  _getDataFromGlobalVar(testId) {
168
182
  try {
169
183
  if (global?.testomatioDataStore[this.dataDirName]) {
@@ -224,18 +238,7 @@ module.exports.DataStorage = DataStorage;
224
238
  // TODO: use .env
225
239
  // TODO: ability to intercept multiple loggers (upd: no need)
226
240
 
227
- /* Playwright
228
- There is no global scope sharing between tests and reporter listeners.
229
- Thus need to use file storage. But when tests are run in parallel, the new problem occurs.
230
- Tried to solve it by using the TEST_WORKER_INDEX env variable, but it is available inside the listener
231
- (available only inside the test). Thus when cannot match currently running test id from the reporter listener
232
- with the currently running test.
233
- The only solution I see for now is to pass the test id to the logger from the test itself.
234
- By passing the testInfo param to the test function:
235
- async ({ page }, testInfo)
236
- and then retrieving the .title from it.
237
- */
238
241
 
239
242
  /* Cypress
240
243
  Parallelization is only available if using Cypress Dashboard??
241
- */
244
+ */
package/lib/logger.js CHANGED
@@ -34,44 +34,24 @@ class Logger {
34
34
  // commented because prefer to use "intercept" method
35
35
  // if (params?.logger) this._loggerToIntercept = params.logger;
36
36
 
37
- this.intercept(this._loggerToIntercept);
37
+ // intercept console by default
38
+ this.intercept(console);
38
39
 
39
40
  // singleton
40
41
  if (!Logger.instance) {
41
42
  Logger.instance = this;
42
43
  }
43
- }
44
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);
45
+ this._helpers = {
46
+ parseLastArgToGetTestId: (...args) => {
47
+ try {
48
+ return this.dataStorage._tryToRetrieveTestId(args.at(-1));
49
+ } catch (e) {
50
+ // node 14 support
51
+ return this.dataStorage._tryToRetrieveTestId(args[args.length - 1]);
52
+ }
53
+ },
54
+ };
75
55
  }
76
56
 
77
57
  /**
@@ -102,12 +82,16 @@ class Logger {
102
82
  return logs || '';
103
83
  }
104
84
 
105
- _strinfifyLogs(...args) {
85
+ _stringifyLogs(...args) {
106
86
  const logs = [];
107
87
  // stringify everything except strings
108
88
  for (const arg of args) {
89
+ // ignore empty strings
90
+ if (arg === '') continue;
109
91
  if (typeof arg === 'string') {
110
92
  logs.push(arg);
93
+ } else if (Array.isArray(arg)) {
94
+ logs.push(arg.join(' '));
111
95
  } else {
112
96
  try {
113
97
  // eslint-disable-next-line no-unused-expressions
@@ -121,138 +105,153 @@ class Logger {
121
105
  return logs.join(' ');
122
106
  }
123
107
 
124
- assert(...args) {
125
- // sometimes user invokes logger without any arguments passed
126
- if (!args.length) return;
108
+ /**
109
+ * Tagget template literal. Allows to use different syntaxes:
110
+ * 1. Tagget template: $`text ${someVar}`
111
+ * 2. Standard: $(`text ${someVar}`)
112
+ * 3. Standard with multiple arguments: $('text', someVar)
113
+ */
114
+ _log(strings, ...args) {
115
+ // entity which is used to define testId
116
+ let context = null;
117
+ // last argument could contain testId
118
+ const testId = this._helpers.parseLastArgToGetTestId(...args);
119
+ if (testId) {
120
+ // last arg is test id, do not log it
121
+ context = args.pop();
122
+ }
127
123
 
128
- const level = 'ERROR';
129
- const severity = LEVELS[level].severity;
130
- if (severity < LEVELS[this.logLevel]?.severity) return;
124
+ // filter empty strings
125
+ strings = strings.filter(item => item !== '');
131
126
 
132
- const logs = this._strinfifyLogs(...args);
133
- this.dataStorage.putData(logs);
134
- try {
135
- this._originalUserLogger.log(`Assertion result: `, ...args);
136
- } catch (e) {
137
- // method could be unexisting, ignore error
127
+ let logs;
128
+ // this block means tagged template is used (syntax like $`text ${someVar}`)
129
+ if (Array.isArray(strings) && strings.length === args.length + 1) {
130
+ logs = strings.reduce(
131
+ (result, current, index) =>
132
+ result +
133
+ current +
134
+ // strings are splitted by args when use tagged template, thus we add arg after each string
135
+ // it looks like: `string1 arg1 string2 arg2 string3`
136
+ (args[index] !== undefined // eslint-disable-line no-nested-ternary
137
+ ? typeof args[index] === 'string'
138
+ ? args[index] // add arg as it is
139
+ : this._stringifyLogs(args[index]) // stringify arg
140
+ : ' '), // add space if no arg after string
141
+ // initial accumulator value
142
+ '',
143
+ );
144
+ } else {
145
+ // this block means arguments syntax is used (syntax like $('text', someVar))
146
+ // in this case strings represents just a first argument
147
+ logs = this._stringifyLogs(strings, ...args);
138
148
  }
149
+ this._originalUserLogger.log(logs);
150
+ this.dataStorage.putData(logs, context);
139
151
  }
140
152
 
141
- debug(...args) {
142
- if (!args.length) return;
153
+ /**
154
+ * This function is a wrapper for all logging methods (not to repeat the same code)
155
+ * @param {*} argsArray
156
+ * @param {*} level
157
+ * @returns
158
+ */
159
+ _logWrapper(argsArray, level) {
160
+ if (!argsArray.length) return;
161
+
162
+ // entity which is used to define testId
163
+ let context = null;
164
+ // last argument could contain testId
165
+ const testId = this._helpers.parseLastArgToGetTestId(...argsArray);
166
+ if (testId) {
167
+ // last arg is test id, do not log it
168
+ context = argsArray.pop();
169
+ }
143
170
 
144
- const level = 'DEBUG';
145
171
  const severity = LEVELS[level].severity;
146
172
  if (severity < LEVELS[this.logLevel]?.severity) return;
147
173
 
148
- if (this.logLevel === 'error' || this.logLevel === 'warn') return;
149
- const logs = this._strinfifyLogs(...args);
174
+ const logs = this._stringifyLogs(...argsArray);
150
175
  const colorizedLogs = chalk[LEVELS[level].color](logs);
151
- this.dataStorage.putData(colorizedLogs);
176
+ this.dataStorage.putData(colorizedLogs, context);
152
177
  try {
153
- this._originalUserLogger.debug(...args);
178
+ // level.toLowerCase() represents method name (log, warn, error, etc)
179
+ this._originalUserLogger[level.toLowerCase()](colorizedLogs);
154
180
  } catch (e) {
155
181
  // method could be unexisting, ignore error
156
182
  }
157
183
  }
158
184
 
159
- error(...args) {
185
+ assert(...args) {
186
+ // sometimes user invokes logger without any arguments passed
160
187
  if (!args.length) return;
161
188
 
162
189
  const level = 'ERROR';
163
190
  const severity = LEVELS[level].severity;
164
191
  if (severity < LEVELS[this.logLevel]?.severity) return;
165
192
 
166
- const logs = this._strinfifyLogs(...args);
167
- const colorizedLogs = chalk[LEVELS[level].color](logs);
168
- this.dataStorage.putData(colorizedLogs);
193
+ const logs = this._stringifyLogs(...args);
194
+ this.dataStorage.putData(logs);
169
195
  try {
170
- this._originalUserLogger.error(...args);
196
+ this._originalUserLogger.log(`Assertion result: `, ...args);
171
197
  } catch (e) {
172
198
  // method could be unexisting, ignore error
173
199
  }
174
200
  }
175
201
 
176
- info(...args) {
177
- if (!args.length) return;
202
+ debug(...args) {
203
+ this._logWrapper(args, 'DEBUG');
204
+ }
178
205
 
179
- const level = 'INFO';
180
- const severity = LEVELS[level].severity;
181
- if (severity < LEVELS[this.logLevel]?.severity) return;
206
+ error(...args) {
207
+ this._logWrapper(args, 'ERROR');
208
+ }
182
209
 
183
- const logs = this._strinfifyLogs(...args);
184
- this.dataStorage.putData(logs);
185
- try {
186
- this._originalUserLogger.info(...args);
187
- } catch (e) {
188
- // method could be unexisting, ignore error
189
- }
210
+ info(...args) {
211
+ this._logWrapper(args, 'INFO');
190
212
  }
191
213
 
192
214
  log(...args) {
193
- if (!args.length) return;
194
-
195
- const level = 'INFO';
196
- const severity = LEVELS[level].severity;
197
- if (severity < LEVELS[this.logLevel]?.severity) return;
198
-
199
- const logs = this._strinfifyLogs(...args);
200
- this.dataStorage.putData(logs);
201
- try {
202
- this._originalUserLogger.log(...args);
203
- } catch (e) {
204
- // method could be unexisting, ignore error
205
- }
215
+ this._logWrapper(args, 'LOG');
206
216
  }
207
217
 
208
218
  trace(...args) {
209
- if (!args.length) return;
210
-
211
- const level = 'TRACE';
212
- const severity = LEVELS[level].severity;
213
- if (severity < LEVELS[this.logLevel]?.severity) return;
214
-
215
- const logs = this._strinfifyLogs(...args);
216
- const colorizedLogs = chalk[LEVELS[level].color](logs);
217
- this.dataStorage.putData(colorizedLogs);
218
- try {
219
- this._originalUserLogger.trace(...args);
220
- } catch (e) {
221
- // method could be unexisting, ignore error
222
- }
219
+ this._logWrapper(args, 'TRACE');
223
220
  }
224
221
 
225
222
  warn(...args) {
226
- if (!args.length) return;
227
-
228
- const level = 'WARN';
229
- const severity = LEVELS[level].severity;
230
- if (severity < LEVELS[this.logLevel]?.severity) return;
231
-
232
- const logs = this._strinfifyLogs(...args);
233
- const colorizedLogs = chalk[LEVELS[level].color](logs);
234
- this.dataStorage.putData(colorizedLogs);
235
- try {
236
- this._originalUserLogger.warn(...args);
237
- } catch (e) {
238
- // method could be unexisting, ignore error
239
- }
223
+ this._logWrapper(args, 'WARN');
240
224
  }
241
225
 
242
226
  /**
243
227
  * Intercepts user logger messages.
244
228
  * When call this method, Logger start to control the user logger,
245
- * but almost nothing is changed for user ragarding the console output (like log level set by user)
229
+ * but almost nothing is changed for user regarding the console output (like log level set by user)
246
230
  * (until multiple loggers are intercepted,
247
231
  * in this case only the last intercepted logger will be used as user console output).
248
232
  * @param {*} userLogger
249
233
  */
250
234
  intercept(userLogger) {
251
235
  if (!userLogger) return;
252
- debug(`User logger intercepted`);
253
236
 
254
- // save original user logger to use it for logging (last intercepted will be used for console output)
255
- this._originalUserLogger = { ...userLogger };
237
+ /* prevent multiple console interceptions (cause infinite loop)
238
+ actual only for "console", because its used as default output and is intercepted by default */
239
+ const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
240
+ if (isUserLoggerConsole && process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED) {
241
+ debug(`Try to intercept console, but it is already intercepted`);
242
+ return;
243
+ }
244
+
245
+ process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED = 'true';
246
+ debug(isUserLoggerConsole ? 'console intercepted' : 'User logger intercepted');
247
+
248
+ /* logs could be intercepted for playwright only if console provides output, thus adding this exception.
249
+ it means default _originalUserLogger (console by default) will be used to provide output
250
+ (for other frameworks user logger will be passed for output) */
251
+ if (this.dataStorage.runningEnvironment !== 'playwright') {
252
+ // save original user logger to use it for logging (last intercepted will be used for console output)
253
+ this._originalUserLogger = { ...userLogger };
254
+ }
256
255
  this._loggerToIntercept = userLogger;
257
256
 
258
257
  /*
@@ -287,7 +286,9 @@ class Logger {
287
286
  Logger.instance = null;
288
287
 
289
288
  // module.exports.Logger = Logger;
290
- module.exports = new Logger();
289
+ const logger = new Logger();
290
+
291
+ module.exports = logger;
291
292
 
292
293
  // TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
293
294
  // upd: did not face such loggers, but still could be useful
package/lib/util.js CHANGED
@@ -12,7 +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
  const captures = testTitle.match(/@T([\w\d]+)/);
17
+
16
18
  if (captures) {
17
19
  return captures[1];
18
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.2.4-beta",
3
+ "version": "1.3.0-beta",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",
@@ -51,10 +51,12 @@
51
51
  "test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
52
52
  },
53
53
  "devDependencies": {
54
+ "@cucumber/cucumber": "^9.3.0",
54
55
  "@redocly/cli": "^1.0.0-beta.125",
55
56
  "@wdio/reporter": "^7.16.13",
56
57
  "chai": "^4.3.6",
57
58
  "codeceptjs": "^3.2.3",
59
+ "cucumber": "^6.0.7",
58
60
  "eslint": "^8.7.0",
59
61
  "eslint-config-airbnb-base": "^15.0.0",
60
62
  "eslint-config-prettier": "^8.3.0",
package/lib/helpers.js DELETED
@@ -1,34 +0,0 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
- const { TESTOMAT_TMP_STORAGE } = require('./constants');
4
-
5
- // NEXT HELPERS ARE CREATED FOR PLAYWRIGHT. IGNORE FOR NOW. PROBABLY WILL BE REMOVED SHORTLY
6
-
7
- // TEST_WORKER_INDEX is not available inside the test listener :/
8
- /**
9
- * Sets id of currently running test to storage (global variable or file)
10
- * Used for Playwright
11
- * @param {*} testId
12
- */
13
- function setCurrentlyRunningTestIdForPlaywright(testId) {
14
- const workerId = +process.env.TEST_WORKER_INDEX || 0;
15
- const filePath = path.join(TESTOMAT_TMP_STORAGE.mainDir, 'current_test_id_' + workerId);
16
- fs.writeFileSync(filePath, testId);
17
- }
18
-
19
- /**
20
- * Gets id of currently running test from storage (global variable or file)
21
- * Used for Playwright
22
- * @returns string test id of currently running test
23
- */
24
- function getCurrentlyRunningTestIdForPlaywright() {
25
- const workerId = +process.env.TEST_WORKER_INDEX || 0;
26
- const filePath = path.join(TESTOMAT_TMP_STORAGE.mainDir, 'current_test_id_' + workerId);
27
- if (fs.existsSync(filePath)) {
28
- return fs.readFileSync(filePath, 'utf8') || null;
29
- }
30
- return null;
31
- };
32
-
33
- module.exports.setCurrentlyRunningTestId = setCurrentlyRunningTestIdForPlaywright;
34
- module.exports.getCurrentlyRunningTestId = getCurrentlyRunningTestIdForPlaywright;