@testomatio/reporter 1.2.0-beta-4 → 1.2.0

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,14 +1,18 @@
1
1
  const debug = require('debug')('@testomatio/reporter:pipe:testomatio');
2
2
  const chalk = require('chalk');
3
+ // Retry interceptor function
4
+ const axiosRetry = require('axios-retry');
5
+ // Default axios instance
3
6
  const axios = require('axios');
4
7
  const JsonCycle = require('json-cycle');
5
- const { APP_PREFIX, STATUS } = require('../constants');
8
+
9
+ const { APP_PREFIX, STATUS, AXIOS_TIMEOUT, AXIOS_RETRY_TIMEOUT } = require('../constants');
6
10
  const { isValidUrl, foundedTestLog } = require('../utils/utils');
7
- const { parseFilterParams, generateFilterRequestParams, setS3Credentials, } = require('../utils/pipe_utils');
11
+ const { parseFilterParams, generateFilterRequestParams, setS3Credentials } = require('../utils/pipe_utils');
12
+ const config = require('../config');
8
13
 
9
- const { TESTOMATIO_RUN } = process.env;
10
- if (TESTOMATIO_RUN) {
11
- process.env.runId = TESTOMATIO_RUN;
14
+ if (process.env.TESTOMATIO_RUN) {
15
+ process.env.runId = process.env.TESTOMATIO_RUN;
12
16
  }
13
17
 
14
18
  /**
@@ -21,7 +25,7 @@ class TestomatioPipe {
21
25
  constructor(params, store) {
22
26
  this.isEnabled = false;
23
27
  this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
24
- this.apiKey = params.apiKey || process.env.TESTOMATIO;
28
+ this.apiKey = params.apiKey || config.TESTOMATIO;
25
29
  debug('Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
26
30
  if (!this.apiKey) {
27
31
  return;
@@ -33,10 +37,32 @@ class TestomatioPipe {
33
37
  this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
34
38
  this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
35
39
  this.env = process.env.TESTOMATIO_ENV;
36
-
40
+ this.label = process.env.TESTOMATIO_LABEL;
41
+ // Create a new instance of axios with a custom config
37
42
  this.axios = axios.create({
38
43
  baseURL: `${this.url.trim()}`,
39
- timeout: 30000
44
+ timeout: AXIOS_TIMEOUT,
45
+ });
46
+ // Pass the axios instance to the retry function
47
+ axiosRetry(this.axios, {
48
+ retries: 3, // Number of retries (Defaults to 3)
49
+ shouldResetTimeout: true,
50
+ retryCondition: error => {
51
+ // Conditional check the error status code
52
+ switch (error.response.status) {
53
+ case 409:
54
+ case 429:
55
+ case 502:
56
+ case 503:
57
+ return true; // Retry request with response status code 409, 429, 502, 503
58
+ default:
59
+ return false; // Do not retry the others
60
+ }
61
+ },
62
+ retryDelay: retryCount => retryCount * AXIOS_RETRY_TIMEOUT, // sum = 15sec
63
+ onRetry: retryCount => {
64
+ debug(`Retry attempt #${retryCount} failed. Retrying again...`);
65
+ },
40
66
  });
41
67
 
42
68
  this.isEnabled = true;
@@ -56,7 +82,7 @@ class TestomatioPipe {
56
82
  /**
57
83
  * Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
58
84
  * @param {Object} opts - The options for preparing the test grepList.
59
- * @returns {Promise<string[]>} - An array containing the retrieved
85
+ * @returns {Promise<string[]>} - An array containing the retrieved
60
86
  * test grepList, or an empty array if no tests are found or the request is disabled.
61
87
  * @throws {Error} - Throws an error if there was a problem while making the request.
62
88
  */
@@ -69,7 +95,7 @@ class TestomatioPipe {
69
95
  const q = generateFilterRequestParams({
70
96
  type,
71
97
  id,
72
- apiKey: this.apiKey.trim()
98
+ apiKey: this.apiKey.trim(),
73
99
  });
74
100
 
75
101
  if (!q) {
@@ -83,14 +109,10 @@ class TestomatioPipe {
83
109
  foundedTestLog(APP_PREFIX, data.tests);
84
110
  return data.tests;
85
111
  }
86
-
112
+
87
113
  console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
88
- }
89
- catch (err) {
90
- console.error(
91
- APP_PREFIX,
92
- `🚩 Error getting Testomat.io test grepList: ${err}`,
93
- );
114
+ } catch (err) {
115
+ console.error(APP_PREFIX, `🚩 Error getting Testomat.io test grepList: ${err}`);
94
116
  }
95
117
  }
96
118
 
@@ -131,6 +153,7 @@ class TestomatioPipe {
131
153
  jira_id: this.jiraId,
132
154
  env: this.env,
133
155
  title: this.title,
156
+ label: this.label,
134
157
  shared_run: this.sharedRun,
135
158
  }).filter(([, value]) => !!value),
136
159
  );
@@ -148,10 +171,13 @@ class TestomatioPipe {
148
171
  maxContentLength: Infinity,
149
172
  maxBodyLength: Infinity,
150
173
  });
174
+
151
175
  this.runId = resp.data.uid;
152
176
  this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
153
177
  this.runPublicUrl = resp.data.public_url;
178
+
154
179
  if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
180
+
155
181
  this.store.runUrl = this.runUrl;
156
182
  this.store.runPublicUrl = this.runPublicUrl;
157
183
  this.store.runId = this.runId;
@@ -168,50 +194,47 @@ class TestomatioPipe {
168
194
  debug('"createRun" function finished');
169
195
  }
170
196
 
171
- /**
172
- *
173
- * @param testData data
174
- * @returns
175
- */
176
197
  addTest(data) {
177
- debug('Adding test...');
178
198
  if (!this.isEnabled) return;
179
199
  if (!this.runId) return;
180
200
  data.api_key = this.apiKey;
181
201
  data.create = this.createNewTests;
182
202
  const json = JsonCycle.stringify(data);
183
203
 
184
- return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, {
185
- maxContentLength: Infinity,
186
- maxBodyLength: Infinity,
187
- headers: {
188
- // Overwrite Axios's automatically set Content-Type
189
- 'Content-Type': 'application/json',
190
- },
191
- })
192
- .catch(err => {
193
- if (err.response) {
194
- if (err.response.status >= 400) {
195
- const responseData = err.response.data || { message: '' };
204
+ debug('Adding test', json);
205
+
206
+ return this.axios
207
+ .post(`/api/reporter/${this.runId}/testrun`, json, {
208
+ maxContentLength: Infinity,
209
+ maxBodyLength: Infinity,
210
+ headers: {
211
+ // Overwrite Axios's automatically set Content-Type
212
+ 'Content-Type': 'application/json',
213
+ },
214
+ })
215
+ .catch(err => {
216
+ if (err.response) {
217
+ if (err.response.status >= 400) {
218
+ const responseData = err.response.data || { message: '' };
219
+ console.log(
220
+ APP_PREFIX,
221
+ chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
222
+ chalk.grey(data?.title || ''),
223
+ );
224
+ if (err.response.data.message.includes('could not be matched')) {
225
+ this.hasUnmatchedTests = true;
226
+ }
227
+ return;
228
+ }
196
229
  console.log(
197
230
  APP_PREFIX,
198
- chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
199
- chalk.grey(data?.title || ''),
231
+ chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
232
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
200
233
  );
201
- if (err.response.data.message.includes('could not be matched')) {
202
- this.hasUnmatchedTests = true;
203
- }
204
- return;
234
+ } else {
235
+ console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
205
236
  }
206
- console.log(
207
- APP_PREFIX,
208
- chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
209
- `Report couldn't be processed: ${err?.response?.data?.message}`,
210
- );
211
- } else {
212
- console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
213
- }
214
- });
237
+ });
215
238
  }
216
239
 
217
240
  /**
@@ -0,0 +1,46 @@
1
+ const { services } = require('./services');
2
+ const { initPlaywrightForStorage } = require('./adapter/playwright');
3
+
4
+ if (process.env.PLAYWRIGHT_TEST_BASE_URL) {
5
+ initPlaywrightForStorage();
6
+ }
7
+
8
+ /**
9
+ * Stores path to file as artifact and uploads it to the S3 storage
10
+ * @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
11
+ */
12
+ function saveArtifact(data, context = null) {
13
+ if (!data) return;
14
+ services.artifacts.put(data, context);
15
+ }
16
+
17
+ /**
18
+ * Attach log message(s) to the test report
19
+ * @param {...any} args
20
+ */
21
+ function logMessage(...args) {
22
+ services.logger._templateLiteralLog(...args);
23
+ }
24
+
25
+ /**
26
+ * Similar to "log" function but marks message in report as a step
27
+ * @param {*} message
28
+ */
29
+ function addStep(message) {
30
+ services.logger.step(message);
31
+ }
32
+
33
+ /**
34
+ * Add key-value pair(s) to the test report
35
+ * @param {*} keyValue
36
+ */
37
+ function setKeyValue(keyValue) {
38
+ services.keyValues.put(keyValue);
39
+ }
40
+
41
+ module.exports = {
42
+ artifact: saveArtifact,
43
+ log: logMessage,
44
+ step: addStep,
45
+ keyValue: setKeyValue,
46
+ };
package/lib/reporter.js CHANGED
@@ -1,18 +1,19 @@
1
- const logger = require('./logger');
2
1
  const TestomatClient = require('./client');
3
2
  const TRConstants = require('./constants');
4
- const TRArtifacts = require('./_ArtifactStorageOld');
3
+ const { services } = require('./services');
5
4
 
6
- const log = logger.templateLiteralLog.bind(logger);
7
- const step = logger.step.bind(logger);
5
+ const reporterFunctions = require('./reporter-functions');
8
6
 
9
7
  module.exports = {
10
- logger,
11
- testomatioLogger: logger,
12
- log,
13
- step,
8
+ // TODO: deprecate in future; use log or testomat.log
9
+ testomatioLogger: services.logger,
10
+
11
+ artifact: reporterFunctions.artifact,
12
+ log: reporterFunctions.log,
13
+ logger: services.logger,
14
+ meta: reporterFunctions.keyValue,
15
+ step: reporterFunctions.step,
16
+
14
17
  TestomatClient,
15
18
  TRConstants,
16
- TRArtifacts,
17
- addArtifact: TRArtifacts.artifact,
18
19
  };
@@ -0,0 +1,57 @@
1
+ const debug = require('debug')('@testomatio/reporter:services-artifacts');
2
+ const { dataStorage } = require('../data-storage');
3
+
4
+ /**
5
+ * Artifact storage is supposed to store file paths
6
+ */
7
+ class ArtifactStorage {
8
+ static #instance;
9
+
10
+ /**
11
+ * Singleton
12
+ * @returns {ArtifactStorage}
13
+ */
14
+ static getInstance() {
15
+ if (!this.#instance) {
16
+ this.#instance = new ArtifactStorage();
17
+ }
18
+ return this.#instance;
19
+ }
20
+
21
+ /**
22
+ * Stores path to file as artifact and uploads it to the S3 storage
23
+ * @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
24
+ * @param {*} context testId or test title
25
+ */
26
+ put(data, context = null) {
27
+ if (!data) return;
28
+ debug('Save artifact:', data);
29
+ dataStorage.putData('artifact', data, context);
30
+ }
31
+
32
+ /**
33
+ * Returns list of artifacts to upload
34
+ * @param {*} context testId or test context from test runner
35
+ * @returns {(string | {path: string, type: string, name: string})[]}
36
+ */
37
+ get(context) {
38
+ let artifacts = dataStorage.getData('artifact', context);
39
+ if (!artifacts || !artifacts.length) return [];
40
+
41
+ artifacts = artifacts.map(artifactData => {
42
+ // artifact could be an object ({type, path, name} props) or string (just path)
43
+ let artifact;
44
+ try {
45
+ artifact = JSON.parse(artifactData);
46
+ } catch (e) {
47
+ artifact = artifactData;
48
+ }
49
+ return artifact;
50
+ });
51
+ artifacts = artifacts.filter(artifact => !!artifact);
52
+ debug(`Artifacts for test ${context}:`, artifacts);
53
+ return artifacts.length ? artifacts : [];
54
+ }
55
+ }
56
+
57
+ module.exports.artifactStorage = ArtifactStorage.getInstance();
@@ -0,0 +1,13 @@
1
+ const { logger } = require('./logger');
2
+ const { artifactStorage } = require('./artifacts');
3
+ const { keyValueStorage } = require('./key-values');
4
+ const { dataStorage } = require('../data-storage');
5
+
6
+ module.exports.services = {
7
+ logger,
8
+ artifacts: artifactStorage,
9
+ keyValues: keyValueStorage,
10
+ setContext: context => {
11
+ dataStorage.setContext(context);
12
+ },
13
+ };
@@ -0,0 +1,58 @@
1
+ const debug = require('debug')('@testomatio/reporter:services-key-value');
2
+ const { dataStorage } = require('../data-storage');
3
+
4
+ class KeyValueStorage {
5
+ static #instance;
6
+
7
+ /**
8
+ *
9
+ * @returns {KeyValueStorage}
10
+ */
11
+ static getInstance() {
12
+ if (!this.#instance) {
13
+ this.#instance = new KeyValueStorage();
14
+ }
15
+ return this.#instance;
16
+ }
17
+
18
+ /**
19
+ * Stores key-value pair and passes it to reporter
20
+ * @param {{key: string}} keyValue - key-value pair(s) as object
21
+ * @param {*} context - full test title
22
+ */
23
+ put(keyValue, context = null) {
24
+ if (!keyValue) return;
25
+ dataStorage.putData('keyvalue', keyValue, context);
26
+ }
27
+
28
+ #isKeyValueObject(smth) {
29
+ return smth && typeof smth === 'object' && !Array.isArray(smth) && smth !== null;
30
+ }
31
+
32
+ /**
33
+ * Returns key-values pairs for the test as object
34
+ * @param {*} context testId or test context from test runner
35
+ * @returns {{[key: string]: string} | {}} key-values pairs as object, e.g. {priority: 'high', browser: 'chrome'}
36
+ */
37
+ get(context = null) {
38
+ const keyValuesList = dataStorage.getData('keyvalue', context);
39
+ if (!keyValuesList || !keyValuesList?.length) return {};
40
+
41
+ const keyValues = {};
42
+ for (const keyValue of keyValuesList) {
43
+ if (this.#isKeyValueObject(keyValue)) {
44
+ Object.assign(keyValues, keyValue);
45
+ } else if (typeof keyValue === 'string') {
46
+ try {
47
+ Object.assign(keyValues, JSON.parse(keyValue));
48
+ } catch (e) {
49
+ debug(`Error parsing key-values for test ${context}`, keyValue);
50
+ }
51
+ }
52
+ }
53
+
54
+ return keyValues;
55
+ }
56
+ }
57
+
58
+ module.exports.keyValueStorage = KeyValueStorage.getInstance();
@@ -1,6 +1,6 @@
1
1
  const chalk = require('chalk');
2
- const debug = require('debug')('@testomatio/reporter:logger');
3
- const { DataStorage } = require('./dataStorage');
2
+ const debug = require('debug')('@testomatio/reporter:services-logger');
3
+ const { dataStorage } = require('../data-storage');
4
4
 
5
5
  const LOG_METHODS = ['assert', 'debug', 'error', 'info', 'log', 'trace', 'warn'];
6
6
  const LEVELS = {
@@ -26,32 +26,25 @@ class Logger {
26
26
  // set default logger to be used in log, warn, error, etc methods
27
27
  #originalUserLogger = { ...console };
28
28
 
29
- #dataStorage = new DataStorage('log');
29
+ #userLoggerWithOverridenMethods;
30
30
 
31
- logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
32
-
33
- constructor() {
34
- // intercept console by default
35
- this.intercept(console);
31
+ static #instance;
36
32
 
37
- // singleton
38
- if (!Logger.instance) {
39
- Logger.instance = this;
33
+ /**
34
+ *
35
+ * @returns {Logger}
36
+ */
37
+ static getInstance() {
38
+ if (!this.#instance) {
39
+ this.#instance = new Logger();
40
40
  }
41
+ return this.#instance;
42
+ }
41
43
 
42
- // add beforeEach hook for mocha. it does not override existing hook, just add new one
43
- try {
44
- // @ts-ignore
45
- if (!beforeEach) return;
46
- // @ts-ignore-next-line
47
- beforeEach(function () {
48
- if (this.currentTest?.__mocha_id__) {
49
- global.testTitle = this.currentTest.fullTitle();
50
- }
51
- });
52
- } catch (e) {
53
- // ignore
54
- }
44
+ logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
45
+
46
+ constructor() {
47
+ if (!dataStorage.isFileStorage || process.env.TESTOMATIO_INTERCEPT_CONSOLE_LOGS) this.intercept(console);
55
48
  }
56
49
 
57
50
  /**
@@ -61,9 +54,6 @@ class Logger {
61
54
  * @param {...any} values
62
55
  */
63
56
  step(strings, ...values) {
64
- // get testId for mocha
65
- const context = global.testTitle ?? null;
66
-
67
57
  let logs = '';
68
58
  for (let i = 0; i < strings.length; i++) {
69
59
  logs += strings[i];
@@ -72,17 +62,18 @@ class Logger {
72
62
  }
73
63
  }
74
64
  logs = chalk.blue(`> ${logs}`);
75
- this.#dataStorage.putData(logs, context);
65
+ dataStorage.putData('log', logs);
76
66
  }
77
67
 
78
68
  /**
79
69
  *
80
- * @param {*} context testId or test context from test runner
81
- * @returns
70
+ * @param {string} context testId or test context from test runner
71
+ * @returns {string[]}
82
72
  */
83
73
  getLogs(context) {
84
- const logs = this.#dataStorage.getData(context);
85
- return logs || '';
74
+ const logs = dataStorage.getData('log', context);
75
+ if (!logs) return [];
76
+ return logs;
86
77
  }
87
78
 
88
79
  #stringifyLogs(...args) {
@@ -114,14 +105,9 @@ class Logger {
114
105
  * 2. Standard: log(`text ${someVar}`)
115
106
  * 3. Standard with multiple arguments: log('text', someVar)
116
107
  */
117
- templateLiteralLog(strings, ...args) {
108
+ _templateLiteralLog(strings, ...args) {
118
109
  if (Array.isArray(strings)) strings = strings.filter(item => item !== '').map(item => item.trim());
119
110
  if (Array.isArray(args)) args = args.filter(item => item !== '');
120
- // entity which is used to define testId
121
- let context = null;
122
-
123
- // get testId for mocha
124
- context = global.testTitle ?? null;
125
111
 
126
112
  let logs;
127
113
  // this block means tagged template is used (syntax like $`text ${someVar}`)
@@ -146,7 +132,7 @@ class Logger {
146
132
  logs = this.#stringifyLogs(strings, ...args);
147
133
  }
148
134
  this.#originalUserLogger.log(logs);
149
- this.#dataStorage.putData(logs, context);
135
+ dataStorage.putData('log', logs);
150
136
  }
151
137
 
152
138
  /**
@@ -158,18 +144,17 @@ class Logger {
158
144
  #logWrapper(argsArray, level) {
159
145
  if (!argsArray.length) return;
160
146
 
161
- // entity which is used to define testId
162
- let context = null;
163
-
164
- // get context for mocha
165
- context = global.testTitle ?? null;
166
-
167
147
  const severity = LEVELS[level].severity;
168
148
  if (severity < LEVELS[this.logLevel]?.severity) return;
169
149
 
170
150
  const logs = this.#stringifyLogs(...argsArray);
151
+
171
152
  const colorizedLogs = chalk[LEVELS[level].color](logs);
172
- this.#dataStorage.putData(colorizedLogs, context);
153
+ // do not attach logs from testomatio reporter itself
154
+ if (!logs.includes('[TESTOMATIO]')) {
155
+ dataStorage.putData('log', colorizedLogs);
156
+ }
157
+
173
158
  try {
174
159
  // level.toLowerCase() represents method name (log, warn, error, etc)
175
160
  this.#originalUserLogger[level.toLowerCase()](colorizedLogs);
@@ -212,16 +197,18 @@ class Logger {
212
197
  * @param {*} userLogger
213
198
  */
214
199
  intercept(userLogger) {
215
- /* prevent multiple console interceptions (cause of infinite loop)
216
- actual only for "console", because its used as default output and is intercepted by default */
217
- const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
218
- if (isUserLoggerConsole && process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED) {
219
- debug(`Try to intercept console, but it is already intercepted`);
220
- return;
200
+ // STEP 1: reset previously intercepted logger methods to original
201
+ if (this.#userLoggerWithOverridenMethods) {
202
+ for (const method of LOG_METHODS) {
203
+ this.#userLoggerWithOverridenMethods[method] = this.#originalUserLogger[method];
204
+ }
221
205
  }
222
206
 
223
- process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED = 'true';
224
- debug(isUserLoggerConsole ? 'console intercepted' : 'User logger intercepted');
207
+ // STEP 2: intercept new logger
208
+ this.#originalUserLogger = { ...userLogger };
209
+
210
+ const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
211
+ debug(`Intercepting ${isUserLoggerConsole ? 'console' : 'some user'} logger}`);
225
212
 
226
213
  // override user logger (any, e.g. console) methods to intercept log messages
227
214
  for (const method of LOG_METHODS) {
@@ -234,10 +221,7 @@ class Logger {
234
221
  userLogger[method] = (...args) => this[method](...args);
235
222
  }
236
223
 
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
- */
224
+ this.#userLoggerWithOverridenMethods = userLogger;
241
225
 
242
226
  /*
243
227
  Initial idea was to intercept any logger (tracer, pino, etc),
@@ -247,11 +231,22 @@ class Logger {
247
231
  Also its difficult to understand (actually did not find the way to do it) if logger was already intercepted or not.
248
232
  Thus, decided to intercept only console by default and provide output by default console.
249
233
  It means, if user uses his own logger, its messages will be intercepted,
250
- but the output will be provided by console.
234
+ but the output will be always provided by console.
251
235
  TODO: try to implement the providing output to terminal by user logger
252
236
  */
253
237
  }
254
238
 
239
+ stopInterception() {
240
+ debug('Stop ntercepting logs');
241
+
242
+ // restore original user logger
243
+ if (this.#userLoggerWithOverridenMethods) {
244
+ for (const method of LOG_METHODS) {
245
+ this.#userLoggerWithOverridenMethods[method] = this.#originalUserLogger[method];
246
+ }
247
+ }
248
+ }
249
+
255
250
  /**
256
251
  * Allows to configure logger. Make sure you do it before the logger usage in your code.
257
252
  *
@@ -267,12 +262,7 @@ class Logger {
267
262
  }
268
263
  }
269
264
 
270
- Logger.instance = null;
271
-
272
- // module.exports.Logger = Logger;
273
- const logger = new Logger();
274
-
275
- module.exports = logger;
265
+ module.exports.logger = Logger.getInstance();
276
266
 
277
267
  // TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
278
268
  // upd: did not face such loggers, but still could be useful
@@ -289,7 +279,6 @@ I found the only ability to get any logs using .task('log', 'message') (this is
289
279
  and then intercept it with:
290
280
  on('task', {
291
281
  log (message) {
292
- console.log(message)
293
282
  return null
294
283
  }
295
284
  })
@@ -317,3 +306,6 @@ Finally, in the test it will look like:
317
306
 
318
307
  Parallelization in Cypress is only available if using Cypress Dashboard??
319
308
  */
309
+
310
+ // TODO: add time to logs
311
+ // TODO: add logger name to logs?
@@ -141,7 +141,15 @@ const fetchSourceCode = (contents, opts = {}) => {
141
141
  // remove special chars from title
142
142
  if (!lineIndex && opts.title) {
143
143
  const title = opts.title.replace(/[([@].*/g, '');
144
- lineIndex = lines.findIndex(l => l.includes(title));
144
+
145
+ if (opts.lang === 'java') {
146
+ lineIndex = lines.findIndex(l => l.includes(`test${title}`));
147
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
148
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
149
+ if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
150
+ } else {
151
+ lineIndex = lines.findIndex(l => l.includes(title));
152
+ }
145
153
  }
146
154
 
147
155
  if (opts.prepend) {
@@ -290,6 +298,23 @@ function removeColorCodes(input) {
290
298
  return input.replace(/\x1b\[[0-9;]*m/g, '');
291
299
  }
292
300
 
301
+ const testRunnerHelper = {
302
+ // for Jest
303
+ getNameOfCurrentlyRunningTest: () => {
304
+ if (global.testomatioTestTitle) return global.testomatioTestTitle;
305
+
306
+ if (!process.env.JEST_WORKER_ID) return null;
307
+ try {
308
+ // TODO: expect?.getState()?.testPath + ' ' + expect?.getState()?.currentTestName
309
+ // @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
310
+ // eslint-disable-next-line no-undef
311
+ return expect?.getState()?.currentTestName;
312
+ } catch (e) {
313
+ return null;
314
+ }
315
+ },
316
+ };
317
+
293
318
  module.exports = {
294
319
  isSameTest,
295
320
  fetchSourceCode,
@@ -306,5 +331,6 @@ module.exports = {
306
331
  parseSuite,
307
332
  humanize,
308
333
  removeColorCodes,
309
- foundedTestLog
334
+ foundedTestLog,
335
+ testRunnerHelper,
310
336
  };