@testomatio/reporter 1.2.0-beta → 1.2.0-beta-1

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.
@@ -5,45 +5,23 @@ const os = require('os');
5
5
  const { join } = require('path');
6
6
  const JestReporter = require('./adapter/jest');
7
7
  const { TESTOMAT_TMP_STORAGE } = require('./constants');
8
- const { fileSystem } = require('./util');
8
+ const { fileSystem } = require('./utils/utils');
9
+ const getTestIdFromTestTitle = require('./utils/utils').parseTest;
9
10
 
10
11
  class DataStorage {
11
12
  /**
12
13
  * 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' | ...
14
+ * Stores data to global variable or to file depending on what is applicable for current test runner
15
+ * (running environment).
15
16
  * @param {*} dataType storage type (like 'log', 'artifact'); could be any string, used only to define file path
16
- * @param {*} params
17
17
  */
18
- constructor(dataType, params = {}) {
19
- if (!dataType) throw new Error('Data type is required when creating data storage');
18
+ constructor(dataType) {
20
19
  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
- }
20
+ this.isFileStorage = true;
21
+ this.#refreshStorageType();
39
22
 
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;
23
+ this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE.mainDir, this.dataType);
24
+ fileSystem.createDir(this.dataDirPath);
47
25
  }
48
26
 
49
27
  /**
@@ -51,10 +29,18 @@ class DataStorage {
51
29
  * @returns jest | mocha | ...
52
30
  */
53
31
  getRunningEnviroment() {
32
+ // jest
54
33
  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;
34
+
35
+ if (global.codeceptjs) return 'codeceptjs';
36
+
37
+ // 'cucumber:current', 'cucumber:legacy'
38
+ if (global.testomatioRunningEnvironment) return global.testomatioRunningEnvironment;
39
+
40
+ if (process.env.PLAYWRIGHT_TEST_BASE_URL || process.env.TEST_WORKER_INDEX) return 'playwright';
41
+
42
+ // mocha - can't detect
43
+ return null;
58
44
  }
59
45
 
60
46
  /**
@@ -64,95 +50,169 @@ class DataStorage {
64
50
  * @returns
65
51
  */
66
52
  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...
53
+ this.#refreshStorageType();
54
+
55
+ let testId = this.#tryToRetrieveTestId(context) || null;
56
+
57
+ if (this.runningEnvironment === 'codeceptjs') {
58
+ this.isFileStorage = false;
59
+ testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
60
+ }
61
+
62
+ if (this.runningEnvironment === 'jest') {
63
+ testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
73
64
  }
74
65
 
75
- // try to get testId for Jest
76
- testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
66
+ // logs in playwright are gathered by pw framework itself
67
+ if (this.runningEnvironment === 'playwright' && this.dataType === 'log') return;
68
+
69
+ // get id from global store
70
+ if (global.testomatioDataStore && global.testomatioDataStore.currentlyRunningTestId)
71
+ testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
72
+
73
+ if (!testId && global?.currentlyRunningTestTitle)
74
+ testId = this.#tryToRetrieveTestId(global.currentlyRunningTestTitle);
77
75
 
78
- // if testId is not provided, data is be saved to `{dataType}_other` file;
79
- if (!testId) testId = 'other';
76
+ if (!testId) {
77
+ debug(`No test id provided for ${this.dataType} data: ${data}`);
78
+ return;
79
+ }
80
80
 
81
81
  if (this.isFileStorage) {
82
- this._putDataToFile(data, testId);
82
+ this.#putDataToFile(data, testId);
83
83
  } else {
84
- this._putDataToGlobalVar(data, testId);
84
+ this.#putDataToGlobalVar(data, testId);
85
85
  }
86
86
  }
87
87
 
88
88
  /**
89
- * Returns data, stored for specific testId (or data which was stored without test id specified)
89
+ * Returns data, stored for specific testId (or data which was stored without test id specified).
90
+ * This method will get data from global variable. But if it is not available, it will try to get data from file.
91
+ * Thus, good approach is to remove file storage folder before each test run (and after, for sure).
90
92
  *
91
- * (Don't try to guess the execution environment (e.g. test runner) inside this method, it could be any)
93
+ * Defining the execution environment is not guaranteed! Is used only as additional check.
92
94
  * @param {*} context could be testId or any context (test, suite, etc) which is used to define testId
93
95
  * @returns
94
96
  */
95
97
  getData(context) {
98
+ this.#refreshStorageType();
99
+
96
100
  let testId = null;
97
101
  if (typeof context === 'string') {
98
- testId = context;
102
+ testId = this.#tryToRetrieveTestId(context) || context;
99
103
  } else {
100
104
  // TODO: derive testId from context
101
105
  // testId = context...
102
106
  }
103
107
 
104
- if (!testId) testId = 'other';
105
-
106
- if (this.isFileStorage) {
107
- return this._getDataFromFile(testId);
108
+ if (!testId) {
109
+ debug(`Cannot get test id from passed context:`, context);
110
+ return null;
108
111
  }
112
+
113
+ let testData = '';
114
+
109
115
  if (global?.testomatioDataStore) {
110
- return this._getDataFromGlobalVar(testId);
116
+ testData = this.#getDataFromGlobalVar(testId);
117
+ // these frameworks use global variable storage
118
+ if (testData && ['cucumber', 'codeceptjs'].includes(this.runningEnvironment)) return testData;
111
119
  }
120
+
121
+ /* condition is removed for mocha
122
+ mocha has created a global storage, but just in some cases, generally it is not available
123
+ */
124
+ // if (this.isFileStorage || !testData) {
125
+ // testData = this.#getDataFromFile(testId);
126
+ // if (testData) return testData;
127
+ // }
128
+
129
+ const testDataFromFile = this.#getDataFromFile(testId);
130
+ testData += testDataFromFile || '';
131
+
112
132
  debug(`No ${this.dataType} data for test id ${testId} in both file and global variable`);
133
+ return testData || '';
134
+ }
135
+
136
+ /**
137
+ * This method is named as "try" because it does not guarantee that test id could be retrieved.
138
+ * Context could be anything (test, suite, string, etc) which is used to define testId.
139
+ * Or it could represent any other entity (which does not contain test id).
140
+ * @param {*} context
141
+ */
142
+ #tryToRetrieveTestId(context) {
143
+ if (!context) return null;
144
+ this.#refreshStorageType();
145
+
146
+ if (this.runningEnvironment === 'playwright' || context?.title) {
147
+ // context is testInfo
148
+ const testId = getTestIdFromTestTitle(context.title);
149
+ if (testId) return testId;
150
+ }
151
+
152
+ if (typeof context === 'string') {
153
+ const testId = getTestIdFromTestTitle(context);
154
+ if (testId) return testId;
155
+ }
156
+
113
157
  return null;
114
158
  }
115
159
 
116
- _getDataFromGlobalVar(testId) {
160
+ /**
161
+ * Refreshes storage type (file or global variable) depending on current environment
162
+ * This method should be run on each attempt to put or get data from/to storage
163
+ * Because storage instance is created before the test runner is started. And storage type could be changed
164
+ */
165
+ #refreshStorageType() {
166
+ this.runningEnvironment = this.getRunningEnviroment();
167
+
168
+ // some test frameworks do not persist global variables, thus file storage is used for them (by default)
169
+ if (['playwright', 'codeceptjs'].includes(this.runningEnvironment)) this.isFileStorage = false;
170
+ }
171
+
172
+ #getDataFromGlobalVar(testId) {
117
173
  try {
118
- if (global?.testomatioDataStore[this.dataDirName]) {
119
- const testData = global.testomatioDataStore[this.dataDirName][testId];
174
+ if (global?.testomatioDataStore[this.dataType]) {
175
+ const testData = global.testomatioDataStore[this.dataType][testId];
120
176
  debug(`Data for test id ${testId}:\n${testData}`);
121
- return testData;
177
+ return testData || '';
122
178
  }
123
- debug(`No ${this.dataType} data for test id ${testId}`);
124
- return null;
179
+ debug(`No ${this.dataType} data for test id ${testId} in <global> storage`);
180
+ return '';
125
181
  } catch (e) {
126
182
  // there could be no data, ignore
127
183
  }
128
184
  }
129
185
 
130
- _getDataFromFile(testId) {
186
+ #getDataFromFile(testId) {
131
187
  try {
132
- const filepath = join(TESTOMAT_TMP_STORAGE.mainDir, `${this.dataType}_${testId}`);
188
+ const filepath = join(this.dataDirPath, `${this.dataType}_${testId}`);
133
189
  if (fs.existsSync(filepath)) {
134
190
  const testData = fs.readFileSync(filepath, 'utf-8');
135
191
  debug(`Data for test id ${testId}:\n${testData}`);
136
- return testData;
192
+ return testData || '';
137
193
  }
138
- debug(`No ${this.dataType} data for test id ${testId}`);
139
- return null;
194
+ debug(`No ${this.dataType} data for test id ${testId} in <file> storage`);
195
+ return '';
140
196
  } catch (e) {
141
197
  // there could be no data, ignore
142
198
  }
143
- return null;
199
+ return '';
144
200
  }
145
201
 
146
- _putDataToGlobalVar(data, testId) {
202
+ #putDataToGlobalVar(data, testId) {
147
203
  debug('Saving data to global variable for test', testId, ':\n', data, '\n');
148
- global.testomatioDataStore[this.dataDirName] = {};
149
- global.testomatioDataStore[this.dataDirName][testId] = data;
204
+ if (!global.testomatioDataStore) global.testomatioDataStore = {};
205
+ if (!global.testomatioDataStore?.[this.dataType]) global.testomatioDataStore[this.dataType] = {};
206
+ global.testomatioDataStore?.[this.dataType][testId] // eslint-disable-line no-unused-expressions
207
+ ? (global.testomatioDataStore[this.dataType][testId] += `\n${data}`)
208
+ : (global.testomatioDataStore[this.dataType][testId] = data);
150
209
  }
151
210
 
152
- _putDataToFile(data, testId) {
211
+ #putDataToFile(data, testId) {
153
212
  if (typeof data !== 'string') data = JSON.stringify(data);
154
213
  const filename = `${this.dataType}_${testId}`;
155
- const filepath = join(TESTOMAT_TMP_STORAGE.mainDir, filename);
214
+ const filepath = join(this.dataDirPath, filename);
215
+ if (!fs.existsSync(this.dataDirPath)) fileSystem.createDir(this.dataDirPath);
156
216
  debug('Saving data to file for test', testId, 'to', filepath, ':\n', data, '\n');
157
217
 
158
218
  // TODO: handle multiple invocations of JSON.stringify.
@@ -169,4 +229,4 @@ module.exports.DataStorage = DataStorage;
169
229
  // TODO: rewrite client - everything regarding storing artifacts
170
230
  // TODO: try to define adapter inside client
171
231
  // TODO: use .env
172
- // TODO: ability to intercept multiple loggers
232
+ // TODO: ability to intercept multiple loggers (upd: no need)
@@ -10,24 +10,42 @@ class JavaAdapter extends Adapter {
10
10
 
11
11
  formatTest(t) {
12
12
  const fileParts = t.suite_title.split('.')
13
- const example = t.title.match(/\[(.*)\]/)?.[1];
14
- if (example) t.example = { "#": example }
15
13
 
16
14
  t.file = namespaceToFileName(t.suite_title);
17
15
  t.title = t.title.split('(')[0];
16
+
17
+ // detect params
18
+ const paramMatches = t.title.match(/\[(.*?)\]/g);
19
+
20
+ if (paramMatches) {
21
+ const params = paramMatches.map((_match, index) => `param${index + 1}`);
22
+ if (params.length === 1) params[0] = 'param';
23
+ let paramIndex = 0;
24
+
25
+ t.title = t.title.replace(/: \[(.*?)\]/g, () => {
26
+ if (params.length < 2) return `\${param}`
27
+ const paramName = params[paramIndex] || `param${paramIndex + 1}`;
28
+ paramIndex++;
29
+ return `\${${paramName}}`;
30
+ });
31
+ const example = {};
32
+ paramMatches.forEach((match, index) => { example[params[index]] = match.replace(/[[\]]/g, '') });
33
+ t.example = example;
34
+ }
35
+
18
36
  t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
19
37
  return t;
20
38
  }
21
39
 
22
- formatStack(t) {
23
- const stack = super.formatStack(t);
40
+ // formatStack(t) {
41
+ // const stack = super.formatStack(t);
24
42
 
25
- const file = t.suite_title.split('.');
43
+ // const file = t.suite_title.split('.');
26
44
 
27
- const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
28
- const regexp = new RegExp(fileLine,"g")
29
- return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
30
- }
45
+ // const fileLine = `at .*${file[file.length - 1]}\.java:(\\d+)` // eslint-disable-line no-useless-escape
46
+ // const regexp = new RegExp(fileLine,"g")
47
+ // return stack.replace(regexp, `${this.opts.javaTests}${path.sep}${namespaceToFileName(t.suite_title)}:$1:`);
48
+ // }
31
49
  }
32
50
 
33
51
  function namespaceToFileName(fileName) {