@testomatio/reporter 1.1.0-beta.mocha-create.8 → 1.1.0-rc.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 +1 -0
- package/lib/adapter/codecept.js +81 -6
- package/lib/adapter/cucumber/current.js +15 -8
- package/lib/adapter/cucumber/legacy.js +2 -1
- package/lib/adapter/cypress-plugin/index.js +46 -26
- package/lib/adapter/jest.js +39 -0
- package/lib/adapter/mocha.js +59 -7
- package/lib/adapter/playwright.js +21 -9
- package/lib/bin/startTest.js +2 -1
- package/lib/client.js +46 -44
- package/lib/config.js +5 -0
- package/lib/data-storage.js +203 -0
- package/lib/junit-adapter/index.js +3 -2
- package/lib/pipe/testomatio.js +2 -1
- package/lib/reporter-functions.js +21 -9
- package/lib/reporter.js +8 -1
- package/lib/{storages/artifact-storage.js → services/artifacts.js} +17 -30
- package/lib/services/index.js +13 -0
- package/lib/{storages/key-value-storage.js → services/key-values.js} +18 -18
- package/lib/{storages → services}/logger.js +32 -20
- package/lib/utils/utils.js +13 -4
- package/lib/xmlReader.js +106 -104
- package/package.json +2 -2
- package/Changelog.md +0 -367
- package/lib/storages/data-storage.js +0 -307
package/lib/client.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
const debug = require('debug')('@testomatio/reporter:client');
|
|
2
2
|
const createCallsiteRecord = require('callsite-record');
|
|
3
3
|
const { sep, join } = require('path');
|
|
4
|
-
const { minimatch } = require('minimatch')
|
|
4
|
+
const { minimatch } = require('minimatch');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const chalk = require('chalk');
|
|
7
7
|
const { randomUUID } = require('crypto');
|
|
8
8
|
const upload = require('./fileUploader');
|
|
9
9
|
const { APP_PREFIX } = require('./constants');
|
|
10
10
|
const pipesFactory = require('./pipe');
|
|
11
|
-
const artifactStorage = require('./storages/artifact-storage');
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* @typedef {import('../types').TestData} TestData
|
|
@@ -36,7 +35,7 @@ class Client {
|
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
37
|
* Asynchronously prepares the execution list for running tests through various pipes.
|
|
39
|
-
* Each pipe in the client is checked for enablement,
|
|
38
|
+
* Each pipe in the client is checked for enablement,
|
|
40
39
|
* and if all pipes are disabled, the function returns a resolved Promise.
|
|
41
40
|
* Otherwise, it executes the `prepareRun` method for each enabled pipe and collects the results.
|
|
42
41
|
* The results are then filtered to remove any undefined values.
|
|
@@ -46,7 +45,7 @@ class Client {
|
|
|
46
45
|
* @param {Object} params - The options for preparing the test execution list.
|
|
47
46
|
* @param {string} params.pipe - Name of the executed pipe.
|
|
48
47
|
* @param {string} params.pipeOptions - Filter option.
|
|
49
|
-
* @returns {Promise<any>} - A Promise that resolves to an
|
|
48
|
+
* @returns {Promise<any>} - A Promise that resolves to an
|
|
50
49
|
* array containing the prepared execution list,
|
|
51
50
|
* or resolves to undefined if no valid results are found or if all pipes are disabled.
|
|
52
51
|
*/
|
|
@@ -63,22 +62,22 @@ class Client {
|
|
|
63
62
|
if (!filterPipe.isEnabled) {
|
|
64
63
|
// TODO:for the future for the another pipes
|
|
65
64
|
console.warn(
|
|
66
|
-
APP_PREFIX,
|
|
67
|
-
`At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"
|
|
65
|
+
APP_PREFIX,
|
|
66
|
+
`At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`,
|
|
68
67
|
);
|
|
69
68
|
return;
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
const results = await Promise.all(
|
|
73
|
-
({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })
|
|
74
|
-
)
|
|
75
|
-
|
|
71
|
+
const results = await Promise.all(
|
|
72
|
+
this.pipes.map(async p => ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })),
|
|
73
|
+
);
|
|
74
|
+
|
|
76
75
|
const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
|
|
77
76
|
|
|
78
77
|
if (!result || result.length === 0) {
|
|
79
78
|
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
79
|
+
}
|
|
80
|
+
|
|
82
81
|
debug('Execution tests list', result);
|
|
83
82
|
|
|
84
83
|
return result;
|
|
@@ -135,41 +134,28 @@ class Client {
|
|
|
135
134
|
suite_title,
|
|
136
135
|
suite_id,
|
|
137
136
|
test_id,
|
|
137
|
+
manuallyAttachedArtifacts,
|
|
138
|
+
meta,
|
|
138
139
|
} = testData;
|
|
139
140
|
let { message = '' } = testData;
|
|
140
141
|
|
|
141
|
-
const uploadedFiles = [];
|
|
142
|
-
|
|
143
|
-
let stack = '';
|
|
144
142
|
|
|
143
|
+
let errorFormatted = '';
|
|
145
144
|
if (error) {
|
|
146
|
-
|
|
145
|
+
errorFormatted += this.formatError(error) || '';
|
|
147
146
|
message = error?.message;
|
|
148
147
|
}
|
|
149
|
-
if (steps) {
|
|
150
|
-
stack = this.formatSteps(stack, steps);
|
|
151
|
-
}
|
|
152
148
|
|
|
153
|
-
|
|
149
|
+
// Attach logs
|
|
150
|
+
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
154
151
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
const logger = require('./storages/logger');
|
|
158
|
-
const testLogs = logger.getLogs(test_id);
|
|
159
|
-
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
160
|
-
if (stack) stack += '\n\n';
|
|
161
|
-
stack += testLogs ? `${chalk.bold('Logs:')}\n${testLogs}` : '';
|
|
152
|
+
// add artifacts
|
|
153
|
+
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
162
154
|
|
|
163
|
-
|
|
164
|
-
const artifactFiles = artifactStorage.get(test_id);
|
|
165
|
-
if (artifactFiles) files.push(...artifactFiles);
|
|
166
|
-
|
|
167
|
-
// GET KEY-VALUEs from storage
|
|
168
|
-
const keyValueStorage = require('./storages/key-value-storage');
|
|
169
|
-
const keyValues = keyValueStorage.get(test_id);
|
|
155
|
+
const uploadedFiles = [];
|
|
170
156
|
|
|
171
|
-
for (const
|
|
172
|
-
uploadedFiles.push(upload.uploadFileByPath(
|
|
157
|
+
for (const f of files) {
|
|
158
|
+
uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
|
|
173
159
|
}
|
|
174
160
|
|
|
175
161
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
@@ -187,7 +173,7 @@ class Client {
|
|
|
187
173
|
files,
|
|
188
174
|
steps,
|
|
189
175
|
status,
|
|
190
|
-
stack,
|
|
176
|
+
stack: fullLogs,
|
|
191
177
|
example,
|
|
192
178
|
file,
|
|
193
179
|
code,
|
|
@@ -198,9 +184,11 @@ class Client {
|
|
|
198
184
|
message,
|
|
199
185
|
run_time: parseFloat(time),
|
|
200
186
|
artifacts,
|
|
201
|
-
meta
|
|
187
|
+
meta,
|
|
202
188
|
};
|
|
203
189
|
|
|
190
|
+
debug('Adding test run...', data);
|
|
191
|
+
|
|
204
192
|
this.queue = this.queue.then(() =>
|
|
205
193
|
Promise.all(
|
|
206
194
|
this.pipes.map(async p => {
|
|
@@ -252,15 +240,27 @@ class Client {
|
|
|
252
240
|
return this.queue;
|
|
253
241
|
}
|
|
254
242
|
|
|
255
|
-
|
|
256
|
-
|
|
243
|
+
/**
|
|
244
|
+
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
245
|
+
* @returns {string}
|
|
246
|
+
*/
|
|
247
|
+
formatLogs({ error, steps, logs }) {
|
|
248
|
+
error = error?.trim();
|
|
249
|
+
steps = steps?.trim();
|
|
250
|
+
logs = logs?.trim();
|
|
251
|
+
|
|
252
|
+
let testLogs = '';
|
|
253
|
+
if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}\n\n`;
|
|
254
|
+
if (logs) testLogs += `${chalk.bold.gray('################[ Logs ]################')}\n${logs}\n\n`;
|
|
255
|
+
if (error) testLogs += `${chalk.bold.red('################[ Failure ]################')}\n${error}`;
|
|
256
|
+
return testLogs;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
formatError(error, message) {
|
|
260
260
|
if (!message) message = error.message;
|
|
261
261
|
if (error.inspect) message = error.inspect() || '';
|
|
262
262
|
|
|
263
|
-
let stack =
|
|
263
|
+
let stack = `${message}\n`;
|
|
264
264
|
|
|
265
265
|
// diffs for mocha, cypress, codeceptjs style
|
|
266
266
|
if (error.actual && error.expected) {
|
|
@@ -277,11 +277,11 @@ class Client {
|
|
|
277
277
|
const record = createCallsiteRecord({
|
|
278
278
|
forError: error,
|
|
279
279
|
isCallsiteFrame: frame => {
|
|
280
|
-
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
280
|
+
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
281
281
|
if (hasFrame) return false;
|
|
282
282
|
if (isNotInternalFrame(frame)) hasFrame = true;
|
|
283
283
|
return hasFrame;
|
|
284
|
-
}
|
|
284
|
+
},
|
|
285
285
|
});
|
|
286
286
|
if (record && !record.filename.startsWith('http')) {
|
|
287
287
|
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
@@ -294,9 +294,11 @@ class Client {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
function isNotInternalFrame(frame) {
|
|
297
|
-
return
|
|
297
|
+
return (
|
|
298
|
+
frame.getFileName().includes(sep) &&
|
|
298
299
|
!frame.getFileName().includes('node_modules') &&
|
|
299
300
|
!frame.getFileName().includes('internal')
|
|
301
|
+
);
|
|
300
302
|
}
|
|
301
303
|
|
|
302
304
|
module.exports = Client;
|
package/lib/config.js
CHANGED
|
@@ -8,6 +8,10 @@ const debug = require('debug')('@testomatio/reporter:config');
|
|
|
8
8
|
const dotenv = require('dotenv');
|
|
9
9
|
const envFileVars = dotenv.config({ path: '.env' }).parsed; */
|
|
10
10
|
|
|
11
|
+
const TESTOMATIO = process.env.TESTOMATIO || process.env.TESTOMATIO_API_KEY || process.env.TESTOMATIO_TOKEN || '';
|
|
12
|
+
if (TESTOMATIO === 'undefined') console.error('TESTOMATIO is "undefined". Something went wrong. Contact dev team.');
|
|
13
|
+
process.env.TESTOMATIO = TESTOMATIO;
|
|
14
|
+
|
|
11
15
|
// select only TESTOMATIO related variables (only to print them in debug)
|
|
12
16
|
const testomatioEnvVars =
|
|
13
17
|
Object.keys(process.env)
|
|
@@ -22,3 +26,4 @@ debug('TESTOMATIO variables:', testomatioEnvVars);
|
|
|
22
26
|
const config = process.env;
|
|
23
27
|
|
|
24
28
|
module.exports = config;
|
|
29
|
+
module.exports.TESTOMATIO = TESTOMATIO;
|
|
@@ -0,0 +1,203 @@
|
|
|
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 { TESTOMAT_TMP_STORAGE_DIR } = require('./constants');
|
|
7
|
+
const { fileSystem, jestHelpers } = require('./utils/utils');
|
|
8
|
+
|
|
9
|
+
class DataStorage {
|
|
10
|
+
static #instance;
|
|
11
|
+
|
|
12
|
+
context;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @returns {DataStorage}
|
|
17
|
+
*/
|
|
18
|
+
static getInstance() {
|
|
19
|
+
if (!this.#instance) {
|
|
20
|
+
this.#instance = new DataStorage();
|
|
21
|
+
}
|
|
22
|
+
return this.#instance;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setContext(context) {
|
|
26
|
+
this.context = context;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates data storage instance as singleton
|
|
31
|
+
* Stores data to global variable or to file depending on what is applicable for current test runner (adapter)
|
|
32
|
+
* Recommend to use composition while using this class (instead of inheritance).
|
|
33
|
+
* ! Also the class which will use data storage should be singleton (to avoid data loss).
|
|
34
|
+
*/
|
|
35
|
+
constructor() {
|
|
36
|
+
// some frameworks use global variable to store data, some use file storage
|
|
37
|
+
this.isFileStorage = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#stringToFilename(str) {
|
|
41
|
+
// TODO: use md5 hash later
|
|
42
|
+
const validFilenameRegex = /[^a-zA-Z0-9_.-]/g;
|
|
43
|
+
// replace all characters not in the regex above with underscore, then duplicate underscores removed
|
|
44
|
+
let filename = str.replace(validFilenameRegex, '_').replace(/_{2,}/g, '_').substring(0, 255); // max filename length
|
|
45
|
+
// remove leading and trailing underscores
|
|
46
|
+
filename = filename.replace(/^_+|_+$/g, '');
|
|
47
|
+
return filename;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Puts any data to storage (file or global variable).
|
|
52
|
+
* If file: stores data as text, if global variable – stores as array of data.
|
|
53
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
54
|
+
* @param {*} data anything you want to store (string, object, array, etc)
|
|
55
|
+
* @param {*} context could be testId or any context (test name, suite name, including their IDs etc)
|
|
56
|
+
* suite name + test name is used by default
|
|
57
|
+
* @returns
|
|
58
|
+
*/
|
|
59
|
+
putData(dataType, data, context = null) {
|
|
60
|
+
if (!dataType || !data) return;
|
|
61
|
+
const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
|
|
62
|
+
fileSystem.createDir(dataDirPath);
|
|
63
|
+
|
|
64
|
+
context = context || this.context || global.testomatioTestTitle || jestHelpers.getIdOfCurrentlyRunningTest();
|
|
65
|
+
if (!context) {
|
|
66
|
+
debug(`No context provided for "${dataType}" data:`, data);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
context = this.#stringToFilename(context);
|
|
70
|
+
|
|
71
|
+
if (this.isFileStorage) {
|
|
72
|
+
this.#putDataToFile(dataType, data, context);
|
|
73
|
+
} else {
|
|
74
|
+
this.#putDataToGlobalVar(dataType, data, context);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns data, stored for specific test/context (or data which was stored without test id specified).
|
|
80
|
+
* This method will get data from global variable and/or from from file (previosly saved with put method).
|
|
81
|
+
*
|
|
82
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
83
|
+
* @param {string} context
|
|
84
|
+
* @returns {any []} array of data (any type), null (if no data found for context) or string (if data type is log)
|
|
85
|
+
*/
|
|
86
|
+
getData(dataType, context) {
|
|
87
|
+
// TODO: think if it could be useful
|
|
88
|
+
// context = context || this.context || global.testomatioTestTitle || jestHelpers.getIdOfCurrentlyRunningTest();
|
|
89
|
+
|
|
90
|
+
if (!context) {
|
|
91
|
+
debug(`Trying to get "${dataType}" data without context`);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
context = this.#stringToFilename(context);
|
|
96
|
+
|
|
97
|
+
let testDataFromFile = [];
|
|
98
|
+
let testDataFromGlobalVar = [];
|
|
99
|
+
|
|
100
|
+
if (global?.testomatioDataStore) {
|
|
101
|
+
testDataFromGlobalVar = this.#getDataFromGlobalVar(dataType, context);
|
|
102
|
+
if (testDataFromGlobalVar) {
|
|
103
|
+
if (testDataFromGlobalVar.length) return testDataFromGlobalVar;
|
|
104
|
+
}
|
|
105
|
+
// don't return nothing if no data in global variable
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
testDataFromFile = this.#getDataFromFile(dataType, context);
|
|
109
|
+
|
|
110
|
+
if (testDataFromFile.length) {
|
|
111
|
+
return testDataFromFile;
|
|
112
|
+
}
|
|
113
|
+
debug(`No "${dataType}" data for context "${context}" in both file and global variable`);
|
|
114
|
+
|
|
115
|
+
// in case no data found for context
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
121
|
+
* @param {string} context
|
|
122
|
+
* @returns aray of data (any type)
|
|
123
|
+
*/
|
|
124
|
+
#getDataFromGlobalVar(dataType, context) {
|
|
125
|
+
try {
|
|
126
|
+
if (global?.testomatioDataStore[dataType]) {
|
|
127
|
+
const testData = global.testomatioDataStore[dataType][context];
|
|
128
|
+
if (testData) debug(`"${dataType}" data for constext "${context}":`, testData.join(', '));
|
|
129
|
+
return testData || [];
|
|
130
|
+
}
|
|
131
|
+
// debug(`No ${this.dataType} data for context ${context} in <global> storage`);
|
|
132
|
+
return [];
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// there could be no data, ignore
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
140
|
+
* @param {*} context
|
|
141
|
+
* @returns array of data (any type)
|
|
142
|
+
*/
|
|
143
|
+
#getDataFromFile(dataType, context) {
|
|
144
|
+
const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
|
|
145
|
+
try {
|
|
146
|
+
const filepath = join(dataDirPath, `${dataType}_${context}`);
|
|
147
|
+
if (fs.existsSync(filepath)) {
|
|
148
|
+
const testDataAsText = fs.readFileSync(filepath, 'utf-8');
|
|
149
|
+
if (testDataAsText) debug(`"${dataType}" data for context "${context}":`, testDataAsText);
|
|
150
|
+
const testDataArr = testDataAsText?.split(os.EOL) || [];
|
|
151
|
+
return testDataArr;
|
|
152
|
+
}
|
|
153
|
+
// debug(`No ${this.dataType} data for ${context} in <file> storage`);
|
|
154
|
+
return [];
|
|
155
|
+
} catch (e) {
|
|
156
|
+
// there could be no data, ignore
|
|
157
|
+
}
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Puts data to global variable. Unlike the file storage, stores data in array (file storage just append as string).
|
|
163
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
164
|
+
* @param {*} data
|
|
165
|
+
* @param {*} context
|
|
166
|
+
*/
|
|
167
|
+
#putDataToGlobalVar(dataType, data, context) {
|
|
168
|
+
debug('Saving data to global variable for ', context, ':', data);
|
|
169
|
+
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
170
|
+
if (!global.testomatioDataStore?.[dataType]) global.testomatioDataStore[dataType] = {};
|
|
171
|
+
|
|
172
|
+
if (!global.testomatioDataStore?.[dataType][context]) global.testomatioDataStore[dataType][context] = [];
|
|
173
|
+
global.testomatioDataStore[dataType][context].push(data);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Puts data to file. Unlike the global variable storage, stores data as string
|
|
178
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
179
|
+
* @param {*} data
|
|
180
|
+
* @param {string} context
|
|
181
|
+
* @returns
|
|
182
|
+
*/
|
|
183
|
+
#putDataToFile(dataType, data, context) {
|
|
184
|
+
const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
|
|
185
|
+
if (typeof data !== 'string') data = JSON.stringify(data);
|
|
186
|
+
const filename = `${dataType}_${context}`;
|
|
187
|
+
const filepath = join(dataDirPath, filename);
|
|
188
|
+
if (!fs.existsSync(dataDirPath)) fileSystem.createDir(dataDirPath);
|
|
189
|
+
debug(`Saving data to file for context "${context}" to ${filepath}. Data: ${JSON.stringify(data)}`);
|
|
190
|
+
|
|
191
|
+
// append new line if file already exists (in this case its definitely includes some data)
|
|
192
|
+
if (fs.existsSync(filepath)) {
|
|
193
|
+
fs.appendFileSync(filepath, os.EOL + data, 'utf-8');
|
|
194
|
+
} else {
|
|
195
|
+
fs.writeFileSync(filepath, data, 'utf-8');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports.dataStorage = DataStorage.getInstance();
|
|
201
|
+
|
|
202
|
+
// TODO: consider using fs promises instead of writeSync/appendFileSync to
|
|
203
|
+
// prevent blocking and improve performance (probably queue usage will be required)
|
|
@@ -6,8 +6,7 @@ const RubyAdapter = require('./ruby');
|
|
|
6
6
|
const CSharpAdapter = require('./csharp');
|
|
7
7
|
|
|
8
8
|
function AdapterFactory(lang, opts) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
|
|
11
10
|
if (lang === 'java') {
|
|
12
11
|
return new JavaAdapter(opts);
|
|
13
12
|
}
|
|
@@ -23,6 +22,8 @@ function AdapterFactory(lang, opts) {
|
|
|
23
22
|
if (lang === 'c#' || lang === 'csharp') {
|
|
24
23
|
return new CSharpAdapter(opts);
|
|
25
24
|
}
|
|
25
|
+
|
|
26
|
+
return new Adapter(opts);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
module.exports = AdapterFactory;
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -5,6 +5,7 @@ const JsonCycle = require('json-cycle');
|
|
|
5
5
|
const { APP_PREFIX, STATUS } = require('../constants');
|
|
6
6
|
const { isValidUrl, foundedTestLog } = require('../utils/utils');
|
|
7
7
|
const { parseFilterParams, generateFilterRequestParams, setS3Credentials, } = require('../utils/pipe_utils');
|
|
8
|
+
const { TESTOMATIO } = require('../config');
|
|
8
9
|
|
|
9
10
|
if (process.env.TESTOMATIO_RUN) {
|
|
10
11
|
process.env.runId = process.env.TESTOMATIO_RUN;
|
|
@@ -20,7 +21,7 @@ class TestomatioPipe {
|
|
|
20
21
|
constructor(params, store) {
|
|
21
22
|
this.isEnabled = false;
|
|
22
23
|
this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
23
|
-
this.apiKey = params.apiKey ||
|
|
24
|
+
this.apiKey = params.apiKey || TESTOMATIO;
|
|
24
25
|
debug('Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
|
|
25
26
|
if (!this.apiKey) {
|
|
26
27
|
return;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
|
|
1
|
+
const { services } = require('./services');
|
|
2
|
+
const { initPlaywrightForStorage } = require('./adapter/playwright');
|
|
3
|
+
|
|
4
|
+
if (process.env.PLAYWRIGHT_TEST_BASE_URL) {
|
|
5
|
+
initPlaywrightForStorage();
|
|
6
|
+
}
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Stores path to file as artifact and uploads it to the S3 storage
|
|
@@ -8,7 +11,7 @@ const keyValueStorage = require('./storages/key-value-storage');
|
|
|
8
11
|
*/
|
|
9
12
|
function saveArtifact(data, context = null) {
|
|
10
13
|
if (!data) return;
|
|
11
|
-
|
|
14
|
+
services.artifacts.put(data, context);
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
/**
|
|
@@ -16,23 +19,31 @@ function saveArtifact(data, context = null) {
|
|
|
16
19
|
* @param {...any} args
|
|
17
20
|
*/
|
|
18
21
|
function logMessage(...args) {
|
|
19
|
-
logger.log(...args);
|
|
22
|
+
services.logger.log(...args);
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
/**
|
|
23
26
|
* Similar to "log" function but marks message in report as a step
|
|
24
|
-
* @param {*} message
|
|
27
|
+
* @param {*} message
|
|
25
28
|
*/
|
|
26
29
|
function addStep(message) {
|
|
27
|
-
logger.step(message);
|
|
30
|
+
services.logger.step(message);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
34
|
* Add key-value pair(s) to the test report
|
|
32
|
-
* @param {*} keyValue
|
|
35
|
+
* @param {*} keyValue
|
|
33
36
|
*/
|
|
34
37
|
function setKeyValue(keyValue) {
|
|
35
|
-
|
|
38
|
+
services.keyValues.put(keyValue);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param {string} context – test id or test title or suite title + test title
|
|
44
|
+
*/
|
|
45
|
+
function _setStorageContext(context) {
|
|
46
|
+
services.setContext(context);
|
|
36
47
|
}
|
|
37
48
|
|
|
38
49
|
module.exports = {
|
|
@@ -40,4 +51,5 @@ module.exports = {
|
|
|
40
51
|
log: logMessage,
|
|
41
52
|
step: addStep,
|
|
42
53
|
keyValue: setKeyValue,
|
|
54
|
+
_setStorageContext,
|
|
43
55
|
};
|
package/lib/reporter.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { services } = require('./services');
|
|
2
2
|
const TestomatClient = require('./client');
|
|
3
3
|
const TRConstants = require('./constants');
|
|
4
4
|
|
|
5
|
+
const logger = services.logger;
|
|
6
|
+
|
|
7
|
+
// TODO: should be deprecated, reporter-functions should be used instead
|
|
5
8
|
const log = logger.templateLiteralLog.bind(logger);
|
|
6
9
|
const step = logger.step.bind(logger);
|
|
10
|
+
|
|
7
11
|
const reporterFunctions = require('./reporter-functions');
|
|
8
12
|
|
|
9
13
|
module.exports = {
|
|
14
|
+
// duplications; no difference
|
|
10
15
|
logger,
|
|
11
16
|
testomatioLogger: logger,
|
|
17
|
+
|
|
12
18
|
log,
|
|
13
19
|
step,
|
|
14
20
|
TestomatClient,
|
|
@@ -20,4 +26,5 @@ module.exports.testomat = {
|
|
|
20
26
|
log: reporterFunctions.log,
|
|
21
27
|
step: reporterFunctions.step,
|
|
22
28
|
meta: reporterFunctions.keyValue,
|
|
29
|
+
_setStorageContext: reporterFunctions._setStorageContext,
|
|
23
30
|
};
|
|
@@ -1,28 +1,21 @@
|
|
|
1
1
|
const debug = require('debug')('@testomatio/reporter:artifact-storage');
|
|
2
|
-
const
|
|
2
|
+
const { dataStorage } = require('../data-storage');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Artifact storage is supposed to store file paths
|
|
6
6
|
*/
|
|
7
7
|
class ArtifactStorage {
|
|
8
|
+
static #instance;
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// singleton
|
|
14
|
-
if (!ArtifactStorage.instance) {
|
|
15
|
-
ArtifactStorage.instance = this;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// singleton
|
|
10
|
+
/**
|
|
11
|
+
* Singleton
|
|
12
|
+
* @returns {ArtifactStorage}
|
|
13
|
+
*/
|
|
20
14
|
static getInstance() {
|
|
21
|
-
if (!
|
|
22
|
-
|
|
15
|
+
if (!this.#instance) {
|
|
16
|
+
this.#instance = new ArtifactStorage();
|
|
23
17
|
}
|
|
24
|
-
|
|
25
|
-
return ArtifactStorage.instance;
|
|
18
|
+
return this.#instance;
|
|
26
19
|
}
|
|
27
20
|
|
|
28
21
|
/**
|
|
@@ -33,23 +26,20 @@ class ArtifactStorage {
|
|
|
33
26
|
put(data, context = null) {
|
|
34
27
|
if (!data) return;
|
|
35
28
|
debug('Save artifact:', data);
|
|
36
|
-
|
|
29
|
+
dataStorage.putData('artifact', data, context);
|
|
37
30
|
}
|
|
38
31
|
|
|
39
32
|
/**
|
|
40
33
|
* Returns list of artifacts to upload
|
|
41
34
|
* @param {*} context testId or test context from test runner
|
|
42
|
-
* @returns {string | {path: string, type: string, name: string}[]}
|
|
35
|
+
* @returns {(string | {path: string, type: string, name: string})[]}
|
|
43
36
|
*/
|
|
44
37
|
get(context) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// array of any data
|
|
48
|
-
let artifacts = this.dataStorage.getData(context);
|
|
49
|
-
if (!artifacts || !artifacts.length) return null;
|
|
38
|
+
let artifacts = dataStorage.getData('artifact', context);
|
|
39
|
+
if (!artifacts || !artifacts.length) return [];
|
|
50
40
|
|
|
51
41
|
artifacts = artifacts.map(artifactData => {
|
|
52
|
-
// artifact could be an object ({type, path, name} props) or string
|
|
42
|
+
// artifact could be an object ({type, path, name} props) or string (just path)
|
|
53
43
|
let artifact;
|
|
54
44
|
try {
|
|
55
45
|
artifact = JSON.parse(artifactData);
|
|
@@ -59,12 +49,9 @@ class ArtifactStorage {
|
|
|
59
49
|
return artifact;
|
|
60
50
|
});
|
|
61
51
|
artifacts = artifacts.filter(artifact => !!artifact);
|
|
62
|
-
|
|
52
|
+
debug(`Artifacts for test ${context}:`, artifacts);
|
|
53
|
+
return artifacts.length ? artifacts : [];
|
|
63
54
|
}
|
|
64
55
|
}
|
|
65
56
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// const artifactStorage = ArtifactStorage.getInstance();
|
|
69
|
-
const artifactStorage = new ArtifactStorage();
|
|
70
|
-
module.exports = artifactStorage;
|
|
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
|
+
};
|