@testomatio/reporter 1.2.4-beta → 1.3.0-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 +116 -115
- package/lib/util.js +2 -0
- package/package.json +3 -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
|
@@ -34,44 +34,24 @@ class Logger {
|
|
|
34
34
|
// commented because prefer to use "intercept" method
|
|
35
35
|
// if (params?.logger) this._loggerToIntercept = params.logger;
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
// intercept console by default
|
|
38
|
+
this.intercept(console);
|
|
38
39
|
|
|
39
40
|
// singleton
|
|
40
41
|
if (!Logger.instance) {
|
|
41
42
|
Logger.instance = this;
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
44
|
|
|
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);
|
|
45
|
+
this._helpers = {
|
|
46
|
+
parseLastArgToGetTestId: (...args) => {
|
|
47
|
+
try {
|
|
48
|
+
return this.dataStorage._tryToRetrieveTestId(args.at(-1));
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// node 14 support
|
|
51
|
+
return this.dataStorage._tryToRetrieveTestId(args[args.length - 1]);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
75
55
|
}
|
|
76
56
|
|
|
77
57
|
/**
|
|
@@ -102,12 +82,16 @@ class Logger {
|
|
|
102
82
|
return logs || '';
|
|
103
83
|
}
|
|
104
84
|
|
|
105
|
-
|
|
85
|
+
_stringifyLogs(...args) {
|
|
106
86
|
const logs = [];
|
|
107
87
|
// stringify everything except strings
|
|
108
88
|
for (const arg of args) {
|
|
89
|
+
// ignore empty strings
|
|
90
|
+
if (arg === '') continue;
|
|
109
91
|
if (typeof arg === 'string') {
|
|
110
92
|
logs.push(arg);
|
|
93
|
+
} else if (Array.isArray(arg)) {
|
|
94
|
+
logs.push(arg.join(' '));
|
|
111
95
|
} else {
|
|
112
96
|
try {
|
|
113
97
|
// eslint-disable-next-line no-unused-expressions
|
|
@@ -121,138 +105,153 @@ class Logger {
|
|
|
121
105
|
return logs.join(' ');
|
|
122
106
|
}
|
|
123
107
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Tagget template literal. Allows to use different syntaxes:
|
|
110
|
+
* 1. Tagget template: $`text ${someVar}`
|
|
111
|
+
* 2. Standard: $(`text ${someVar}`)
|
|
112
|
+
* 3. Standard with multiple arguments: $('text', someVar)
|
|
113
|
+
*/
|
|
114
|
+
_log(strings, ...args) {
|
|
115
|
+
// entity which is used to define testId
|
|
116
|
+
let context = null;
|
|
117
|
+
// last argument could contain testId
|
|
118
|
+
const testId = this._helpers.parseLastArgToGetTestId(...args);
|
|
119
|
+
if (testId) {
|
|
120
|
+
// last arg is test id, do not log it
|
|
121
|
+
context = args.pop();
|
|
122
|
+
}
|
|
127
123
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
124
|
+
// filter empty strings
|
|
125
|
+
strings = strings.filter(item => item !== '');
|
|
131
126
|
|
|
132
|
-
|
|
133
|
-
this
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
let logs;
|
|
128
|
+
// this block means tagged template is used (syntax like $`text ${someVar}`)
|
|
129
|
+
if (Array.isArray(strings) && strings.length === args.length + 1) {
|
|
130
|
+
logs = strings.reduce(
|
|
131
|
+
(result, current, index) =>
|
|
132
|
+
result +
|
|
133
|
+
current +
|
|
134
|
+
// strings are splitted by args when use tagged template, thus we add arg after each string
|
|
135
|
+
// it looks like: `string1 arg1 string2 arg2 string3`
|
|
136
|
+
(args[index] !== undefined // eslint-disable-line no-nested-ternary
|
|
137
|
+
? typeof args[index] === 'string'
|
|
138
|
+
? args[index] // add arg as it is
|
|
139
|
+
: this._stringifyLogs(args[index]) // stringify arg
|
|
140
|
+
: ' '), // add space if no arg after string
|
|
141
|
+
// initial accumulator value
|
|
142
|
+
'',
|
|
143
|
+
);
|
|
144
|
+
} else {
|
|
145
|
+
// this block means arguments syntax is used (syntax like $('text', someVar))
|
|
146
|
+
// in this case strings represents just a first argument
|
|
147
|
+
logs = this._stringifyLogs(strings, ...args);
|
|
138
148
|
}
|
|
149
|
+
this._originalUserLogger.log(logs);
|
|
150
|
+
this.dataStorage.putData(logs, context);
|
|
139
151
|
}
|
|
140
152
|
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
/**
|
|
154
|
+
* This function is a wrapper for all logging methods (not to repeat the same code)
|
|
155
|
+
* @param {*} argsArray
|
|
156
|
+
* @param {*} level
|
|
157
|
+
* @returns
|
|
158
|
+
*/
|
|
159
|
+
_logWrapper(argsArray, level) {
|
|
160
|
+
if (!argsArray.length) return;
|
|
161
|
+
|
|
162
|
+
// entity which is used to define testId
|
|
163
|
+
let context = null;
|
|
164
|
+
// last argument could contain testId
|
|
165
|
+
const testId = this._helpers.parseLastArgToGetTestId(...argsArray);
|
|
166
|
+
if (testId) {
|
|
167
|
+
// last arg is test id, do not log it
|
|
168
|
+
context = argsArray.pop();
|
|
169
|
+
}
|
|
143
170
|
|
|
144
|
-
const level = 'DEBUG';
|
|
145
171
|
const severity = LEVELS[level].severity;
|
|
146
172
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
147
173
|
|
|
148
|
-
|
|
149
|
-
const logs = this._strinfifyLogs(...args);
|
|
174
|
+
const logs = this._stringifyLogs(...argsArray);
|
|
150
175
|
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
151
|
-
this.dataStorage.putData(colorizedLogs);
|
|
176
|
+
this.dataStorage.putData(colorizedLogs, context);
|
|
152
177
|
try {
|
|
153
|
-
|
|
178
|
+
// level.toLowerCase() represents method name (log, warn, error, etc)
|
|
179
|
+
this._originalUserLogger[level.toLowerCase()](colorizedLogs);
|
|
154
180
|
} catch (e) {
|
|
155
181
|
// method could be unexisting, ignore error
|
|
156
182
|
}
|
|
157
183
|
}
|
|
158
184
|
|
|
159
|
-
|
|
185
|
+
assert(...args) {
|
|
186
|
+
// sometimes user invokes logger without any arguments passed
|
|
160
187
|
if (!args.length) return;
|
|
161
188
|
|
|
162
189
|
const level = 'ERROR';
|
|
163
190
|
const severity = LEVELS[level].severity;
|
|
164
191
|
if (severity < LEVELS[this.logLevel]?.severity) return;
|
|
165
192
|
|
|
166
|
-
const logs = this.
|
|
167
|
-
|
|
168
|
-
this.dataStorage.putData(colorizedLogs);
|
|
193
|
+
const logs = this._stringifyLogs(...args);
|
|
194
|
+
this.dataStorage.putData(logs);
|
|
169
195
|
try {
|
|
170
|
-
this._originalUserLogger.
|
|
196
|
+
this._originalUserLogger.log(`Assertion result: `, ...args);
|
|
171
197
|
} catch (e) {
|
|
172
198
|
// method could be unexisting, ignore error
|
|
173
199
|
}
|
|
174
200
|
}
|
|
175
201
|
|
|
176
|
-
|
|
177
|
-
|
|
202
|
+
debug(...args) {
|
|
203
|
+
this._logWrapper(args, 'DEBUG');
|
|
204
|
+
}
|
|
178
205
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
206
|
+
error(...args) {
|
|
207
|
+
this._logWrapper(args, 'ERROR');
|
|
208
|
+
}
|
|
182
209
|
|
|
183
|
-
|
|
184
|
-
this.
|
|
185
|
-
try {
|
|
186
|
-
this._originalUserLogger.info(...args);
|
|
187
|
-
} catch (e) {
|
|
188
|
-
// method could be unexisting, ignore error
|
|
189
|
-
}
|
|
210
|
+
info(...args) {
|
|
211
|
+
this._logWrapper(args, 'INFO');
|
|
190
212
|
}
|
|
191
213
|
|
|
192
214
|
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
|
-
}
|
|
215
|
+
this._logWrapper(args, 'LOG');
|
|
206
216
|
}
|
|
207
217
|
|
|
208
218
|
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
|
-
}
|
|
219
|
+
this._logWrapper(args, 'TRACE');
|
|
223
220
|
}
|
|
224
221
|
|
|
225
222
|
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
|
-
}
|
|
223
|
+
this._logWrapper(args, 'WARN');
|
|
240
224
|
}
|
|
241
225
|
|
|
242
226
|
/**
|
|
243
227
|
* Intercepts user logger messages.
|
|
244
228
|
* When call this method, Logger start to control the user logger,
|
|
245
|
-
* but almost nothing is changed for user
|
|
229
|
+
* but almost nothing is changed for user regarding the console output (like log level set by user)
|
|
246
230
|
* (until multiple loggers are intercepted,
|
|
247
231
|
* in this case only the last intercepted logger will be used as user console output).
|
|
248
232
|
* @param {*} userLogger
|
|
249
233
|
*/
|
|
250
234
|
intercept(userLogger) {
|
|
251
235
|
if (!userLogger) return;
|
|
252
|
-
debug(`User logger intercepted`);
|
|
253
236
|
|
|
254
|
-
|
|
255
|
-
|
|
237
|
+
/* prevent multiple console interceptions (cause infinite loop)
|
|
238
|
+
actual only for "console", because its used as default output and is intercepted by default */
|
|
239
|
+
const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
|
|
240
|
+
if (isUserLoggerConsole && process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED) {
|
|
241
|
+
debug(`Try to intercept console, but it is already intercepted`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
process.env.TESTOMATIO_LOGGER_CONSOLE_INTERCEPTED = 'true';
|
|
246
|
+
debug(isUserLoggerConsole ? 'console intercepted' : 'User logger intercepted');
|
|
247
|
+
|
|
248
|
+
/* logs could be intercepted for playwright only if console provides output, thus adding this exception.
|
|
249
|
+
it means default _originalUserLogger (console by default) will be used to provide output
|
|
250
|
+
(for other frameworks user logger will be passed for output) */
|
|
251
|
+
if (this.dataStorage.runningEnvironment !== 'playwright') {
|
|
252
|
+
// save original user logger to use it for logging (last intercepted will be used for console output)
|
|
253
|
+
this._originalUserLogger = { ...userLogger };
|
|
254
|
+
}
|
|
256
255
|
this._loggerToIntercept = userLogger;
|
|
257
256
|
|
|
258
257
|
/*
|
|
@@ -287,7 +286,9 @@ class Logger {
|
|
|
287
286
|
Logger.instance = null;
|
|
288
287
|
|
|
289
288
|
// module.exports.Logger = Logger;
|
|
290
|
-
|
|
289
|
+
const logger = new Logger();
|
|
290
|
+
|
|
291
|
+
module.exports = logger;
|
|
291
292
|
|
|
292
293
|
// TODO: parse passed arguments as {level: 'str', message: 'str'} because some loggers use such syntax;
|
|
293
294
|
// 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.0-beta",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -51,10 +51,12 @@
|
|
|
51
51
|
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
+
"@cucumber/cucumber": "^9.3.0",
|
|
54
55
|
"@redocly/cli": "^1.0.0-beta.125",
|
|
55
56
|
"@wdio/reporter": "^7.16.13",
|
|
56
57
|
"chai": "^4.3.6",
|
|
57
58
|
"codeceptjs": "^3.2.3",
|
|
59
|
+
"cucumber": "^6.0.7",
|
|
58
60
|
"eslint": "^8.7.0",
|
|
59
61
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
60
62
|
"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;
|