@testomatio/reporter 1.1.1-beta-codecept-logger → 1.1.1-file-upload.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 +11 -17
- package/lib/adapter/codecept.js +26 -14
- 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 +46 -8
- package/lib/adapter/mocha.js +45 -11
- package/lib/adapter/playwright.js +42 -16
- package/lib/bin/startTest.js +2 -1
- package/lib/client.js +30 -16
- package/lib/config.js +5 -0
- package/lib/{storages/data-storage.js → data-storage.js} +76 -43
- package/lib/fileUploader.js +9 -7
- package/lib/junit-adapter/index.js +3 -2
- package/lib/pipe/testomatio.js +4 -2
- package/lib/reporter-functions.js +10 -18
- package/lib/reporter.js +8 -13
- package/lib/{storages/artifact-storage.js → services/artifacts.js} +9 -31
- package/lib/services/index.js +13 -0
- package/lib/{storages/key-value-storage.js → services/key-values.js} +10 -25
- package/lib/{storages → services}/logger.js +13 -30
- package/lib/utils/utils.js +25 -2
- package/lib/xmlReader.js +106 -104
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -27,6 +27,7 @@ class Client {
|
|
|
27
27
|
this.pipes = pipesFactory(params, store);
|
|
28
28
|
this.queue = Promise.resolve();
|
|
29
29
|
this.totalUploaded = 0;
|
|
30
|
+
this.failedToUpload = 0;
|
|
30
31
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
31
32
|
this.executionList = Promise.resolve();
|
|
32
33
|
|
|
@@ -130,9 +131,12 @@ class Client {
|
|
|
130
131
|
steps,
|
|
131
132
|
code = null,
|
|
132
133
|
title,
|
|
134
|
+
file,
|
|
133
135
|
suite_title,
|
|
134
136
|
suite_id,
|
|
135
137
|
test_id,
|
|
138
|
+
manuallyAttachedArtifacts,
|
|
139
|
+
meta,
|
|
136
140
|
} = testData;
|
|
137
141
|
let { message = '' } = testData;
|
|
138
142
|
|
|
@@ -146,18 +150,13 @@ class Client {
|
|
|
146
150
|
// Attach logs
|
|
147
151
|
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
148
152
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
// if (artifactFiles) files.push(...artifactFiles);
|
|
153
|
+
// add artifacts
|
|
154
|
+
if (manuallyAttachedArtifacts?.length) files.push(...manuallyAttachedArtifacts);
|
|
152
155
|
|
|
153
|
-
// // GET KEY-VALUEs from storage
|
|
154
|
-
// const keyValueStorage = require('./storages/key-value-storage');
|
|
155
|
-
// const keyValues = keyValueStorage.get(test_id);
|
|
156
|
-
|
|
157
156
|
const uploadedFiles = [];
|
|
158
157
|
|
|
159
|
-
for (const
|
|
160
|
-
uploadedFiles.push(upload.uploadFileByPath(
|
|
158
|
+
for (const f of files) {
|
|
159
|
+
uploadedFiles.push(upload.uploadFileByPath(f, this.uuid));
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
@@ -165,11 +164,14 @@ class Client {
|
|
|
165
164
|
uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
|
|
166
165
|
}
|
|
167
166
|
|
|
168
|
-
const artifacts = await Promise.all(uploadedFiles);
|
|
167
|
+
const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
|
|
169
168
|
|
|
170
|
-
|
|
169
|
+
if (artifacts.length < uploadedFiles.length) {
|
|
170
|
+
const failedUploading = uploadedFiles.length - artifacts.length;
|
|
171
|
+
this.failedToUpload += failedUploading;
|
|
172
|
+
}
|
|
171
173
|
|
|
172
|
-
this.totalUploaded +=
|
|
174
|
+
this.totalUploaded += artifacts.length;
|
|
173
175
|
|
|
174
176
|
const data = {
|
|
175
177
|
files,
|
|
@@ -177,6 +179,7 @@ class Client {
|
|
|
177
179
|
status,
|
|
178
180
|
stack: fullLogs,
|
|
179
181
|
example,
|
|
182
|
+
file,
|
|
180
183
|
code,
|
|
181
184
|
title,
|
|
182
185
|
suite_title,
|
|
@@ -185,7 +188,7 @@ class Client {
|
|
|
185
188
|
message,
|
|
186
189
|
run_time: parseFloat(time),
|
|
187
190
|
artifacts,
|
|
188
|
-
|
|
191
|
+
meta,
|
|
189
192
|
};
|
|
190
193
|
|
|
191
194
|
debug('Adding test run...', data);
|
|
@@ -234,6 +237,16 @@ class Client {
|
|
|
234
237
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
235
238
|
} uploaded to S3 bucket`,
|
|
236
239
|
);
|
|
240
|
+
|
|
241
|
+
if (this.failedToUpload > 0) {
|
|
242
|
+
console.log(
|
|
243
|
+
APP_PREFIX,
|
|
244
|
+
chalk.yellow(
|
|
245
|
+
`Some artifacts were not uploaded. ${this.failedToUpload} artifacts could not be uploaded. Run tests with DEBUG="@testomatio/reporter:file-uploader" to see details"`,
|
|
246
|
+
),
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
237
250
|
}
|
|
238
251
|
})
|
|
239
252
|
.catch(err => console.log(APP_PREFIX, err));
|
|
@@ -251,9 +264,9 @@ class Client {
|
|
|
251
264
|
logs = logs?.trim();
|
|
252
265
|
|
|
253
266
|
let testLogs = '';
|
|
254
|
-
if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}`;
|
|
255
|
-
if (logs) testLogs +=
|
|
256
|
-
if (error) testLogs +=
|
|
267
|
+
if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}\n\n`;
|
|
268
|
+
if (logs) testLogs += `${chalk.bold.gray('################[ Logs ]################')}\n${logs}\n\n`;
|
|
269
|
+
if (error) testLogs += `${chalk.bold.red('################[ Failure ]################')}\n${error}`;
|
|
257
270
|
return testLogs;
|
|
258
271
|
}
|
|
259
272
|
|
|
@@ -296,6 +309,7 @@ class Client {
|
|
|
296
309
|
|
|
297
310
|
function isNotInternalFrame(frame) {
|
|
298
311
|
return (
|
|
312
|
+
frame.getFileName() &&
|
|
299
313
|
frame.getFileName().includes(sep) &&
|
|
300
314
|
!frame.getFileName().includes('node_modules') &&
|
|
301
315
|
!frame.getFileName().includes('internal')
|
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;
|
|
@@ -3,25 +3,38 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { join } = require('path');
|
|
6
|
-
const { TESTOMAT_TMP_STORAGE_DIR } = require('
|
|
7
|
-
const { fileSystem } = require('
|
|
6
|
+
const { TESTOMAT_TMP_STORAGE_DIR } = require('./constants');
|
|
7
|
+
const { fileSystem, jestHelpers } = require('./utils/utils');
|
|
8
8
|
|
|
9
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
|
+
|
|
10
29
|
/**
|
|
11
|
-
* Creates data storage instance
|
|
12
|
-
* Stores data to global variable or to file depending on what is applicable for current test runner
|
|
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)
|
|
13
32
|
* Recommend to use composition while using this class (instead of inheritance).
|
|
14
33
|
* ! Also the class which will use data storage should be singleton (to avoid data loss).
|
|
15
|
-
* But the data storage itself is not singleton. It will have an instance per data type.
|
|
16
|
-
* @param {'log' | 'artifact' | 'keyvalue'} dataType – type of data to store
|
|
17
34
|
*/
|
|
18
|
-
constructor(
|
|
19
|
-
this.dataType = dataType || 'data';
|
|
20
|
-
|
|
35
|
+
constructor() {
|
|
21
36
|
// some frameworks use global variable to store data, some use file storage
|
|
22
37
|
this.isFileStorage = true;
|
|
23
|
-
|
|
24
|
-
this.dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, this.dataType);
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
#stringToFilename(str) {
|
|
@@ -37,23 +50,28 @@ class DataStorage {
|
|
|
37
50
|
/**
|
|
38
51
|
* Puts any data to storage (file or global variable).
|
|
39
52
|
* If file: stores data as text, if global variable – stores as array of data.
|
|
53
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
40
54
|
* @param {*} data anything you want to store (string, object, array, etc)
|
|
41
55
|
* @param {*} context could be testId or any context (test name, suite name, including their IDs etc)
|
|
42
56
|
* suite name + test name is used by default
|
|
43
57
|
* @returns
|
|
44
58
|
*/
|
|
45
|
-
putData(data, context = null) {
|
|
46
|
-
|
|
59
|
+
putData(dataType, data, context = null) {
|
|
60
|
+
if (!dataType || !data) return;
|
|
61
|
+
|
|
62
|
+
context = context || this.context || global.testomatioTestTitle || jestHelpers.getIdOfCurrentlyRunningTest();
|
|
47
63
|
if (!context) {
|
|
48
|
-
debug(`No context provided for "${
|
|
64
|
+
debug(`No context provided for "${dataType}" data:`, data);
|
|
49
65
|
return;
|
|
50
66
|
}
|
|
51
67
|
context = this.#stringToFilename(context);
|
|
52
68
|
|
|
53
69
|
if (this.isFileStorage) {
|
|
54
|
-
|
|
70
|
+
const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
|
|
71
|
+
fileSystem.createDir(dataDirPath);
|
|
72
|
+
this.#putDataToFile(dataType, data, context);
|
|
55
73
|
} else {
|
|
56
|
-
this.#putDataToGlobalVar(data, context);
|
|
74
|
+
this.#putDataToGlobalVar(dataType, data, context);
|
|
57
75
|
}
|
|
58
76
|
}
|
|
59
77
|
|
|
@@ -61,12 +79,16 @@ class DataStorage {
|
|
|
61
79
|
* Returns data, stored for specific test/context (or data which was stored without test id specified).
|
|
62
80
|
* This method will get data from global variable and/or from from file (previosly saved with put method).
|
|
63
81
|
*
|
|
82
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
64
83
|
* @param {string} context
|
|
65
|
-
* @returns {any []} of data (any type), null (if no data found for
|
|
84
|
+
* @returns {any []} array of data (any type), null (if no data found for context) or string (if data type is log)
|
|
66
85
|
*/
|
|
67
|
-
getData(context) {
|
|
86
|
+
getData(dataType, context) {
|
|
87
|
+
// TODO: think if it could be useful
|
|
88
|
+
// context = context || this.context || global.testomatioTestTitle || jestHelpers.getIdOfCurrentlyRunningTest();
|
|
89
|
+
|
|
68
90
|
if (!context) {
|
|
69
|
-
debug(`
|
|
91
|
+
debug(`Trying to get "${dataType}" data without context`);
|
|
70
92
|
return null;
|
|
71
93
|
}
|
|
72
94
|
|
|
@@ -76,36 +98,37 @@ class DataStorage {
|
|
|
76
98
|
let testDataFromGlobalVar = [];
|
|
77
99
|
|
|
78
100
|
if (global?.testomatioDataStore) {
|
|
79
|
-
testDataFromGlobalVar = this.#getDataFromGlobalVar(context);
|
|
101
|
+
testDataFromGlobalVar = this.#getDataFromGlobalVar(dataType, context);
|
|
80
102
|
if (testDataFromGlobalVar) {
|
|
81
103
|
if (testDataFromGlobalVar.length) return testDataFromGlobalVar;
|
|
82
104
|
}
|
|
83
105
|
// don't return nothing if no data in global variable
|
|
84
106
|
}
|
|
85
107
|
|
|
86
|
-
testDataFromFile = this.#getDataFromFile(context);
|
|
108
|
+
testDataFromFile = this.#getDataFromFile(dataType, context);
|
|
87
109
|
|
|
88
110
|
if (testDataFromFile.length) {
|
|
89
111
|
return testDataFromFile;
|
|
90
112
|
}
|
|
91
|
-
debug(`No "${
|
|
113
|
+
debug(`No "${dataType}" data for context "${context}" in both file and global variable`);
|
|
92
114
|
|
|
93
|
-
// in case no data found for
|
|
115
|
+
// in case no data found for context
|
|
94
116
|
return null;
|
|
95
117
|
}
|
|
96
118
|
|
|
97
119
|
/**
|
|
120
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
98
121
|
* @param {string} context
|
|
99
122
|
* @returns aray of data (any type)
|
|
100
123
|
*/
|
|
101
|
-
#getDataFromGlobalVar(context) {
|
|
124
|
+
#getDataFromGlobalVar(dataType, context) {
|
|
102
125
|
try {
|
|
103
|
-
if (global?.testomatioDataStore[
|
|
104
|
-
const testData = global.testomatioDataStore[
|
|
105
|
-
if (testData) debug(`"${
|
|
126
|
+
if (global?.testomatioDataStore[dataType]) {
|
|
127
|
+
const testData = global.testomatioDataStore[dataType][context];
|
|
128
|
+
if (testData) debug(`"${dataType}" data for constext "${context}":`, testData.join(', '));
|
|
106
129
|
return testData || [];
|
|
107
130
|
}
|
|
108
|
-
// debug(`No ${this.dataType} data for
|
|
131
|
+
// debug(`No ${this.dataType} data for context ${context} in <global> storage`);
|
|
109
132
|
return [];
|
|
110
133
|
} catch (e) {
|
|
111
134
|
// there could be no data, ignore
|
|
@@ -113,20 +136,21 @@ class DataStorage {
|
|
|
113
136
|
}
|
|
114
137
|
|
|
115
138
|
/**
|
|
116
|
-
*
|
|
139
|
+
* @param {'log' | 'artifact' | 'keyvalue'} dataType
|
|
117
140
|
* @param {*} context
|
|
118
141
|
* @returns array of data (any type)
|
|
119
142
|
*/
|
|
120
|
-
#getDataFromFile(context) {
|
|
143
|
+
#getDataFromFile(dataType, context) {
|
|
144
|
+
const dataDirPath = path.join(TESTOMAT_TMP_STORAGE_DIR, dataType);
|
|
121
145
|
try {
|
|
122
|
-
const filepath = join(
|
|
146
|
+
const filepath = join(dataDirPath, `${dataType}_${context}`);
|
|
123
147
|
if (fs.existsSync(filepath)) {
|
|
124
148
|
const testDataAsText = fs.readFileSync(filepath, 'utf-8');
|
|
125
|
-
if (testDataAsText) debug(`"${
|
|
149
|
+
if (testDataAsText) debug(`"${dataType}" data for context "${context}":`, testDataAsText);
|
|
126
150
|
const testDataArr = testDataAsText?.split(os.EOL) || [];
|
|
127
151
|
return testDataArr;
|
|
128
152
|
}
|
|
129
|
-
// debug(`No ${this.dataType} data for
|
|
153
|
+
// debug(`No ${this.dataType} data for ${context} in <file> storage`);
|
|
130
154
|
return [];
|
|
131
155
|
} catch (e) {
|
|
132
156
|
// there could be no data, ignore
|
|
@@ -136,24 +160,33 @@ class DataStorage {
|
|
|
136
160
|
|
|
137
161
|
/**
|
|
138
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
|
|
139
164
|
* @param {*} data
|
|
140
165
|
* @param {*} context
|
|
141
166
|
*/
|
|
142
|
-
#putDataToGlobalVar(data, context) {
|
|
167
|
+
#putDataToGlobalVar(dataType, data, context) {
|
|
143
168
|
debug('Saving data to global variable for ', context, ':', data);
|
|
144
169
|
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
145
|
-
if (!global.testomatioDataStore?.[
|
|
170
|
+
if (!global.testomatioDataStore?.[dataType]) global.testomatioDataStore[dataType] = {};
|
|
146
171
|
|
|
147
|
-
if (!global.testomatioDataStore?.[
|
|
148
|
-
global.testomatioDataStore[
|
|
172
|
+
if (!global.testomatioDataStore?.[dataType][context]) global.testomatioDataStore[dataType][context] = [];
|
|
173
|
+
global.testomatioDataStore[dataType][context].push(data);
|
|
149
174
|
}
|
|
150
175
|
|
|
151
|
-
|
|
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);
|
|
152
185
|
if (typeof data !== 'string') data = JSON.stringify(data);
|
|
153
|
-
const filename = `${
|
|
154
|
-
const filepath = join(
|
|
155
|
-
if (!fs.existsSync(
|
|
156
|
-
debug(
|
|
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)}`);
|
|
157
190
|
|
|
158
191
|
// append new line if file already exists (in this case its definitely includes some data)
|
|
159
192
|
if (fs.existsSync(filepath)) {
|
|
@@ -164,7 +197,7 @@ class DataStorage {
|
|
|
164
197
|
}
|
|
165
198
|
}
|
|
166
199
|
|
|
167
|
-
module.exports = DataStorage;
|
|
200
|
+
module.exports.dataStorage = DataStorage.getInstance();
|
|
168
201
|
|
|
169
202
|
// TODO: consider using fs promises instead of writeSync/appendFileSync to
|
|
170
203
|
// prevent blocking and improve performance (probably queue usage will be required)
|
package/lib/fileUploader.js
CHANGED
|
@@ -11,7 +11,6 @@ const readFile = util.promisify(fs.readFile);
|
|
|
11
11
|
const stat = util.promisify(fs.stat);
|
|
12
12
|
const chalk = require('chalk');
|
|
13
13
|
const { randomUUID } = require('crypto');
|
|
14
|
-
const memoize = require('lodash.memoize');
|
|
15
14
|
|
|
16
15
|
const { APP_PREFIX } = require('./constants');
|
|
17
16
|
|
|
@@ -146,8 +145,13 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
146
145
|
params
|
|
147
146
|
});
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
|
|
149
|
+
const link = await getS3LocationLink(out);
|
|
150
|
+
|
|
151
|
+
debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
|
|
152
|
+
|
|
153
|
+
return link;
|
|
154
|
+
}
|
|
151
155
|
catch (e) {
|
|
152
156
|
debug('S3 file uploading error: ', e);
|
|
153
157
|
|
|
@@ -279,8 +283,6 @@ const getS3LocationLink = async (out) => {
|
|
|
279
283
|
|
|
280
284
|
let s3Location = response?.Location;
|
|
281
285
|
|
|
282
|
-
debug('Uploaded response.Location', s3Location);
|
|
283
|
-
|
|
284
286
|
if (!s3Location) {
|
|
285
287
|
// TODO: out: a fallback case - remove after deeper testing
|
|
286
288
|
s3Location = out?.singleUploadResult?.Location;
|
|
@@ -295,8 +297,8 @@ const getS3LocationLink = async (out) => {
|
|
|
295
297
|
};
|
|
296
298
|
|
|
297
299
|
module.exports = {
|
|
298
|
-
uploadFileByPath
|
|
299
|
-
uploadFileAsBuffer
|
|
300
|
+
uploadFileByPath,
|
|
301
|
+
uploadFileAsBuffer,
|
|
300
302
|
isArtifactsEnabled,
|
|
301
303
|
resetConfig,
|
|
302
304
|
};
|
|
@@ -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;
|
|
@@ -175,13 +176,14 @@ class TestomatioPipe {
|
|
|
175
176
|
* @returns
|
|
176
177
|
*/
|
|
177
178
|
addTest(data) {
|
|
178
|
-
debug('Adding test...');
|
|
179
179
|
if (!this.isEnabled) return;
|
|
180
180
|
if (!this.runId) return;
|
|
181
181
|
data.api_key = this.apiKey;
|
|
182
182
|
data.create = this.createNewTests;
|
|
183
183
|
const json = JsonCycle.stringify(data);
|
|
184
184
|
|
|
185
|
+
debug('Adding test', json);
|
|
186
|
+
|
|
185
187
|
return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, {
|
|
186
188
|
maxContentLength: Infinity,
|
|
187
189
|
maxBodyLength: Infinity,
|
|
@@ -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,7 +19,7 @@ function saveArtifact(data, context = null) {
|
|
|
16
19
|
* @param {...any} args
|
|
17
20
|
*/
|
|
18
21
|
function logMessage(...args) {
|
|
19
|
-
logger.
|
|
22
|
+
services.logger.templateLiteralLog(...args);
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
/**
|
|
@@ -24,7 +27,7 @@ function logMessage(...args) {
|
|
|
24
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
|
/**
|
|
@@ -32,17 +35,7 @@ function addStep(message) {
|
|
|
32
35
|
* @param {*} keyValue
|
|
33
36
|
*/
|
|
34
37
|
function setKeyValue(keyValue) {
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
* @param {string} context – test id or test title or suite title + test title
|
|
41
|
-
*/
|
|
42
|
-
function _setContext(context) {
|
|
43
|
-
logger.setContext(context);
|
|
44
|
-
keyValueStorage.setContext(context);
|
|
45
|
-
artifactStorage.setContext(context);
|
|
38
|
+
services.keyValues.put(keyValue);
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
module.exports = {
|
|
@@ -50,5 +43,4 @@ module.exports = {
|
|
|
50
43
|
log: logMessage,
|
|
51
44
|
step: addStep,
|
|
52
45
|
keyValue: setKeyValue,
|
|
53
|
-
_setContext,
|
|
54
46
|
};
|
package/lib/reporter.js
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
const { logger } = require('./storages/logger');
|
|
2
1
|
const TestomatClient = require('./client');
|
|
3
2
|
const TRConstants = require('./constants');
|
|
4
3
|
|
|
5
|
-
const log = logger.templateLiteralLog.bind(logger);
|
|
6
|
-
const step = logger.step.bind(logger);
|
|
7
4
|
const reporterFunctions = require('./reporter-functions');
|
|
8
5
|
|
|
9
6
|
module.exports = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
log,
|
|
13
|
-
step,
|
|
14
|
-
TestomatClient,
|
|
15
|
-
TRConstants,
|
|
16
|
-
};
|
|
7
|
+
// TODO: deprecate in future; use log or testomat.log
|
|
8
|
+
logger: reporterFunctions.log,
|
|
9
|
+
testomatioLogger: reporterFunctions.log,
|
|
17
10
|
|
|
18
|
-
module.exports.testomat = {
|
|
19
11
|
artifact: reporterFunctions.artifact,
|
|
20
12
|
log: reporterFunctions.log,
|
|
21
|
-
step: reporterFunctions.step,
|
|
22
13
|
meta: reporterFunctions.keyValue,
|
|
23
|
-
|
|
14
|
+
step: reporterFunctions.step,
|
|
15
|
+
|
|
16
|
+
TestomatClient,
|
|
17
|
+
TRConstants,
|
|
24
18
|
};
|
|
19
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const debug = require('debug')('@testomatio/reporter:
|
|
2
|
-
const
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:services-artifacts');
|
|
2
|
+
const { dataStorage } = require('../data-storage');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Artifact storage is supposed to store file paths
|
|
@@ -7,18 +7,6 @@ const DataStorage = require('./data-storage');
|
|
|
7
7
|
class ArtifactStorage {
|
|
8
8
|
static #instance;
|
|
9
9
|
|
|
10
|
-
#context;
|
|
11
|
-
|
|
12
|
-
// there is autocompletion for the class methods if implemented singleton this way
|
|
13
|
-
constructor() {
|
|
14
|
-
this.dataStorage = new DataStorage('artifact');
|
|
15
|
-
|
|
16
|
-
// singleton
|
|
17
|
-
if (!ArtifactStorage.#instance) {
|
|
18
|
-
ArtifactStorage.#instance = this;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
10
|
/**
|
|
23
11
|
* Singleton
|
|
24
12
|
* @returns {ArtifactStorage}
|
|
@@ -30,39 +18,28 @@ class ArtifactStorage {
|
|
|
30
18
|
return this.#instance;
|
|
31
19
|
}
|
|
32
20
|
|
|
33
|
-
/**
|
|
34
|
-
* @param {string} context - suite title + test title
|
|
35
|
-
*/
|
|
36
|
-
setContext(context) {
|
|
37
|
-
this.#context = context;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
21
|
/**
|
|
41
22
|
* Stores path to file as artifact and uploads it to the S3 storage
|
|
42
23
|
* @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
|
|
43
24
|
* @param {*} context testId or test title
|
|
44
25
|
*/
|
|
45
26
|
put(data, context = null) {
|
|
46
|
-
context = context || this.#context;
|
|
47
27
|
if (!data) return;
|
|
48
28
|
debug('Save artifact:', data);
|
|
49
|
-
|
|
29
|
+
dataStorage.putData('artifact', data, context);
|
|
50
30
|
}
|
|
51
31
|
|
|
52
32
|
/**
|
|
53
33
|
* Returns list of artifacts to upload
|
|
54
34
|
* @param {*} context testId or test context from test runner
|
|
55
|
-
* @returns {string | {path: string, type: string, name: string}[]}
|
|
35
|
+
* @returns {(string | {path: string, type: string, name: string})[]}
|
|
56
36
|
*/
|
|
57
37
|
get(context) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// array of any data
|
|
61
|
-
let artifacts = this.dataStorage.getData(context);
|
|
62
|
-
if (!artifacts || !artifacts.length) return null;
|
|
38
|
+
let artifacts = dataStorage.getData('artifact', context);
|
|
39
|
+
if (!artifacts || !artifacts.length) return [];
|
|
63
40
|
|
|
64
41
|
artifacts = artifacts.map(artifactData => {
|
|
65
|
-
// 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)
|
|
66
43
|
let artifact;
|
|
67
44
|
try {
|
|
68
45
|
artifact = JSON.parse(artifactData);
|
|
@@ -72,7 +49,8 @@ class ArtifactStorage {
|
|
|
72
49
|
return artifact;
|
|
73
50
|
});
|
|
74
51
|
artifacts = artifacts.filter(artifact => !!artifact);
|
|
75
|
-
|
|
52
|
+
debug(`Artifacts for test ${context}:`, artifacts);
|
|
53
|
+
return artifacts.length ? artifacts : [];
|
|
76
54
|
}
|
|
77
55
|
}
|
|
78
56
|
|
|
@@ -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
|
+
};
|