@testomatio/reporter 1.3.0-beta → 1.3.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.
Files changed (46) hide show
  1. package/README.md +61 -54
  2. package/lib/adapter/codecept.js +96 -35
  3. package/lib/adapter/cucumber/current.js +18 -11
  4. package/lib/adapter/cucumber/legacy.js +6 -5
  5. package/lib/adapter/cucumber.js +2 -2
  6. package/lib/adapter/cypress-plugin/index.js +52 -25
  7. package/lib/adapter/jasmine.js +1 -1
  8. package/lib/adapter/jest.js +49 -16
  9. package/lib/adapter/mocha.js +103 -51
  10. package/lib/adapter/playwright.js +95 -33
  11. package/lib/adapter/webdriver.js +1 -1
  12. package/lib/bin/reportXml.js +22 -16
  13. package/lib/bin/startTest.js +27 -6
  14. package/lib/client.js +180 -61
  15. package/lib/config.js +34 -0
  16. package/lib/constants.js +32 -7
  17. package/lib/data-storage.js +203 -0
  18. package/lib/fileUploader.js +135 -54
  19. package/lib/junit-adapter/adapter.js +0 -2
  20. package/lib/junit-adapter/csharp.js +3 -4
  21. package/lib/junit-adapter/index.js +3 -3
  22. package/lib/junit-adapter/java.js +35 -17
  23. package/lib/junit-adapter/javascript.js +1 -2
  24. package/lib/junit-adapter/python.js +12 -14
  25. package/lib/junit-adapter/ruby.js +1 -2
  26. package/lib/pipe/csv.js +5 -3
  27. package/lib/pipe/github.js +27 -39
  28. package/lib/pipe/gitlab.js +22 -26
  29. package/lib/pipe/html.js +354 -0
  30. package/lib/pipe/index.js +3 -1
  31. package/lib/pipe/testomatio.js +259 -53
  32. package/lib/reporter-functions.js +46 -0
  33. package/lib/reporter.js +11 -9
  34. package/lib/services/artifacts.js +57 -0
  35. package/lib/services/index.js +13 -0
  36. package/lib/services/key-values.js +58 -0
  37. package/lib/services/logger.js +311 -0
  38. package/lib/template/testomatio.hbs +1236 -0
  39. package/lib/utils/pipe_utils.js +129 -0
  40. package/lib/{util.js → utils/utils.js} +144 -14
  41. package/lib/xmlReader.js +211 -122
  42. package/package.json +17 -8
  43. package/lib/_ArtifactStorageOld.js +0 -142
  44. package/lib/artifactStorage.js +0 -25
  45. package/lib/dataStorage.js +0 -244
  46. package/lib/logger.js +0 -294
@@ -1,142 +0,0 @@
1
- const debug = require('debug')('@testomatio/reporter:storage');
2
- const { join, resolve } = require('path');
3
- const fs = require('fs');
4
- const os = require('os');
5
- const uuid = require('uuid');
6
- const { TESTOMAT_ARTIFACT_SUFFIX } = require('./constants');
7
- const { specificTestInfo } = require('./util');
8
-
9
- class ArtifactStorage {
10
-
11
- constructor(params) {
12
- this.isFile = false;
13
- this.isMemory = true;
14
- this.storage = params?.toFile;
15
-
16
- if (this.storage) {
17
- // this is fine to use when you start saving artifacts;
18
- // but when you need to retrieve them, you will not know if there is "isFile" param,
19
- // because you need to create a new instance of ArtifactStorage inside client
20
- // so the solution is make it opposite to isMemory (which will be retrieved from global)
21
- this.isFile = true;
22
- this.isMemory = false;
23
- this.tmpDirFullpath = this.createTestomatTmpDir(ArtifactStorage._tmpPrefix);
24
- debug('SAVE to tmp folder mode enabled!');
25
- }
26
- }
27
-
28
- static async storeToFile(tmpDirName, artifact, testSuffix = "test") {
29
- // this assumes to use multiple files for each test. do we really want this?
30
- const suffix = TESTOMAT_ARTIFACT_SUFFIX + uuid.v4() + testSuffix;
31
- const dirpath = join(os.tmpdir(), tmpDirName);
32
- // why json?
33
- const filepath = resolve(dirpath, `${suffix}.json`);
34
-
35
- return fs.promises.appendFile(filepath, JSON.stringify(artifact));
36
- }
37
-
38
- static async artifact(artifact, context) {
39
- // TODO: mode for different pre-hooks. As variant - "all-tests" || "only_fail"
40
- // const mode = process.env.TESTOMAT_ARTIFACTS || "only_fail";
41
-
42
- // you save the artifact without specifying its source - test id
43
- if (Array.isArray(global.testomatioArtifacts)) {
44
- debug("Saving artifacts to global storage");
45
-
46
- global.testomatioArtifacts.push(artifact);
47
- }
48
-
49
- if (global?.testomatioArtifacts === undefined) {
50
- const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
51
-
52
- const testSuffix = specificTestInfo(context.test);
53
-
54
- if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
55
- debug("Saving artifacts to memory tmp folder");
56
-
57
- return ArtifactStorage.storeToFile(tmpDirNames[tmpDirNames.length - 1], artifact, testSuffix);
58
- }
59
- }
60
- }
61
-
62
- async artifactByTestName(test) {
63
- const list = [];
64
-
65
- if (this.isFile && this.tmpDirFullpath) {
66
- const files = fs.readdirSync(this.tmpDirFullpath);
67
-
68
- for (const file of files) {
69
- if (file.includes(test)) {
70
- const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
71
-
72
- list.push(JSON.parse(buff.toString()));
73
- }
74
- }
75
- }
76
-
77
- return list;
78
- }
79
-
80
- // returns all the content from all files; do we really need it?
81
- async tmpContents() {
82
- const contents = [];
83
-
84
- if (this.isFile && this.tmpDirFullpath) {
85
- const files = fs.readdirSync(this.tmpDirFullpath);
86
-
87
- for (const file of files) {
88
- const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
89
- const content = buff.toString();
90
-
91
- contents.push(content);
92
- }
93
- }
94
-
95
- return contents;
96
- }
97
-
98
- createTestomatTmpDir(clientPrefix) {
99
- return fs.mkdtempSync(join(os.tmpdir(), clientPrefix));
100
- }
101
-
102
- clearTmpDirByName(name) {
103
- const tmpDirPath = join(os.tmpdir(), name);
104
-
105
- if (fs.existsSync(tmpDirPath)) {
106
- fs.rmSync(tmpDirPath, { recursive: true });
107
- debug(` Testomat tmpDir = ${tmpDirPath} was deleted successfully!`);
108
-
109
-
110
- }
111
- }
112
-
113
- // no need to use multiple dirs; we can implement it later if required
114
- static tmpTestomatDirNames() {
115
- const subname = ArtifactStorage._tmpPrefix || "tsmt_reporter";
116
-
117
- return fs.readdirSync(os.tmpdir(), { withFileTypes: true })
118
- .filter((item) => item.isDirectory())
119
- .map((item) => item.name)
120
- .filter((name) => name.includes(subname));
121
- }
122
-
123
- cleanup() {
124
- // when you do a cleanup, I know nothing about isFile param
125
- if (this.isFile && this.tmpDirFullpath) {
126
- const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
127
-
128
- if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
129
- for (const name of tmpDirNames) {
130
- this.clearTmpDirByName(name);
131
- }
132
- }
133
- }
134
- else {
135
- debug("The tmp folder has not been created! Nothing to delete");
136
- }
137
- }
138
- }
139
-
140
- ArtifactStorage._tmpPrefix = "tsmt_reporter";
141
-
142
- module.exports = ArtifactStorage;
@@ -1,25 +0,0 @@
1
- // const debug = require('debug')('@testomatio/reporter:logger');
2
- const { DataStorage } = require('./dataStorage');
3
-
4
- class ArtifactStorage {
5
- constructor(params = {}) {
6
- this.dataStorage = new DataStorage('artifact', params);
7
-
8
- // singleton
9
- if (!ArtifactStorage.instance) {
10
- ArtifactStorage.instance = this;
11
- }
12
- }
13
-
14
- save(data, context = null) {
15
- this.dataStorage.putData(data, context);
16
- }
17
-
18
- get(context) {
19
- this.dataStorage.getData(context);
20
- }
21
- }
22
-
23
- ArtifactStorage.instance = null;
24
-
25
- module.exports = new ArtifactStorage();
@@ -1,244 +0,0 @@
1
- const debug = require('debug')('@testomatio/reporter:storage');
2
- const fs = require('fs');
3
- const path = require('path');
4
- const os = require('os');
5
- const { join } = require('path');
6
- const JestReporter = require('./adapter/jest');
7
- const { TESTOMAT_TMP_STORAGE } = require('./constants');
8
- const { fileSystem } = require('./util');
9
- const getTestIdFromTestTitle = require('./util').parseTest;
10
-
11
- class DataStorage {
12
- /**
13
- * Creates data storage instance for specific data type.
14
- * Stores data to global variable or to file depending on what is applicable for current test runner.
15
- * dataType: 'log' | 'artifact' | ...
16
- * @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
17
- */
18
- constructor(dataType) {
19
- // if (!dataType) throw new Error('Data type is required when creating data storage');
20
- this.dataType = dataType || 'data';
21
- this.dataDirName = this.dataType;
22
-
23
- this._refreshStorageType();
24
-
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);
28
-
29
- debug(`Data storage mode: ${this.isFileStorage ? 'file' : 'memory'}`);
30
- }
31
-
32
- /**
33
- * Try to define the running environment. Not 100% reliable and used as additional check
34
- * @returns jest | mocha | ...
35
- */
36
- getRunningEnviroment() {
37
- // jest
38
- if (process.env.JEST_WORKER_ID) return 'jest';
39
-
40
- // mocha
41
- try {
42
- // @ts-expect-error mocha is defined only in mocha environment
43
- if (typeof mocha !== 'undefined') return 'mocha';
44
- } catch (e) {
45
- // ignore
46
- }
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;
59
- }
60
-
61
- /**
62
- * Puts any data to storage (file or global variable)
63
- * @param {*} data anything you want to store
64
- * @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
65
- * @returns
66
- */
67
- putData(data, context = null) {
68
- this._refreshStorageType();
69
-
70
- let testId = this._tryToRetrieveTestId(context) || null;
71
-
72
- if (this.runningEnvironment === 'codeceptjs') testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
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;
80
-
81
- // if testId is not provided, data is be saved to `{dataType}_other` file;
82
- if (!testId) {
83
- debug(`No test id provided for ${this.dataType} data: ${data}`);
84
- return;
85
- }
86
-
87
- if (this.isFileStorage) {
88
- this._putDataToFile(data, testId);
89
- } else {
90
- this._putDataToGlobalVar(data, testId);
91
- }
92
- }
93
-
94
- /**
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).
98
- *
99
- * Defining the execution environment is not guaranteed! Is used only as additional check.
100
- * @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
101
- * @returns
102
- */
103
- getData(context) {
104
- this._refreshStorageType();
105
-
106
- let testId = null;
107
- if (typeof context === 'string') {
108
- testId = context;
109
- } else {
110
- // TODO: derive testId from context
111
- // testId = context...
112
- }
113
-
114
- if (!testId) {
115
- debug(`Cannot get test id from passed context:\n}`, context);
116
- return null;
117
- }
118
-
119
- let testData = null;
120
-
121
- if (global?.testomatioDataStore) {
122
- testData = this._getDataFromGlobalVar(testId);
123
- if (testData) return testData;
124
- }
125
-
126
- if (this.isFileStorage || !testData) {
127
- testData = this._getDataFromFile(testId);
128
- if (testData) return testData;
129
- }
130
-
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
-
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
-
181
- _getDataFromGlobalVar(testId) {
182
- try {
183
- if (global?.testomatioDataStore[this.dataDirName]) {
184
- const testData = global.testomatioDataStore[this.dataDirName][testId];
185
- debug(`Data for test id ${testId}:\n${testData}`);
186
- return testData;
187
- }
188
- debug(`No ${this.dataType} data for test id ${testId} in <global> storage`);
189
- return null;
190
- } catch (e) {
191
- // there could be no data, ignore
192
- }
193
- }
194
-
195
- _getDataFromFile(testId) {
196
- try {
197
- const filepath = join(this.dataDirPath, `${this.dataType}_${testId}`);
198
- if (fs.existsSync(filepath)) {
199
- const testData = fs.readFileSync(filepath, 'utf-8');
200
- debug(`Data for test id ${testId}:\n${testData}`);
201
- return testData;
202
- }
203
- debug(`No ${this.dataType} data for test id ${testId} in <file> storage`);
204
- return null;
205
- } catch (e) {
206
- // there could be no data, ignore
207
- }
208
- return null;
209
- }
210
-
211
- _putDataToGlobalVar(data, testId) {
212
- debug('Saving data to global variable for test', testId, ':\n', data, '\n');
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);
217
- }
218
-
219
- _putDataToFile(data, testId) {
220
- if (typeof data !== 'string') data = JSON.stringify(data);
221
- const filename = `${this.dataType}_${testId}`;
222
- const filepath = join(this.dataDirPath, filename);
223
- debug('Saving data to file for test', testId, 'to', filepath, ':\n', data, '\n');
224
-
225
- // TODO: handle multiple invocations of JSON.stringify.
226
- // UPD: not actual because decided not to use it - it created extra wrapping quotes "" and // (escaped slashes)
227
- fs.appendFileSync(filepath, data + os.EOL, 'utf-8');
228
- }
229
- }
230
-
231
- module.exports.DataStorage = DataStorage;
232
-
233
- // TODO: consider using fs promises instead of writeSync/appendFileSync to
234
- // prevent blocking and improve performance (probably queue usage will be required)
235
-
236
- // TODO: rewrite client - everything regarding storing artifacts
237
- // TODO: try to define adapter inside client
238
- // TODO: use .env
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 DELETED
@@ -1,294 +0,0 @@
1
- const chalk = require('chalk');
2
- const debug = require('debug')('@testomatio/reporter:logger');
3
- const { DataStorage } = require('./dataStorage');
4
-
5
- const LOG_METHODS = ['assert', 'debug', 'error', 'info', 'log', 'trace', 'warn'];
6
- const LEVELS = {
7
- ALL: { severity: 1, color: '' },
8
- VERBOSE: { severity: 3, color: 'grey' },
9
- TRACE: { severity: 5, color: 'grey' },
10
- DEBUG: { severity: 7, color: 'cyan' },
11
- INFO: { severity: 9, color: 'black' },
12
- LOG: { severity: 11, color: 'black' },
13
- WARN: { severity: 13, color: 'yellow' },
14
- ERROR: { severity: 15, color: 'red' },
15
- };
16
-
17
- /**
18
- * Logger allows to:
19
- * 1. Intercept logs from user logger (console.log, etc) and store them.
20
- * 2. Output logs to console (actually, logger functionality).
21
- * 3. Varied syntax.
22
- */
23
- class Logger {
24
- // _originalUserLogger used to output logs to console by the user logger
25
- // _loggerToIntercept intercepted and reassigned immediately when added
26
-
27
- constructor() {
28
- // set default logger to be used in log, warn, error, etc methods
29
- this._originalUserLogger = { ...console };
30
-
31
- this.dataStorage = new DataStorage('log');
32
- this.logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
33
-
34
- // commented because prefer to use "intercept" method
35
- // if (params?.logger) this._loggerToIntercept = params.logger;
36
-
37
- // intercept console by default
38
- this.intercept(console);
39
-
40
- // singleton
41
- if (!Logger.instance) {
42
- Logger.instance = this;
43
- }
44
-
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
- };
55
- }
56
-
57
- /**
58
- * Allows you to define a step inside a test. Step name is attached to the report and
59
- * helps to understand the test flow.
60
- * @param {*} strings
61
- * @param {...any} values
62
- */
63
- step(strings, ...values) {
64
- let logs = '';
65
- for (let i = 0; i < strings.length; i++) {
66
- logs += strings[i];
67
- if (i < values.length) {
68
- logs += values[i];
69
- }
70
- }
71
- logs = chalk.blue(`> ${logs}`);
72
- this.dataStorage.putData(logs);
73
- }
74
-
75
- /**
76
- *
77
- * @param {*} context testId or test context from test runner
78
- * @returns
79
- */
80
- getLogs(context) {
81
- const logs = this.dataStorage.getData(context);
82
- return logs || '';
83
- }
84
-
85
- _stringifyLogs(...args) {
86
- const logs = [];
87
- // stringify everything except strings
88
- for (const arg of args) {
89
- // ignore empty strings
90
- if (arg === '') continue;
91
- if (typeof arg === 'string') {
92
- logs.push(arg);
93
- } else if (Array.isArray(arg)) {
94
- logs.push(arg.join(' '));
95
- } else {
96
- try {
97
- // eslint-disable-next-line no-unused-expressions
98
- this.prettyObjects ? logs.push(JSON.stringify(arg, null, 2)) : logs.push(JSON.stringify(arg));
99
- } catch (e) {
100
- debug('Error while stringify object', e);
101
- logs.push(arg);
102
- }
103
- }
104
- }
105
- return logs.join(' ');
106
- }
107
-
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
- }
123
-
124
- // filter empty strings
125
- strings = strings.filter(item => item !== '');
126
-
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);
148
- }
149
- this._originalUserLogger.log(logs);
150
- this.dataStorage.putData(logs, context);
151
- }
152
-
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
- }
170
-
171
- const severity = LEVELS[level].severity;
172
- if (severity < LEVELS[this.logLevel]?.severity) return;
173
-
174
- const logs = this._stringifyLogs(...argsArray);
175
- const colorizedLogs = chalk[LEVELS[level].color](logs);
176
- this.dataStorage.putData(colorizedLogs, context);
177
- try {
178
- // level.toLowerCase() represents method name (log, warn, error, etc)
179
- this._originalUserLogger[level.toLowerCase()](colorizedLogs);
180
- } catch (e) {
181
- // method could be unexisting, ignore error
182
- }
183
- }
184
-
185
- assert(...args) {
186
- // sometimes user invokes logger without any arguments passed
187
- if (!args.length) return;
188
-
189
- const level = 'ERROR';
190
- const severity = LEVELS[level].severity;
191
- if (severity < LEVELS[this.logLevel]?.severity) return;
192
-
193
- const logs = this._stringifyLogs(...args);
194
- this.dataStorage.putData(logs);
195
- try {
196
- this._originalUserLogger.log(`Assertion result: `, ...args);
197
- } catch (e) {
198
- // method could be unexisting, ignore error
199
- }
200
- }
201
-
202
- debug(...args) {
203
- this._logWrapper(args, 'DEBUG');
204
- }
205
-
206
- error(...args) {
207
- this._logWrapper(args, 'ERROR');
208
- }
209
-
210
- info(...args) {
211
- this._logWrapper(args, 'INFO');
212
- }
213
-
214
- log(...args) {
215
- this._logWrapper(args, 'LOG');
216
- }
217
-
218
- trace(...args) {
219
- this._logWrapper(args, 'TRACE');
220
- }
221
-
222
- warn(...args) {
223
- this._logWrapper(args, 'WARN');
224
- }
225
-
226
- /**
227
- * Intercepts user logger messages.
228
- * When call this method, Logger start to control the user logger,
229
- * but almost nothing is changed for user regarding the console output (like log level set by user)
230
- * (until multiple loggers are intercepted,
231
- * in this case only the last intercepted logger will be used as user console output).
232
- * @param {*} userLogger
233
- */
234
- intercept(userLogger) {
235
- if (!userLogger) return;
236
-
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
- }
255
- this._loggerToIntercept = userLogger;
256
-
257
- /*
258
- override user logger (any, e.g. console) methods to intercept log messages
259
- this._loggerToIntercept = this; could be used, but decided to override only output methods
260
- */
261
- for (const method of LOG_METHODS) {
262
- /*
263
- decided to comment next code line because its better to create method even if it does not exist in user logger;
264
- on method invocation, we will store the data anyway and catch block will prevent potential errors
265
- */
266
- // if (!this._loggerToIntercept[method]) continue;
267
- this._loggerToIntercept[method] = (...args) => this[method](...args);
268
- }
269
- }
270
-
271
- /**
272
- * Allows to configure logger. Make sure you do it before the logger usage in your code.
273
- *
274
- * @param {Object} [config={}] - The configuration object.
275
- * @param {string} [config.logLevel] - The desired log level. Valid values are 'DEBUG', 'INFO', 'WARN', and 'ERROR'.
276
- * @param {boolean} [config.prettyObjects] - Specifies whether to enable pretty printing of objects.
277
- * @returns {void}
278
- */
279
- configure(config = {}) {
280
- if (!config) return;
281
- if (config.prettyObjects) this.prettyObjects = config.prettyObjects;
282
- if (config.logLevel) this.logLevel = config.logLevel.toUpperCase();
283
- }
284
- }
285
-
286
- Logger.instance = null;
287
-
288
- // module.exports.Logger = Logger;
289
- const logger = new Logger();
290
-
291
- module.exports = logger;
292
-
293
- // TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
294
- // upd: did not face such loggers, but still could be useful