@testomatio/reporter 1.2.4-beta → 1.3.1-beta
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/lib/adapter/cucumber/current.js +2 -2
- package/lib/adapter/cucumber/legacy.js +2 -2
- package/lib/adapter/playwright.js +23 -32
- package/lib/client.js +20 -16
- package/lib/dataStorage.js +54 -51
- package/lib/logger.js +123 -115
- package/lib/util.js +2 -0
- package/package.json +4 -1
- package/lib/helpers.js +0 -34
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
1
|
+
// eslint-disable-next-line global-require, import/no-extraneous-dependencies, import/no-unresolved
|
|
2
2
|
const { Formatter, formatterHelpers } = require('@cucumber/cucumber');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const fs = require('fs');
|
|
@@ -29,7 +29,7 @@ class CucumberReporter extends Formatter {
|
|
|
29
29
|
this.failures = [];
|
|
30
30
|
this.cases = [];
|
|
31
31
|
|
|
32
|
-
this.client = new TestomatClient();
|
|
32
|
+
this.client = new TestomatClient({ apiKey: options.apiKey || process.env.TESTOMATIO });
|
|
33
33
|
this.status = STATUS.PASSED;
|
|
34
34
|
|
|
35
35
|
global.testomatioRunningEnvironment = 'cucumber:current';
|
|
@@ -125,8 +125,8 @@ const createTestomatFormatter = apiKey => {
|
|
|
125
125
|
|
|
126
126
|
if (!scenario.name) return;
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
const message = '';
|
|
129
|
+
const cliMessage = `- ${scenario.name}: ${chalk.bold(status.toUpperCase())}`;
|
|
130
130
|
|
|
131
131
|
// if (event.result.status === 'undefined') {
|
|
132
132
|
// cliMessage += chalk.yellow(
|
|
@@ -7,11 +7,10 @@ const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE } = require('../constan
|
|
|
7
7
|
const TestomatioClient = require('../client');
|
|
8
8
|
const { isArtifactsEnabled } = require('../fileUploader');
|
|
9
9
|
const { parseTest, fileSystem } = require('../util');
|
|
10
|
-
const { setCurrentlyRunningTestId } = require('../helpers');
|
|
11
10
|
|
|
12
11
|
const reportTestPromises = [];
|
|
13
12
|
|
|
14
|
-
class
|
|
13
|
+
class PlaywrightReporter {
|
|
15
14
|
constructor(config = {}) {
|
|
16
15
|
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
17
16
|
|
|
@@ -19,24 +18,18 @@ class TestomatioReporter {
|
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
onBegin(_config, suite) {
|
|
21
|
+
// clean data storage
|
|
22
|
+
fileSystem.clearDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
22
23
|
if (!this.client) return;
|
|
23
24
|
this.suite = suite;
|
|
24
25
|
this.client.createRun();
|
|
25
|
-
|
|
26
|
-
// for data storage
|
|
27
|
-
// fileSystem.createDir(TESTOMAT_TMP_STORAGE.mainDir);
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
onTestBegin(test
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// global.testomatioDataStore.currentlyRunningTestId = testId;
|
|
34
|
-
// TestomatioReporter.currentlyRunningTestId = testId;
|
|
35
|
-
}
|
|
28
|
+
// onTestBegin(test) {
|
|
29
|
+
// const testId = parseTest(test.title);
|
|
30
|
+
// }
|
|
36
31
|
|
|
37
32
|
onTestEnd(test, result) {
|
|
38
|
-
// reset currently running test id
|
|
39
|
-
// setCurrentlyRunningTestId('');
|
|
40
33
|
if (!this.client) return;
|
|
41
34
|
|
|
42
35
|
let testId = parseTest(test.title);
|
|
@@ -51,25 +44,23 @@ class TestomatioReporter {
|
|
|
51
44
|
appendStep(step, steps);
|
|
52
45
|
}
|
|
53
46
|
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
suite_title,
|
|
70
|
-
files: result.attachments.filter(a => a.body || a.path),
|
|
71
|
-
});
|
|
47
|
+
const logs = `\n\n${chalk.bold('Logs:')}\n${chalk.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
|
|
48
|
+
|
|
49
|
+
const reportTestPromise = this.client.addTestRun(checkStatus(result.status), {
|
|
50
|
+
error,
|
|
51
|
+
test_id: testId,
|
|
52
|
+
suite_title,
|
|
53
|
+
title,
|
|
54
|
+
steps: steps.join('\n'),
|
|
55
|
+
time: duration,
|
|
56
|
+
stack: logs,
|
|
57
|
+
}).then(pipes => {
|
|
58
|
+
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
59
|
+
|
|
60
|
+
this.uploads.push({
|
|
61
|
+
testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
|
|
72
62
|
});
|
|
63
|
+
});
|
|
73
64
|
|
|
74
65
|
reportTestPromises.push(reportTestPromise);
|
|
75
66
|
}
|
|
@@ -140,4 +131,4 @@ function tmpFile(prefix = 'tmp.') {
|
|
|
140
131
|
return path.join(tmpdir, prefix + crypto.randomBytes(16).toString('hex'));
|
|
141
132
|
}
|
|
142
133
|
|
|
143
|
-
module.exports =
|
|
134
|
+
module.exports = PlaywrightReporter;
|
package/lib/client.js
CHANGED
|
@@ -38,7 +38,7 @@ class Client {
|
|
|
38
38
|
*/
|
|
39
39
|
createRun() {
|
|
40
40
|
// all pipes disabled, skipping
|
|
41
|
-
if (!this.pipes
|
|
41
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
42
42
|
|
|
43
43
|
const runParams = {
|
|
44
44
|
title: this.title,
|
|
@@ -67,7 +67,7 @@ class Client {
|
|
|
67
67
|
*/
|
|
68
68
|
async addTestRun(status, testData, storeArtifacts = []) {
|
|
69
69
|
// all pipes disabled, skipping
|
|
70
|
-
if (!this.pipes
|
|
70
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return [];
|
|
71
71
|
|
|
72
72
|
if (!testData)
|
|
73
73
|
testData = {
|
|
@@ -92,7 +92,7 @@ class Client {
|
|
|
92
92
|
|
|
93
93
|
const uploadedFiles = [];
|
|
94
94
|
|
|
95
|
-
let stack =
|
|
95
|
+
let stack = '';
|
|
96
96
|
|
|
97
97
|
if (error) {
|
|
98
98
|
stack = this.formatError(error) || '';
|
|
@@ -102,8 +102,10 @@ class Client {
|
|
|
102
102
|
stack += this.formatSteps(stack, steps);
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
stack += testData.stack || '';
|
|
106
|
+
|
|
105
107
|
// in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
|
|
106
|
-
const logger
|
|
108
|
+
const logger = require('./logger');
|
|
107
109
|
const testLogs = logger.getLogs(test_id);
|
|
108
110
|
// debug(`Test logs for ${test_id}:\n`, testLogs);
|
|
109
111
|
if (stack) stack += '\n\n';
|
|
@@ -151,17 +153,19 @@ class Client {
|
|
|
151
153
|
artifacts,
|
|
152
154
|
};
|
|
153
155
|
|
|
154
|
-
this.queue = this.queue
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
156
|
+
this.queue = this.queue.then(() =>
|
|
157
|
+
Promise.all(
|
|
158
|
+
this.pipes.map(async p => {
|
|
159
|
+
try {
|
|
160
|
+
const result = await p.addTest(data);
|
|
161
|
+
return { pipe: p.toString(), result };
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.log(APP_PREFIX, p.toString(), err);
|
|
164
|
+
}
|
|
165
|
+
}),
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
|
|
165
169
|
return this.queue;
|
|
166
170
|
}
|
|
167
171
|
|
|
@@ -174,7 +178,7 @@ class Client {
|
|
|
174
178
|
*/
|
|
175
179
|
updateRunStatus(status, isParallel = false) {
|
|
176
180
|
// all pipes disabled, skipping
|
|
177
|
-
if (!this.pipes
|
|
181
|
+
if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
|
|
178
182
|
|
|
179
183
|
const runParams = { status, parallel: isParallel };
|
|
180
184
|
|
package/lib/dataStorage.js
CHANGED
|
@@ -6,7 +6,8 @@ const { join } = require('path');
|
|
|
6
6
|
const JestReporter = require('./adapter/jest');
|
|
7
7
|
const { TESTOMAT_TMP_STORAGE } = require('./constants');
|
|
8
8
|
const { fileSystem } = require('./util');
|
|
9
|
-
|
|
9
|
+
const getTestIdFromTestTitle = require('./util').parseTest;
|
|
10
|
+
|
|
10
11
|
class DataStorage {
|
|
11
12
|
/**
|
|
12
13
|
* Creates data storage instance for specific data type.
|
|
@@ -28,34 +29,6 @@ class DataStorage {
|
|
|
28
29
|
debug(`Data storage mode: ${this.isFileStorage ? 'file' : 'memory'}`);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
// TODO: implement
|
|
32
|
-
// eslint-disable-next-line no-unused-vars
|
|
33
|
-
getTestIdFromContext(context) {
|
|
34
|
-
// return testId;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Refreshes storage type (file or global variable) depending on current environment
|
|
39
|
-
* This method should be run on each attempt to put or get data from/to storage
|
|
40
|
-
* Because storage instance is created before the test runner is started
|
|
41
|
-
*/
|
|
42
|
-
_refreshStorageType() {
|
|
43
|
-
this.isFileStorage = !global.testomatioDataStore;
|
|
44
|
-
/*
|
|
45
|
-
FYI:
|
|
46
|
-
If this storage instance is used within test runner, it works fine.
|
|
47
|
-
But if, for example, we create storage instance inside the testomatio client (to get stored data),
|
|
48
|
-
the environment could be different (not already a test runner env).
|
|
49
|
-
Thus, checking environment is only reasonable when you put data to starage
|
|
50
|
-
and potentially useless when get data from storage
|
|
51
|
-
*/
|
|
52
|
-
this.runningEnvironment = this.getRunningEnviroment();
|
|
53
|
-
|
|
54
|
-
// jest does not persist global variables, thus file storage is used
|
|
55
|
-
if (this.runningEnvironment === 'jest') this.isFileStorage = true;
|
|
56
|
-
if (this.runningEnvironment === 'playwright') this.isFileStorage = true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
32
|
/**
|
|
60
33
|
* Try to define the running environment. Not 100% reliable and used as additional check
|
|
61
34
|
* @returns jest | mocha | ...
|
|
@@ -94,18 +67,13 @@ class DataStorage {
|
|
|
94
67
|
putData(data, context = null) {
|
|
95
68
|
this._refreshStorageType();
|
|
96
69
|
|
|
97
|
-
let testId = null;
|
|
98
|
-
if (typeof context === 'string') {
|
|
99
|
-
testId = context;
|
|
100
|
-
} else {
|
|
101
|
-
// TODO: derive testId from context
|
|
102
|
-
// testId = context...
|
|
103
|
-
}
|
|
70
|
+
let testId = this._tryToRetrieveTestId(context) || null;
|
|
104
71
|
|
|
105
|
-
// try to get testId for Jest
|
|
106
|
-
if (this.runningEnvironment === 'jest') testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
|
|
107
72
|
if (this.runningEnvironment === 'codeceptjs') testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
|
|
108
|
-
|
|
73
|
+
if (this.runningEnvironment === 'jest') testId = testId ?? JestReporter.getIdOfCurrentlyRunningTest();
|
|
74
|
+
// logs in playwright are gathered by pw framwork itself
|
|
75
|
+
if (this.runningEnvironment === 'playwright' && this.dataType === 'log') return;
|
|
76
|
+
|
|
109
77
|
// get id from global store
|
|
110
78
|
if (global.testomatioDataStore && global.testomatioDataStore.currentlyRunningTestId)
|
|
111
79
|
testId = testId ?? global.testomatioDataStore?.currentlyRunningTestId;
|
|
@@ -164,6 +132,52 @@ class DataStorage {
|
|
|
164
132
|
return testData;
|
|
165
133
|
}
|
|
166
134
|
|
|
135
|
+
/**
|
|
136
|
+
* This method is named as "try" because it does not guarantee that test id could be retrieved.
|
|
137
|
+
* Context could be anything (test, suite, string, etc) which is used to define testId.
|
|
138
|
+
* Or it could represent any other entity (which does not contain test id).
|
|
139
|
+
* @param {*} context
|
|
140
|
+
*/
|
|
141
|
+
_tryToRetrieveTestId(context) {
|
|
142
|
+
if (!context) return null;
|
|
143
|
+
this._refreshStorageType();
|
|
144
|
+
|
|
145
|
+
if (this.runningEnvironment === 'playwright' || context?.title) {
|
|
146
|
+
// context is testInfo
|
|
147
|
+
const testId = getTestIdFromTestTitle(context.title);
|
|
148
|
+
if (testId) return testId;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (typeof context === 'string') {
|
|
152
|
+
const testId = getTestIdFromTestTitle(context);
|
|
153
|
+
if (testId) return testId;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Refreshes storage type (file or global variable) depending on current environment
|
|
161
|
+
* This method should be run on each attempt to put or get data from/to storage
|
|
162
|
+
* Because storage instance is created before the test runner is started. And storage type could be changed
|
|
163
|
+
*/
|
|
164
|
+
_refreshStorageType() {
|
|
165
|
+
this.isFileStorage = !global.testomatioDataStore;
|
|
166
|
+
/*
|
|
167
|
+
FYI:
|
|
168
|
+
If this storage instance is used within test runner, it works fine.
|
|
169
|
+
But if, for example, we create storage instance inside the testomatio client (to get stored data),
|
|
170
|
+
the environment could differs (not already a test runner environment).
|
|
171
|
+
Thus, checking environment is only reasonable when you put data to starage
|
|
172
|
+
(to define where to put data - to global variable or to file)
|
|
173
|
+
and potentially useless when getting data from storage
|
|
174
|
+
*/
|
|
175
|
+
this.runningEnvironment = this.getRunningEnviroment();
|
|
176
|
+
|
|
177
|
+
// some test frameworks do not persist global variables, thus file storage is used for them
|
|
178
|
+
if (this.runningEnvironment === 'jest') this.isFileStorage = true;
|
|
179
|
+
}
|
|
180
|
+
|
|
167
181
|
_getDataFromGlobalVar(testId) {
|
|
168
182
|
try {
|
|
169
183
|
if (global?.testomatioDataStore[this.dataDirName]) {
|
|
@@ -224,18 +238,7 @@ module.exports.DataStorage = DataStorage;
|
|
|
224
238
|
// TODO: use .env
|
|
225
239
|
// TODO: ability to intercept multiple loggers (upd: no need)
|
|
226
240
|
|
|
227
|
-
/* Playwright
|
|
228
|
-
There is no global scope sharing between tests and reporter listeners.
|
|
229
|
-
Thus need to use file storage. But when tests are run in parallel, the new problem occurs.
|
|
230
|
-
Tried to solve it by using the TEST_WORKER_INDEX env variable, but it is available inside the listener
|
|
231
|
-
(available only inside the test). Thus when cannot match currently running test id from the reporter listener
|
|
232
|
-
with the currently running test.
|
|
233
|
-
The only solution I see for now is to pass the test id to the logger from the test itself.
|
|
234
|
-
By passing the testInfo param to the test function:
|
|
235
|
-
async ({ page }, testInfo)
|
|
236
|
-
and then retrieving the .title from it.
|
|
237
|
-
*/
|
|
238
241
|
|
|
239
242
|
/* Cypress
|
|
240
243
|
Parallelization is only available if using Cypress Dashboard??
|
|
241
|
-
*/
|
|
244
|
+
*/
|
package/lib/logger.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const debug = require('debug')('@testomatio/reporter:logger');
|
|
3
|
+
const _ = require('lodash');
|
|
3
4
|
const { DataStorage } = require('./dataStorage');
|
|
4
5
|
|
|
5
6
|
const LOG_METHODS = ['assert', 'debug', 'error', 'info', 'log', 'trace', 'warn'];
|
|
@@ -34,44 +35,24 @@ class Logger {
|
|
|
34
35
|
// commented because prefer to use "intercept" method
|
|
35
36
|
// if (params?.logger) this._loggerToIntercept = params.logger;
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
// intercept console by default
|
|
39
|
+
this.intercept(console);
|
|
38
40
|
|
|
39
41
|
// singleton
|
|
40
42
|
if (!Logger.instance) {
|
|
41
43
|
Logger.instance = this;
|
|
42
44
|
}
|
|
43
|
-
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
logs = strings.reduce(
|
|
56
|
-
(result, current, index) =>
|
|
57
|
-
result +
|
|
58
|
-
current +
|
|
59
|
-
// strings are splitted by args when use tagged template, thus we add arg after each string
|
|
60
|
-
// it looks like: `string1 arg1 string2 arg2 string3`
|
|
61
|
-
(args[index] !== undefined // eslint-disable-line no-nested-ternary
|
|
62
|
-
? typeof args[index] === 'string'
|
|
63
|
-
? args[index] // add arg as it is
|
|
64
|
-
: this._strinfifyLogs(args[index]) // stringify arg
|
|
65
|
-
: ' '), // add space if no arg after string
|
|
66
|
-
// initial accumulator value
|
|
67
|
-
'',
|
|
68
|
-
);
|
|
69
|
-
} else {
|
|
70
|
-
// this block means arguments syntax is used (syntax like $('text', someVar))
|
|
71
|
-
// in this case strings represents just a first argument
|
|
72
|
-
logs = this._strinfifyLogs(strings, ...args);
|
|
73
|
-
}
|
|
74
|
-
this.dataStorage.putData(logs);
|
|
46
|
+
this._helpers = {
|
|
47
|
+
parseLastArgToGetTestId: (...args) => {
|
|
48
|
+
try {
|
|
49
|
+
return this.dataStorage._tryToRetrieveTestId(args.at(-1));
|
|
50
|
+
} catch (e) {
|
|
51
|
+
// node 14 support
|
|
52
|
+
return this.dataStorage._tryToRetrieveTestId(args[args.length - 1]);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
75
56
|
}
|
|
76
57
|
|
|
77
58
|
/**
|
|
@@ -102,12 +83,16 @@ class Logger {
|
|
|
102
83
|
return logs || '';
|
|
103
84
|
}
|
|
104
85
|
|
|
105
|
-
|
|
86
|
+
_stringifyLogs(...args) {
|
|
106
87
|
const logs = [];
|
|
107
88
|
// stringify everything except strings
|
|
108
89
|
for (const arg of args) {
|
|
90
|
+
// ignore empty strings
|
|
91
|
+
if (arg === '') continue;
|
|
109
92
|
if (typeof arg === 'string') {
|
|
110
93
|
logs.push(arg);
|
|
94
|
+
} else if (Array.isArray(arg)) {
|
|
95
|
+
logs.push(arg.join(' '));
|
|
111
96
|
} else {
|
|
112
97
|
try {
|
|
113
98
|
// eslint-disable-next-line no-unused-expressions
|
|
@@ -121,138 +106,159 @@ class Logger {
|
|
|
121
106
|
return logs.join(' ');
|
|
122
107
|
}
|
|
123
108
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Tagget template literal. Allows to use different syntaxes:
|
|
111
|
+
* 1. Tagget template: $`text ${someVar}`
|
|
112
|
+
* 2. Standard: $(`text ${someVar}`)
|
|
113
|
+
* 3. Standard with multiple arguments: $('text', someVar)
|
|
114
|
+
*/
|
|
115
|
+
_log(strings, ...args) {
|
|
116
|
+
// entity which is used to define testId
|
|
117
|
+
let context = null;
|
|
118
|
+
// last argument could contain testId
|
|
119
|
+
const testId = this._helpers.parseLastArgToGetTestId(...args);
|
|
120
|
+
if (testId) {
|
|
121
|
+
// last arg is test id, do not log it
|
|
122
|
+
context = args.pop();
|
|
123
|
+
}
|
|
127
124
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
125
|
+
// filter empty strings
|
|
126
|
+
strings = strings.filter(item => item !== '');
|
|
131
127
|
|
|
132
|
-
|
|
133
|
-
this
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
128
|
+
let logs;
|
|
129
|
+
// this block means tagged template is used (syntax like $`text ${someVar}`)
|
|
130
|
+
if (Array.isArray(strings) && strings.length === args.length + 1) {
|
|
131
|
+
logs = strings.reduce(
|
|
132
|
+
(result, current, index) =>
|
|
133
|
+
result +
|
|
134
|
+
current +
|
|
135
|
+
// strings are splitted by args when use tagged template, thus we add arg after each string
|
|
136
|
+
// it looks like: `string1 arg1 string2 arg2 string3`
|
|
137
|
+
(args[index] !== undefined // eslint-disable-line no-nested-ternary
|
|
138
|
+
? typeof args[index] === 'string'
|
|
139
|
+
? args[index] // add arg as it is
|
|
140
|
+
: this._stringifyLogs(args[index]) // stringify arg
|
|
141
|
+
: ' '), // add space if no arg after string
|
|
142
|
+
// initial accumulator value
|
|
143
|
+
'',
|
|
144
|
+
);
|
|
145
|
+
} else {
|
|
146
|
+
// this block means arguments syntax is used (syntax like $('text', someVar))
|
|
147
|
+
// in this case strings represents just a first argument
|
|
148
|
+
logs = this._stringifyLogs(strings, ...args);
|
|
138
149
|
}
|
|
150
|
+
this._originalUserLogger.log(logs);
|
|
151
|
+
this.dataStorage.putData(logs, context);
|
|
139
152
|
}
|
|
140
153
|
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
/**
|
|
155
|
+
* This function is a wrapper for all logging methods (not to repeat the same code)
|
|
156
|
+
* @param {*} argsArray
|
|
157
|
+
* @param {*} level
|
|
158
|
+
* @returns
|
|
159
|
+
*/
|
|
160
|
+
_logWrapper(argsArray, level) {
|
|
161
|
+
if (!argsArray.length) return;
|
|
162
|
+
|
|
163
|
+
// entity which is used to define testId
|
|
164
|
+
let context = null;
|
|
165
|
+
// last argument could contain testId
|
|
166
|
+
const testId = this._helpers.parseLastArgToGetTestId(...argsArray);
|
|
167
|
+
if (testId) {
|
|
168
|
+
// last arg is test id, do not log it
|
|
169
|
+
context = argsArray.pop();
|
|
170
|
+
}
|
|
143
171
|
|
|
144
|
-
const level = 'DEBUG';
|
|
145
172
|
const severity = LEVELS[level].severity;
|
|
146
173
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
147
174
|
|
|
148
|
-
|
|
149
|
-
const logs = this._strinfifyLogs(...args);
|
|
175
|
+
const logs = this._stringifyLogs(...argsArray);
|
|
150
176
|
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
151
|
-
this.dataStorage.putData(colorizedLogs);
|
|
177
|
+
this.dataStorage.putData(colorizedLogs, context);
|
|
152
178
|
try {
|
|
153
|
-
|
|
179
|
+
// level.toLowerCase() represents method name (log, warn, error, etc)
|
|
180
|
+
this._originalUserLogger[level.toLowerCase()](colorizedLogs);
|
|
154
181
|
} catch (e) {
|
|
155
182
|
// method could be unexisting, ignore error
|
|
156
183
|
}
|
|
157
184
|
}
|
|
158
185
|
|
|
159
|
-
|
|
186
|
+
assert(...args) {
|
|
187
|
+
// sometimes user invokes logger without any arguments passed
|
|
160
188
|
if (!args.length) return;
|
|
161
189
|
|
|
162
190
|
const level = 'ERROR';
|
|
163
191
|
const severity = LEVELS[level].severity;
|
|
164
192
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
165
193
|
|
|
166
|
-
const logs = this.
|
|
167
|
-
|
|
168
|
-
this.dataStorage.putData(colorizedLogs);
|
|
194
|
+
const logs = this._stringifyLogs(...args);
|
|
195
|
+
this.dataStorage.putData(logs);
|
|
169
196
|
try {
|
|
170
|
-
this._originalUserLogger.
|
|
197
|
+
this._originalUserLogger.log(`Assertion result: `, ...args);
|
|
171
198
|
} catch (e) {
|
|
172
199
|
// method could be unexisting, ignore error
|
|
173
200
|
}
|
|
174
201
|
}
|
|
175
202
|
|
|
176
|
-
|
|
177
|
-
|
|
203
|
+
debug(...args) {
|
|
204
|
+
this._logWrapper(args, 'DEBUG');
|
|
205
|
+
}
|
|
178
206
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
207
|
+
error(...args) {
|
|
208
|
+
this._logWrapper(args, 'ERROR');
|
|
209
|
+
}
|
|
182
210
|
|
|
183
|
-
|
|
184
|
-
this.
|
|
185
|
-
try {
|
|
186
|
-
this._originalUserLogger.info(...args);
|
|
187
|
-
} catch (e) {
|
|
188
|
-
// method could be unexisting, ignore error
|
|
189
|
-
}
|
|
211
|
+
info(...args) {
|
|
212
|
+
this._logWrapper(args, 'INFO');
|
|
190
213
|
}
|
|
191
214
|
|
|
192
215
|
log(...args) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const level = 'INFO';
|
|
196
|
-
const severity = LEVELS[level].severity;
|
|
197
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
198
|
-
|
|
199
|
-
const logs = this._strinfifyLogs(...args);
|
|
200
|
-
this.dataStorage.putData(logs);
|
|
201
|
-
try {
|
|
202
|
-
this._originalUserLogger.log(...args);
|
|
203
|
-
} catch (e) {
|
|
204
|
-
// method could be unexisting, ignore error
|
|
205
|
-
}
|
|
216
|
+
this._logWrapper(args, 'LOG');
|
|
206
217
|
}
|
|
207
218
|
|
|
208
219
|
trace(...args) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const level = 'TRACE';
|
|
212
|
-
const severity = LEVELS[level].severity;
|
|
213
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
214
|
-
|
|
215
|
-
const logs = this._strinfifyLogs(...args);
|
|
216
|
-
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
217
|
-
this.dataStorage.putData(colorizedLogs);
|
|
218
|
-
try {
|
|
219
|
-
this._originalUserLogger.trace(...args);
|
|
220
|
-
} catch (e) {
|
|
221
|
-
// method could be unexisting, ignore error
|
|
222
|
-
}
|
|
220
|
+
this._logWrapper(args, 'TRACE');
|
|
223
221
|
}
|
|
224
222
|
|
|
225
223
|
warn(...args) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const level = 'WARN';
|
|
229
|
-
const severity = LEVELS[level].severity;
|
|
230
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
231
|
-
|
|
232
|
-
const logs = this._strinfifyLogs(...args);
|
|
233
|
-
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
234
|
-
this.dataStorage.putData(colorizedLogs);
|
|
235
|
-
try {
|
|
236
|
-
this._originalUserLogger.warn(...args);
|
|
237
|
-
} catch (e) {
|
|
238
|
-
// method could be unexisting, ignore error
|
|
239
|
-
}
|
|
224
|
+
this._logWrapper(args, 'WARN');
|
|
240
225
|
}
|
|
241
226
|
|
|
242
227
|
/**
|
|
243
228
|
* Intercepts user logger messages.
|
|
244
229
|
* When call this method, Logger start to control the user logger,
|
|
245
|
-
* but almost nothing is changed for user
|
|
230
|
+
* but almost nothing is changed for user regarding the console output (like log level set by user)
|
|
246
231
|
* (until multiple loggers are intercepted,
|
|
247
232
|
* in this case only the last intercepted logger will be used as user console output).
|
|
248
233
|
* @param {*} userLogger
|
|
249
234
|
*/
|
|
250
235
|
intercept(userLogger) {
|
|
251
236
|
if (!userLogger) return;
|
|
252
|
-
debug(`User logger intercepted`);
|
|
253
237
|
|
|
254
|
-
|
|
255
|
-
|
|
238
|
+
/* prevent multiple console interceptions (cause infinite loop)
|
|
239
|
+
actual only for "console", because its used as default output and is intercepted by default */
|
|
240
|
+
const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
|
|
241
|
+
if (isUserLoggerConsole && process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED) {
|
|
242
|
+
debug(`Try to intercept console, but it is already intercepted`);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// prevent other loggers multiple interceptions
|
|
246
|
+
if (_.isEqual(userLogger, this._originalUserLogger)) {
|
|
247
|
+
debug(`Try to intercept user logger, but it is already intercepted`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED = 'true';
|
|
252
|
+
debug(isUserLoggerConsole ? 'console intercepted' : 'User logger intercepted');
|
|
253
|
+
|
|
254
|
+
/* logs could be intercepted for playwright only if console provides output, thus adding this exception.
|
|
255
|
+
it means default _originalUserLogger (console by default) will be used to provide output
|
|
256
|
+
(for other frameworks user logger will be passed for output) */
|
|
257
|
+
if (this.dataStorage.runningEnvironment !== 'playwright') {
|
|
258
|
+
// save original user logger to use it for logging (last intercepted will be used for console output)
|
|
259
|
+
//! TODO: temporary don't oeverride user logger to prevent recursion; console will be used for output
|
|
260
|
+
// this._originalUserLogger = { ...userLogger };
|
|
261
|
+
}
|
|
256
262
|
this._loggerToIntercept = userLogger;
|
|
257
263
|
|
|
258
264
|
/*
|
|
@@ -287,7 +293,9 @@ class Logger {
|
|
|
287
293
|
Logger.instance = null;
|
|
288
294
|
|
|
289
295
|
// module.exports.Logger = Logger;
|
|
290
|
-
|
|
296
|
+
const logger = new Logger();
|
|
297
|
+
|
|
298
|
+
module.exports = logger;
|
|
291
299
|
|
|
292
300
|
// TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
|
|
293
301
|
// upd: did not face such loggers, but still could be useful
|
package/lib/util.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1-beta",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"humanize-duration": "^3.27.3",
|
|
26
26
|
"is-valid-path": "^0.1.1",
|
|
27
27
|
"json-cycle": "^1.3.0",
|
|
28
|
+
"lodash": "^4.17.21",
|
|
28
29
|
"lodash.memoize": "^4.1.2",
|
|
29
30
|
"lodash.merge": "^4.6.2",
|
|
30
31
|
"uuid": "^9.0.0"
|
|
@@ -51,10 +52,12 @@
|
|
|
51
52
|
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
55
|
+
"@cucumber/cucumber": "^9.3.0",
|
|
54
56
|
"@redocly/cli": "^1.0.0-beta.125",
|
|
55
57
|
"@wdio/reporter": "^7.16.13",
|
|
56
58
|
"chai": "^4.3.6",
|
|
57
59
|
"codeceptjs": "^3.2.3",
|
|
60
|
+
"cucumber": "^6.0.7",
|
|
58
61
|
"eslint": "^8.7.0",
|
|
59
62
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
60
63
|
"eslint-config-prettier": "^8.3.0",
|
package/lib/helpers.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const { TESTOMAT_TMP_STORAGE } = require('./constants');
|
|
4
|
-
|
|
5
|
-
// NEXT HELPERS ARE CREATED FOR PLAYWRIGHT. IGNORE FOR NOW. PROBABLY WILL BE REMOVED SHORTLY
|
|
6
|
-
|
|
7
|
-
// TEST_WORKER_INDEX is not available inside the test listener :/
|
|
8
|
-
/**
|
|
9
|
-
* Sets id of currently running test to storage (global variable or file)
|
|
10
|
-
* Used for Playwright
|
|
11
|
-
* @param {*} testId
|
|
12
|
-
*/
|
|
13
|
-
function setCurrentlyRunningTestIdForPlaywright(testId) {
|
|
14
|
-
const workerId = +process.env.TEST_WORKER_INDEX || 0;
|
|
15
|
-
const filePath = path.join(TESTOMAT_TMP_STORAGE.mainDir, 'current_test_id_' + workerId);
|
|
16
|
-
fs.writeFileSync(filePath, testId);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Gets id of currently running test from storage (global variable or file)
|
|
21
|
-
* Used for Playwright
|
|
22
|
-
* @returns string test id of currently running test
|
|
23
|
-
*/
|
|
24
|
-
function getCurrentlyRunningTestIdForPlaywright() {
|
|
25
|
-
const workerId = +process.env.TEST_WORKER_INDEX || 0;
|
|
26
|
-
const filePath = path.join(TESTOMAT_TMP_STORAGE.mainDir, 'current_test_id_' + workerId);
|
|
27
|
-
if (fs.existsSync(filePath)) {
|
|
28
|
-
return fs.readFileSync(filePath, 'utf8') || null;
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
module.exports.setCurrentlyRunningTestId = setCurrentlyRunningTestIdForPlaywright;
|
|
34
|
-
module.exports.getCurrentlyRunningTestId = getCurrentlyRunningTestIdForPlaywright;
|