@testomatio/reporter 1.1.0-beta → 1.1.0-beta-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.1.0-beta",
3
+ "version": "1.1.0-beta-2",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",
@@ -20,13 +20,14 @@
20
20
  "debug": "^4.3.4",
21
21
  "dotenv": "^16.0.1",
22
22
  "fast-xml-parser": "^4.0.8",
23
- "glob": "^8.0.3",
23
+ "glob": "^10.3",
24
24
  "has-flag": "^5.0.1",
25
25
  "humanize-duration": "^3.27.3",
26
26
  "is-valid-path": "^0.1.1",
27
27
  "json-cycle": "^1.3.0",
28
28
  "lodash.memoize": "^4.1.2",
29
29
  "lodash.merge": "^4.6.2",
30
+ "minimatch": "^9.0.3",
30
31
  "uuid": "^9.0.0"
31
32
  },
32
33
  "files": [
@@ -41,21 +42,22 @@
41
42
  "lint:fix": "eslint lib --fix",
42
43
  "test": "mocha tests/**",
43
44
  "init": "cd ./tests/adapter/examples/cucumber && npm i",
44
- "test:unit": "mocha tests",
45
45
  "test:adapter": "mocha './tests/adapter/index.test.js'",
46
46
  "test:pipes": "mocha './tests/pipes/*_test.js'",
47
47
  "test:adapter:jest:example": "jest './tests/adapter/examples/jest/index.test.js' --config='./tests/adapter/examples/jest/jest.config.js'",
48
48
  "test:adapter:mocha:example": "mocha './tests/adapter/examples/mocha/index.test.js' --config='./tests/adapter/examples/mocha/mocha.config.js'",
49
49
  "test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
50
50
  "test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
51
- "test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
51
+ "test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
52
+ "test:storage": "npx mocha ./tests-storage/**"
52
53
  },
53
54
  "devDependencies": {
54
- "@cucumber/cucumber": "^8.6.0",
55
+ "@cucumber/cucumber": "^9.3.0",
55
56
  "@redocly/cli": "^1.0.0-beta.125",
56
57
  "@wdio/reporter": "^7.16.13",
57
58
  "chai": "^4.3.6",
58
- "codeceptjs": "^3.2.3",
59
+ "codeceptjs": "latest",
60
+ "cucumber": "^6.0.7",
59
61
  "eslint": "^8.7.0",
60
62
  "eslint-config-airbnb-base": "^15.0.0",
61
63
  "eslint-config-prettier": "^8.3.0",
@@ -64,6 +66,7 @@
64
66
  "jest": "^27.4.7",
65
67
  "mocha": "^9.2.0",
66
68
  "mock-http-server": "^1.4.5",
69
+ "pino": "^8.15.0",
67
70
  "prettier": "2.5.1",
68
71
  "puppeteer": "^13.1.2"
69
72
  },
@@ -1,141 +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._tmpPrefix = "tsmt_reporter";
13
- this.isFile = false;
14
- this.isMemory = true;
15
- this.storage = params?.toFile;
16
-
17
- if (this.storage) {
18
- // this is fine to use when you start saving artifacts;
19
- // but when you need to retrieve them, you will not know if there is "isFile" param,
20
- // because you need to create a new instance of ArtifactStorage inside client
21
- // so the solution is make it opposite to isMemory (which will be retrieved from global)
22
- this.isFile = true;
23
- this.isMemory = false;
24
- this.tmpDirFullpath = this.createTestomatTmpDir(this._tmpPrefix);
25
- debug('SAVE to tmp folder mode enabled!');
26
- }
27
- }
28
-
29
- static async storeToFile(tmpDirName, artifact, testSuffix = "test") {
30
- // this assumes to use multiple files for each test. do we really want this?
31
- const suffix = TESTOMAT_ARTIFACT_SUFFIX + uuid.v4() + testSuffix;
32
- const dirpath = join(os.tmpdir(), tmpDirName);
33
- // why json?
34
- const filepath = resolve(dirpath, `${suffix}.json`);
35
-
36
- return fs.promises.appendFile(filepath, JSON.stringify(artifact));
37
- }
38
-
39
- static async artifact(artifact, context) {
40
- // TODO: mode for different pre-hooks. As variant - "all-tests" || "only_fail"
41
- // const mode = process.env.TESTOMAT_ARTIFACTS || "only_fail";
42
-
43
- // you save the artifact without specifying its source - test id
44
- if (Array.isArray(global.testomatioArtifacts)) {
45
- debug("Saving artifacts to global storage");
46
-
47
- global.testomatioArtifacts.push(artifact);
48
- }
49
-
50
- if (global?.testomatioArtifacts === undefined) {
51
- const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
52
-
53
- const testSuffix = specificTestInfo(context.test);
54
-
55
- if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
56
- debug("Saving artifacts to memory tmp folder");
57
-
58
- return ArtifactStorage.storeToFile(tmpDirNames[tmpDirNames.length - 1], artifact, testSuffix);
59
- }
60
- }
61
- }
62
-
63
- async artifactByTestName(test) {
64
- const list = [];
65
-
66
- if (this.isFile && this.tmpDirFullpath) {
67
- const files = fs.readdirSync(this.tmpDirFullpath);
68
-
69
- for (const file of files) {
70
- if (file.includes(test)) {
71
- const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
72
-
73
- list.push(JSON.parse(buff.toString()));
74
- }
75
- }
76
- }
77
-
78
- return list;
79
- }
80
-
81
- // returns all the content from all files; do we really need it?
82
- async tmpContents() {
83
- const contents = [];
84
-
85
- if (this.isFile && this.tmpDirFullpath) {
86
- const files = fs.readdirSync(this.tmpDirFullpath);
87
-
88
- for (const file of files) {
89
- const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
90
- const content = buff.toString();
91
-
92
- contents.push(content);
93
- }
94
- }
95
-
96
- return contents;
97
- }
98
-
99
- createTestomatTmpDir(clientPrefix) {
100
- return fs.mkdtempSync(join(os.tmpdir(), clientPrefix));
101
- }
102
-
103
- clearTmpDirByName(name) {
104
- const tmpDirPath = join(os.tmpdir(), name);
105
-
106
- if (fs.existsSync(tmpDirPath)) {
107
- fs.rmSync(tmpDirPath, { recursive: true });
108
- debug(` Testomat tmpDir = ${tmpDirPath} was deleted successfully!`);
109
-
110
-
111
- }
112
- }
113
-
114
- // no need to use multiple dirs; we can implement it later if required
115
- static tmpTestomatDirNames() {
116
- const subname = this._tmpPrefix || "tsmt_reporter";
117
-
118
- return fs.readdirSync(os.tmpdir(), { withFileTypes: true })
119
- .filter((item) => item.isDirectory())
120
- .map((item) => item.name)
121
- .filter((name) => name.includes(subname));
122
- }
123
-
124
- cleanup() {
125
- // when you do a cleanup, I know nothing about isFile param
126
- if (this.isFile && this.tmpDirFullpath) {
127
- const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
128
-
129
- if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
130
- for (const name of tmpDirNames) {
131
- this.clearTmpDirByName(name);
132
- }
133
- }
134
- }
135
- else {
136
- debug("The tmp folder has not been created! Nothing to delete");
137
- }
138
- }
139
- }
140
-
141
- 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,172 +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
-
10
- class DataStorage {
11
- /**
12
- * Creates data storage instance for specific data type.
13
- * Stores data to global variable or to file depending on what is applicable for current test runner.
14
- * dataType: 'log' | 'artifact' | ...
15
- * @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
16
- * @param {*} params
17
- */
18
- constructor(dataType, params = {}) {
19
- if (!dataType) throw new Error('Data type is required when creating data storage');
20
- this.dataType = dataType || 'data';
21
- this.dataDirName = `${dataType}s`;
22
- this.isFileStorage = params?.isFileStorage ?? !global.testomatioDataStore;
23
-
24
- /*
25
- FYI:
26
- If this storage instance is used within test runner, it works fine.
27
- But if, for example, we create storage instance inside the testomatio client (to get stored data),
28
- the environment could be different (not already a test runner env).
29
- Thus, checking environment is only reasonable when you put data to starage
30
- and potentially useless when get data from storage
31
- */
32
- const runningEnvironment = this.getRunningEnviroment();
33
- if (runningEnvironment === 'jest') this.isFileStorage = true;
34
-
35
- if (this.isFileStorage) {
36
- this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataDirName);
37
- fileSystem.createDir(this.dataDirPath);
38
- }
39
-
40
- debug(`Data storage mode: ${this.isFileStorage ? 'file' : 'memory'}`);
41
- }
42
-
43
- // TODO: implement
44
- getTestIdFromContext(context) { // eslint-disable-line
45
- // eslint-disable-line
46
- // return testId;
47
- }
48
-
49
- /**
50
- * Try to define the running environment. Not 100% reliable and used as additional check
51
- * @returns jest | mocha | ...
52
- */
53
- getRunningEnviroment() {
54
- if (process.env.JEST_WORKER_ID) return 'jest';
55
- // @ts-expect-error mocha could be undefined, its ok
56
- if (typeof mocha !== 'undefined') return 'mocha';
57
- return undefined;
58
- }
59
-
60
- /**
61
- * Puts any data to storage (file or global variable)
62
- * @param {*} data anything you want to store
63
- * @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
64
- * @returns
65
- */
66
- putData(data, context = null) {
67
- let testId = null;
68
- if (typeof context === 'string') {
69
- testId = context;
70
- } else {
71
- // TODO: derive testId from context
72
- // testId = context...
73
- }
74
-
75
- // try to get testId for Jest
76
- testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
77
-
78
- // if testId is not provided, data is be saved to `{dataType}_other` file;
79
- if (!testId) testId = 'other';
80
-
81
- if (this.isFileStorage) {
82
- this._putDataToFile(data, testId);
83
- } else {
84
- this._putDataToGlobalVar(data, testId);
85
- }
86
- }
87
-
88
- /**
89
- * Returns data, stored for specific testId (or data which was stored without test id specified)
90
- *
91
- * (Don't try to guess the execution environment (e.g. test runner) inside this method, it could be any)
92
- * @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
93
- * @returns
94
- */
95
- getData(context) {
96
- let testId = null;
97
- if (typeof context === 'string') {
98
- testId = context;
99
- } else {
100
- // TODO: derive testId from context
101
- // testId = context...
102
- }
103
-
104
- if (!testId) testId = 'other';
105
-
106
- if (this.isFileStorage) {
107
- return this._getDataFromFile(testId);
108
- }
109
- if (global?.testomatioDataStore) {
110
- return this._getDataFromGlobalVar(testId);
111
- }
112
- debug(`No ${this.dataType} data for test id ${testId} in both file and global variable`);
113
- return null;
114
- }
115
-
116
- _getDataFromGlobalVar(testId) {
117
- try {
118
- if (global?.testomatioDataStore[this.dataDirName]) {
119
- const testData = global.testomatioDataStore[this.dataDirName][testId];
120
- debug(`Data for test id ${testId}:\n${testData}`);
121
- return testData;
122
- }
123
- debug(`No ${this.dataType} data for test id ${testId}`);
124
- return null;
125
- } catch (e) {
126
- // there could be no data, ignore
127
- }
128
- }
129
-
130
- _getDataFromFile(testId) {
131
- try {
132
- const filepath = join(TESTOMAT_TMP_STORAGE.mainDir, `${this.dataType}_${testId}`);
133
- if (fs.existsSync(filepath)) {
134
- const testData = fs.readFileSync(filepath, 'utf-8');
135
- debug(`Data for test id ${testId}:\n${testData}`);
136
- return testData;
137
- }
138
- debug(`No ${this.dataType} data for test id ${testId}`);
139
- return null;
140
- } catch (e) {
141
- // there could be no data, ignore
142
- }
143
- return null;
144
- }
145
-
146
- _putDataToGlobalVar(data, testId) {
147
- debug('Saving data to global variable for test', testId, ':\n', data, '\n');
148
- global.testomatioDataStore[this.dataDirName] = {};
149
- global.testomatioDataStore[this.dataDirName][testId] = data;
150
- }
151
-
152
- _putDataToFile(data, testId) {
153
- if (typeof data !== 'string') data = JSON.stringify(data);
154
- const filename = `${this.dataType}_${testId}`;
155
- const filepath = join(TESTOMAT_TMP_STORAGE.mainDir, filename);
156
- debug('Saving data to file for test', testId, 'to', filepath, ':\n', data, '\n');
157
-
158
- // TODO: handle multiple invocations of JSON.stringify.
159
- // UPD: not actual because decided not to use it - it created extra wrapping quotes "" and // (escaped slashes)
160
- fs.appendFileSync(filepath, data + os.EOL, 'utf-8');
161
- }
162
- }
163
-
164
- module.exports.DataStorage = DataStorage;
165
-
166
- // TODO: consider using fs promises instead of writeSync/appendFileSync to
167
- // prevent blocking and improve performance (probably queue usage will be required)
168
-
169
- // TODO: rewrite client - everything regarding storing artifacts
170
- // TODO: try to define adapter inside client
171
- // TODO: use .env
172
- // TODO: ability to intercept multiple loggers
package/lib/logger.js DELETED
@@ -1,221 +0,0 @@
1
- const debug = require('debug')('@testomatio/reporter:logger');
2
- const { DataStorage } = require('./dataStorage');
3
-
4
- const LOG_METHODS = ['assert', 'debug', 'error', 'info', 'log', 'trace', 'warn'];
5
-
6
- /**
7
- * Logger allows to:
8
- * 1. Intercept logs from user logger (console.log, etc) and store them.
9
- * 2. Output logs to console (actually, logger functionality).
10
- * 3. Varied syntax.
11
- */
12
- class Logger {
13
- // _originalUserLogger used to output logs to console by the user logger
14
- // _loggerToIntercept intercepted and reassigned immediately when added
15
-
16
- constructor(params = {}) {
17
- this.dataStorage = new DataStorage('log', params);
18
-
19
- // commented because prefer to use "intercept" method
20
- // if (params?.logger) this._loggerToIntercept = params.logger;
21
-
22
- this.intercept(this._loggerToIntercept);
23
-
24
- // singleton
25
- if (!Logger.instance) {
26
- Logger.instance = this;
27
- }
28
- }
29
-
30
- /**
31
- * Returns the string (logs) and the context
32
- * This is required because loggers take multiple arguments
33
- * @param {...any} args
34
- */
35
- /*
36
- TODO: its difficult to distinguish context from log artument if its not a string,
37
- e.g. user can use something like console.log('some log', { key: 'value' });
38
- consider not to use this method at all and don't expect context from user inside default log methods;
39
- instead, use custom method like $`some log` and parse it
40
- */
41
- // _getLogStringAndContextFromArgs(...args) {
42
- // let context = '';
43
- // if (args.length > 1 && typeof args[args.length - 1] !== 'string') {
44
- // context = args.pop();
45
- // }
46
- // const logs = args.join(' ');
47
- // return { logs, context };
48
- // }
49
-
50
- /**
51
- * Tagget template literal. Allows to use different syntaxes:
52
- * 1. Tagget template: $`text ${someVar}`
53
- * 2. Standard: $(`text ${someVar}`)
54
- * 3. Standard with multiple arguments: $('text', someVar)
55
- */
56
- _log(strings, ...args) {
57
- let logs;
58
- // this block means tagged template is used (syntax like $`text ${someVar}`)
59
- if (Array.isArray(strings) && strings.length === args.length + 1) {
60
- logs = strings.reduce(
61
- (result, current, index) =>
62
- result +
63
- current +
64
- // strings are splitted by args when use tagged template, thus we add arg after each string
65
- // it looks like: `string1 arg1 string2 arg2 string3`
66
- (args[index] !== undefined // eslint-disable-line no-nested-ternary
67
- ? typeof args[index] === 'string'
68
- ? // add arg as it is
69
- args[index]
70
- : // stringify arg
71
- this._strinfifyLogs(args[index])
72
- : // add space if no arg after string
73
- ' '),
74
- // initial accumulator value
75
- '',
76
- );
77
- } else {
78
- // this block means arguments syntax is used (syntax like $('text', someVar))
79
- logs = this._strinfifyLogs(strings, ...args);
80
- }
81
- this.dataStorage.putData(logs);
82
- }
83
-
84
- /**
85
- *
86
- * @param {*} context testId or test context from test runner
87
- * @returns
88
- */
89
- getLogs(context) {
90
- return this.dataStorage.getData(context);
91
- }
92
-
93
- _strinfifyLogs(...args) {
94
- const logs = [];
95
- // stringify everything except strings
96
- for (const arg of args) {
97
- if (typeof arg === 'string') {
98
- logs.push(arg);
99
- } else {
100
- logs.push(JSON.stringify(arg));
101
- }
102
- }
103
- return logs.join(' ');
104
- }
105
-
106
- assert(...args) {
107
- const logs = this._strinfifyLogs(...args);
108
- this.dataStorage.putData(logs);
109
- try {
110
- this._originalUserLogger.log(`Assertion result: `, ...args);
111
- } catch (e) {
112
- // method could be unexisting, ignore error
113
- }
114
- }
115
-
116
- debug(...args) {
117
- const logs = this._strinfifyLogs(...args);
118
- this.dataStorage.putData(logs);
119
- try {
120
- this._originalUserLogger.debug(...args);
121
- } catch (e) {
122
- // method could be unexisting, ignore error
123
- }
124
- }
125
-
126
- error(...args) {
127
- const logs = this._strinfifyLogs(...args);
128
- this.dataStorage.putData(logs);
129
- try {
130
- this._originalUserLogger.error(...args);
131
- } catch (e) {
132
- // method could be unexisting, ignore error
133
- }
134
- }
135
-
136
- info(...args) {
137
- const logs = this._strinfifyLogs(...args);
138
- this.dataStorage.putData(logs);
139
- try {
140
- this._originalUserLogger.info(...args);
141
- } catch (e) {
142
- // method could be unexisting, ignore error
143
- }
144
- }
145
-
146
- log(...args) {
147
- const logs = this._strinfifyLogs(...args);
148
- this.dataStorage.putData(logs);
149
- try {
150
- this._originalUserLogger.log(...args);
151
- } catch (e) {
152
- // method could be unexisting, ignore error
153
- }
154
- }
155
-
156
- trace(...args) {
157
- const logs = this._strinfifyLogs(...args);
158
- this.dataStorage.putData(logs);
159
- try {
160
- this._originalUserLogger.trace(...args);
161
- } catch (e) {
162
- // method could be unexisting, ignore error
163
- }
164
- }
165
-
166
- warn(...args) {
167
- const logs = this._strinfifyLogs(...args);
168
- this.dataStorage.putData(logs);
169
- try {
170
- this._originalUserLogger.warn(...args);
171
- } catch (e) {
172
- // method could be unexisting, ignore error
173
- }
174
- }
175
-
176
- /**
177
- * Intercepts user logger messages.
178
- * When call this method, Logger start to control the user logger,
179
- * but almost nothing is changed for user ragarding the console output (like log level set by user)
180
- * (until multiple loggers are intercepted,
181
- * in this case only the last intercepted logger will be used as user console output).
182
- * @param {*} userLogger
183
- */
184
- intercept(userLogger) {
185
- if (!userLogger) return;
186
- debug(`Intercepting user logger`);
187
-
188
- // save original user logger to use it for logging (last intercepted will be used for console output)
189
- this._originalUserLogger = { ...userLogger };
190
- this._loggerToIntercept = userLogger;
191
-
192
- /*
193
- override user logger (any, e.g. console) methods to intercept log messages
194
- this._loggerToIntercept = this; could be used, but decided to override only output methods
195
- */
196
- for (const method of LOG_METHODS) {
197
- /*
198
- decided to comment next code line because its better to create method even if it does not exist in user logger;
199
- on method invocation, we will store the data anyway and catch block will prevent potential errors
200
- */
201
- // if (!this._loggerToIntercept[method]) continue;
202
- this._loggerToIntercept[method] = (...args) => this[method](...args);
203
- }
204
- }
205
- }
206
-
207
- Logger.instance = null;
208
-
209
- // module.exports.Logger = Logger;
210
- module.exports = new Logger();
211
-
212
- // TODO: consider using fs promises instead of writeSync/appendFileSync to prevent blocking and improve performance
213
- // TODO: try to use method generator *
214
- // TODO: understand captureStackTrace in output (check on .error) (NOT SUCH IMPORTANT)
215
- // TODO: handle log levels to colorize logs later on UI
216
- // TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax
217
- // TODO: add setLevel method - allows skip messages with the lower level
218
- // TODO: in case of unset _originalUserLogger, logger.{method} will not provide console output,
219
- // need to add some logger by default
220
-
221
- // TODO step method (blue color)