@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.
- package/README.md +13 -9
- package/lib/_ArtifactStorageOld.js +1 -1
- package/lib/adapter/codecept.js +59 -39
- package/lib/adapter/cucumber/current.js +95 -59
- package/lib/adapter/cucumber/legacy.js +25 -11
- package/lib/adapter/cypress-plugin/index.js +1 -1
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +18 -3
- package/lib/adapter/mocha.js +23 -23
- package/lib/adapter/playwright.js +18 -11
- package/lib/adapter/webdriver.js +1 -1
- package/lib/artifactStorage.js +2 -2
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +102 -34
- package/lib/constants.js +7 -0
- package/lib/dataStorage.js +132 -72
- package/lib/junit-adapter/java.js +27 -9
- package/lib/logger.js +182 -164
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +24 -37
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/html.js +187 -0
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.js +138 -34
- package/lib/reporter.js +2 -1
- package/lib/template/real-data-example.js +47 -0
- package/lib/template/template.hbs +249 -0
- package/lib/template/testomatio.hbs +720 -0
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/{util.js → utils/utils.js} +116 -10
- package/lib/xmlReader.js +132 -44
- package/package.json +10 -6
package/lib/dataStorage.js
CHANGED
|
@@ -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('./
|
|
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
|
-
*
|
|
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
|
|
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.
|
|
22
|
-
this
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
if (
|
|
57
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
//
|
|
76
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
|
82
|
+
this.#putDataToFile(data, testId);
|
|
83
83
|
} else {
|
|
84
|
-
this
|
|
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
|
-
*
|
|
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)
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
119
|
-
const testData = global.testomatioDataStore[this.
|
|
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
|
|
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
|
-
|
|
186
|
+
#getDataFromFile(testId) {
|
|
131
187
|
try {
|
|
132
|
-
const filepath = join(
|
|
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
|
|
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
|
|
199
|
+
return '';
|
|
144
200
|
}
|
|
145
201
|
|
|
146
|
-
|
|
202
|
+
#putDataToGlobalVar(data, testId) {
|
|
147
203
|
debug('Saving data to global variable for test', testId, ':\n', data, '\n');
|
|
148
|
-
global.testomatioDataStore
|
|
149
|
-
global.testomatioDataStore[this.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
40
|
+
// formatStack(t) {
|
|
41
|
+
// const stack = super.formatStack(t);
|
|
24
42
|
|
|
25
|
-
|
|
43
|
+
// const file = t.suite_title.split('.');
|
|
26
44
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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) {
|