@testomatio/reporter 1.1.0-beta.mocha-create.8 → 1.1.1-beta-codecept-logger
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/codecept.js +67 -4
- package/lib/adapter/mocha.js +54 -36
- package/lib/client.js +46 -43
- package/lib/pipe/testomatio.js +1 -2
- package/lib/reporter-functions.js +16 -5
- package/lib/reporter.js +2 -1
- package/lib/storages/artifact-storage.js +21 -12
- package/lib/storages/data-storage.js +48 -185
- package/lib/storages/key-value-storage.js +25 -10
- package/lib/storages/logger.js +46 -16
- package/lib/utils/utils.js +0 -14
- package/package.json +1 -1
- package/Changelog.md +0 -367
package/lib/adapter/codecept.js
CHANGED
|
@@ -4,6 +4,7 @@ const TestomatClient = require('../client');
|
|
|
4
4
|
const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
5
5
|
const upload = require('../fileUploader');
|
|
6
6
|
const { parseTest: getIdFromTestTitle, fileSystem } = require('../utils/utils');
|
|
7
|
+
const { logger } = require('../storages/logger');
|
|
7
8
|
|
|
8
9
|
if (!global.codeceptjs) {
|
|
9
10
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
@@ -58,14 +59,47 @@ function CodeceptReporter(config) {
|
|
|
58
59
|
// clear tmp dir
|
|
59
60
|
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
60
61
|
|
|
61
|
-
recorder.add('Creating new run', () =>
|
|
62
|
+
// recorder.add('Creating new run', () => );
|
|
63
|
+
client.createRun();
|
|
62
64
|
videos = [];
|
|
63
65
|
traces = [];
|
|
64
66
|
|
|
65
67
|
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
66
68
|
});
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
let hookSteps = [];
|
|
71
|
+
let suiteHookRunning = false;
|
|
72
|
+
|
|
73
|
+
event.dispatcher.on(event.suite.before, suite => {
|
|
74
|
+
suiteHookRunning = true;
|
|
75
|
+
hookSteps = [];
|
|
76
|
+
global.testomatioDataStore.steps = [];
|
|
77
|
+
|
|
78
|
+
logger.setContext(suite.fullTitle());
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
event.dispatcher.on(event.suite.after, () => {
|
|
82
|
+
logger.setContext(null);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
event.dispatcher.on(event.hook.started, () => {
|
|
86
|
+
// global.testomatioDataStore.steps = [];
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
event.dispatcher.on(event.hook.passed, () => {
|
|
90
|
+
if (suiteHookRunning) hookSteps.push(...global.testomatioDataStore.steps);
|
|
91
|
+
if (suiteHookRunning) logger.setContext(null);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
event.dispatcher.on(event.hook.failed, () => {
|
|
95
|
+
if (suiteHookRunning) hookSteps.push(...global.testomatioDataStore.steps);
|
|
96
|
+
if (suiteHookRunning) logger.setContext(null);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
event.dispatcher.on(event.test.before, test => {
|
|
100
|
+
suiteHookRunning = false;
|
|
101
|
+
global.testomatioDataStore.steps = [];
|
|
102
|
+
|
|
69
103
|
recorder.add(() => {
|
|
70
104
|
currentMetaStep = [];
|
|
71
105
|
// output.reset();
|
|
@@ -76,11 +110,15 @@ function CodeceptReporter(config) {
|
|
|
76
110
|
if (!global.testomatioDataStore) global.testomatioDataStore = {};
|
|
77
111
|
// reset steps
|
|
78
112
|
global.testomatioDataStore.steps = [];
|
|
113
|
+
|
|
114
|
+
logger.setContext(test.fullTitle());
|
|
79
115
|
});
|
|
80
116
|
|
|
81
117
|
event.dispatcher.on(event.test.started, test => {
|
|
118
|
+
logger.setContext(test.fullTitle());
|
|
82
119
|
testTimeMap[test.id] = Date.now();
|
|
83
120
|
if (global.testomatioDataStore) global.testomatioDataStore.currentlyRunningTestId = getIdFromTestTitle(test.title);
|
|
121
|
+
// start logging
|
|
84
122
|
});
|
|
85
123
|
|
|
86
124
|
event.dispatcher.on(event.all.result, async () => {
|
|
@@ -104,6 +142,9 @@ function CodeceptReporter(config) {
|
|
|
104
142
|
}
|
|
105
143
|
const testId = parseTest(tags);
|
|
106
144
|
const testObj = getTestAndMessage(title);
|
|
145
|
+
|
|
146
|
+
const logs = getTestLogs(test);
|
|
147
|
+
|
|
107
148
|
client.addTestRun(STATUS.PASSED, {
|
|
108
149
|
...stripExampleFromTitle(title),
|
|
109
150
|
suite_title: test.parent && test.parent.title,
|
|
@@ -111,6 +152,7 @@ function CodeceptReporter(config) {
|
|
|
111
152
|
time: getDuration(test),
|
|
112
153
|
steps: global.testomatioDataStore.steps.join('\n') || null,
|
|
113
154
|
test_id: testId,
|
|
155
|
+
logs,
|
|
114
156
|
});
|
|
115
157
|
// output.stop();
|
|
116
158
|
});
|
|
@@ -152,6 +194,9 @@ function CodeceptReporter(config) {
|
|
|
152
194
|
if (artifacts.screenshot) files.push({ path: artifacts.screenshot, type: 'image/png' });
|
|
153
195
|
// todo: video must be uploaded later....
|
|
154
196
|
|
|
197
|
+
const logs = getTestLogs(test);
|
|
198
|
+
logger.setContext(null);
|
|
199
|
+
|
|
155
200
|
const reportTestPromise = client
|
|
156
201
|
.addTestRun(STATUS.FAILED, {
|
|
157
202
|
...stripExampleFromTitle(title),
|
|
@@ -162,6 +207,7 @@ function CodeceptReporter(config) {
|
|
|
162
207
|
time: getDuration(test),
|
|
163
208
|
files,
|
|
164
209
|
steps: global.testomatioDataStore?.steps?.join('\n') || null,
|
|
210
|
+
logs,
|
|
165
211
|
})
|
|
166
212
|
.then(pipes => {
|
|
167
213
|
testId = pipes.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
@@ -230,7 +276,7 @@ function CodeceptReporter(config) {
|
|
|
230
276
|
currentMetaStep = metaSteps;
|
|
231
277
|
stepShift = 2 * shift;
|
|
232
278
|
|
|
233
|
-
const durationMs = +new Date() -
|
|
279
|
+
const durationMs = +new Date() - +stepStart;
|
|
234
280
|
let duration = '';
|
|
235
281
|
if (durationMs) {
|
|
236
282
|
duration = repeat(1) + chalk.grey(`(${durationMs}ms)`);
|
|
@@ -255,7 +301,7 @@ async function uploadAttachments(client, attachments, messagePrefix, attachmentT
|
|
|
255
301
|
if (attachments.length > 0) {
|
|
256
302
|
console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType}/-s ...`);
|
|
257
303
|
|
|
258
|
-
const promises = attachments.map(async
|
|
304
|
+
const promises = attachments.map(async attachment => {
|
|
259
305
|
const { testId, title, path, type } = attachment;
|
|
260
306
|
const file = { path, type, title };
|
|
261
307
|
return client.addTestRun(undefined, {
|
|
@@ -303,4 +349,21 @@ function repeat(num) {
|
|
|
303
349
|
return ''.padStart(num, ' ');
|
|
304
350
|
}
|
|
305
351
|
|
|
352
|
+
// TODO: think about moving to some common utils
|
|
353
|
+
function getTestLogs(test) {
|
|
354
|
+
const suiteLogsArr = logger.getLogs(test.parent.fullTitle());
|
|
355
|
+
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
356
|
+
const testLogsArr = logger.getLogs(test.fullTitle());
|
|
357
|
+
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
358
|
+
|
|
359
|
+
let logs = '';
|
|
360
|
+
if (suiteLogs) {
|
|
361
|
+
logs += `${chalk.bold('\t--- BeforeSuite ---')}\n${suiteLogs}`;
|
|
362
|
+
}
|
|
363
|
+
if (testLogs) {
|
|
364
|
+
logs += `\n${chalk.bold('\t--- Test ---')}\n${testLogs}`;
|
|
365
|
+
}
|
|
366
|
+
return logs;
|
|
367
|
+
}
|
|
368
|
+
|
|
306
369
|
module.exports = CodeceptReporter;
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -4,9 +4,19 @@ const chalk = require('chalk');
|
|
|
4
4
|
const TestomatClient = require('../client');
|
|
5
5
|
const { STATUS, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
6
6
|
const { parseTest, fileSystem } = require('../utils/utils');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const { logger } = require('../storages/logger');
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
EVENT_RUN_BEGIN,
|
|
11
|
+
EVENT_RUN_END,
|
|
12
|
+
EVENT_TEST_FAIL,
|
|
13
|
+
EVENT_TEST_PASS,
|
|
14
|
+
EVENT_TEST_PENDING,
|
|
15
|
+
EVENT_SUITE_BEGIN,
|
|
16
|
+
EVENT_SUITE_END,
|
|
17
|
+
EVENT_TEST_BEGIN,
|
|
18
|
+
EVENT_TEST_END,
|
|
19
|
+
} = Mocha.Runner.constants;
|
|
10
20
|
|
|
11
21
|
function MochaReporter(runner, opts) {
|
|
12
22
|
Mocha.reporters.Base.call(this, runner);
|
|
@@ -25,19 +35,34 @@ function MochaReporter(runner, opts) {
|
|
|
25
35
|
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
26
36
|
});
|
|
27
37
|
|
|
38
|
+
runner.on(EVENT_SUITE_BEGIN, async suite => {
|
|
39
|
+
logger.setContext(suite.fullTitle());
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
runner.on(EVENT_SUITE_END, async () => {
|
|
43
|
+
logger.setContext(null);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
runner.on(EVENT_TEST_BEGIN, async test => {
|
|
47
|
+
logger.setContext(test.fullTitle());
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
runner.on(EVENT_TEST_END, async () => {
|
|
51
|
+
logger.setContext(null);
|
|
52
|
+
});
|
|
53
|
+
|
|
28
54
|
runner.on(EVENT_TEST_PASS, async test => {
|
|
29
55
|
passes += 1;
|
|
30
|
-
|
|
31
56
|
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
32
57
|
const testId = parseTest(test.title);
|
|
33
58
|
|
|
59
|
+
const logs = getTestLogs(test);
|
|
60
|
+
|
|
34
61
|
client.addTestRun(STATUS.PASSED, {
|
|
35
62
|
test_id: testId,
|
|
36
|
-
|
|
37
|
-
title: getTestName(test),
|
|
38
|
-
code: test.body.toString(),
|
|
39
|
-
file: getFile(test),
|
|
63
|
+
title: test.title,
|
|
40
64
|
time: test.duration,
|
|
65
|
+
logs,
|
|
41
66
|
});
|
|
42
67
|
});
|
|
43
68
|
|
|
@@ -46,10 +71,7 @@ function MochaReporter(runner, opts) {
|
|
|
46
71
|
console.log('skip: %s', test.fullTitle());
|
|
47
72
|
const testId = parseTest(test.title);
|
|
48
73
|
client.addTestRun(STATUS.SKIPPED, {
|
|
49
|
-
title:
|
|
50
|
-
suite_title: getSuiteTitle(test),
|
|
51
|
-
code: test.body.toString(),
|
|
52
|
-
file: getFile(test),
|
|
74
|
+
title: test.title,
|
|
53
75
|
test_id: testId,
|
|
54
76
|
time: test.duration,
|
|
55
77
|
});
|
|
@@ -60,14 +82,14 @@ function MochaReporter(runner, opts) {
|
|
|
60
82
|
console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
|
|
61
83
|
const testId = parseTest(test.title);
|
|
62
84
|
|
|
85
|
+
const logs = getTestLogs(test);
|
|
86
|
+
|
|
63
87
|
client.addTestRun(STATUS.FAILED, {
|
|
64
88
|
error: err,
|
|
65
|
-
suite_title: getSuiteTitle(test),
|
|
66
|
-
file: getFile(test),
|
|
67
89
|
test_id: testId,
|
|
68
|
-
title:
|
|
69
|
-
code: test.body.toString(),
|
|
90
|
+
title: test.title,
|
|
70
91
|
time: test.duration,
|
|
92
|
+
logs,
|
|
71
93
|
});
|
|
72
94
|
});
|
|
73
95
|
|
|
@@ -78,27 +100,23 @@ function MochaReporter(runner, opts) {
|
|
|
78
100
|
});
|
|
79
101
|
}
|
|
80
102
|
|
|
103
|
+
function getTestLogs(test) {
|
|
104
|
+
const suiteLogsArr = logger.getLogs(test.parent.fullTitle());
|
|
105
|
+
const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
|
|
106
|
+
const testLogsArr = logger.getLogs(test.fullTitle());
|
|
107
|
+
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
108
|
+
|
|
109
|
+
let logs = '';
|
|
110
|
+
if (suiteLogs) {
|
|
111
|
+
logs += `${chalk.bold('\t--- BeforeSuite ---')}\n${suiteLogs}`;
|
|
112
|
+
}
|
|
113
|
+
if (testLogs) {
|
|
114
|
+
logs += `\n${chalk.bold('\t--- Test ---')}\n${testLogs}`;
|
|
115
|
+
}
|
|
116
|
+
return logs;
|
|
117
|
+
}
|
|
118
|
+
|
|
81
119
|
// To have this reporter "extend" a built-in reporter uncomment the following line:
|
|
82
120
|
Mocha.utils.inherits(MochaReporter, Mocha.reporters.Spec);
|
|
83
121
|
|
|
84
122
|
module.exports = MochaReporter;
|
|
85
|
-
|
|
86
|
-
function getSuiteTitle(test, pathArr = []) {
|
|
87
|
-
let root = pathArr.length === 0;
|
|
88
|
-
|
|
89
|
-
if (test.parent.parent) getSuiteTitle(test.parent, pathArr);
|
|
90
|
-
|
|
91
|
-
pathArr.push(test.parent.title);
|
|
92
|
-
|
|
93
|
-
if (root) console.trace(pathArr.filter(t => !!t));
|
|
94
|
-
return pathArr.filter(t => !!t)[0];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function getFile(test) {
|
|
98
|
-
return test.parent.file?.replace(process.cwd(), '');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function getTestName(test) {
|
|
102
|
-
if (process.env.TESTOMATIO_CREATE === 'fulltitle') return test.fullTitle();
|
|
103
|
-
return test.title;
|
|
104
|
-
}
|
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;
|
|
@@ -131,42 +130,31 @@ class Client {
|
|
|
131
130
|
steps,
|
|
132
131
|
code = null,
|
|
133
132
|
title,
|
|
134
|
-
file,
|
|
135
133
|
suite_title,
|
|
136
134
|
suite_id,
|
|
137
135
|
test_id,
|
|
138
136
|
} = testData;
|
|
139
137
|
let { message = '' } = testData;
|
|
140
138
|
|
|
141
|
-
const uploadedFiles = [];
|
|
142
|
-
|
|
143
|
-
let stack = '';
|
|
144
139
|
|
|
140
|
+
let errorFormatted = '';
|
|
145
141
|
if (error) {
|
|
146
|
-
|
|
142
|
+
errorFormatted += this.formatError(error) || '';
|
|
147
143
|
message = error?.message;
|
|
148
144
|
}
|
|
149
|
-
if (steps) {
|
|
150
|
-
stack = this.formatSteps(stack, steps);
|
|
151
|
-
}
|
|
152
145
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// ATTACH LOGS from storage
|
|
156
|
-
// in some cases (e.g. using cucumber) logger instance becomes empty object, if import at the top of the file
|
|
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}` : '';
|
|
146
|
+
// Attach logs
|
|
147
|
+
const fullLogs = this.formatLogs({ error: errorFormatted, steps, logs: testData.logs });
|
|
162
148
|
|
|
163
149
|
// GET ARTIFACTS from storage
|
|
164
|
-
const artifactFiles = artifactStorage.get(test_id);
|
|
165
|
-
if (artifactFiles) files.push(...artifactFiles);
|
|
150
|
+
// const artifactFiles = artifactStorage.get(test_id);
|
|
151
|
+
// if (artifactFiles) files.push(...artifactFiles);
|
|
166
152
|
|
|
167
|
-
// GET KEY-VALUEs from storage
|
|
168
|
-
const keyValueStorage = require('./storages/key-value-storage');
|
|
169
|
-
const keyValues = keyValueStorage.get(test_id);
|
|
153
|
+
// // GET KEY-VALUEs from storage
|
|
154
|
+
// const keyValueStorage = require('./storages/key-value-storage');
|
|
155
|
+
// const keyValues = keyValueStorage.get(test_id);
|
|
156
|
+
|
|
157
|
+
const uploadedFiles = [];
|
|
170
158
|
|
|
171
159
|
for (const file of files) {
|
|
172
160
|
uploadedFiles.push(upload.uploadFileByPath(file, this.uuid));
|
|
@@ -187,9 +175,8 @@ class Client {
|
|
|
187
175
|
files,
|
|
188
176
|
steps,
|
|
189
177
|
status,
|
|
190
|
-
stack,
|
|
178
|
+
stack: fullLogs,
|
|
191
179
|
example,
|
|
192
|
-
file,
|
|
193
180
|
code,
|
|
194
181
|
title,
|
|
195
182
|
suite_title,
|
|
@@ -198,9 +185,11 @@ class Client {
|
|
|
198
185
|
message,
|
|
199
186
|
run_time: parseFloat(time),
|
|
200
187
|
artifacts,
|
|
201
|
-
meta: keyValues,
|
|
188
|
+
// meta: keyValues,
|
|
202
189
|
};
|
|
203
190
|
|
|
191
|
+
debug('Adding test run...', data);
|
|
192
|
+
|
|
204
193
|
this.queue = this.queue.then(() =>
|
|
205
194
|
Promise.all(
|
|
206
195
|
this.pipes.map(async p => {
|
|
@@ -252,15 +241,27 @@ class Client {
|
|
|
252
241
|
return this.queue;
|
|
253
242
|
}
|
|
254
243
|
|
|
255
|
-
|
|
256
|
-
|
|
244
|
+
/**
|
|
245
|
+
* Returns the formatted stack including the stack trace, steps, and logs.
|
|
246
|
+
* @returns {string}
|
|
247
|
+
*/
|
|
248
|
+
formatLogs({ error, steps, logs }) {
|
|
249
|
+
error = error?.trim();
|
|
250
|
+
steps = steps?.trim();
|
|
251
|
+
logs = logs?.trim();
|
|
252
|
+
|
|
253
|
+
let testLogs = '';
|
|
254
|
+
if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}`;
|
|
255
|
+
if (logs) testLogs += `\n\n${chalk.bold.gray('################[ Logs ]################')}\n${logs}`;
|
|
256
|
+
if (error) testLogs += `\n\n${chalk.bold.red('################[ Failure ]################')}\n${error}`;
|
|
257
|
+
return testLogs;
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
formatError(error, message) {
|
|
260
261
|
if (!message) message = error.message;
|
|
261
262
|
if (error.inspect) message = error.inspect() || '';
|
|
262
263
|
|
|
263
|
-
let stack =
|
|
264
|
+
let stack = `${message}\n`;
|
|
264
265
|
|
|
265
266
|
// diffs for mocha, cypress, codeceptjs style
|
|
266
267
|
if (error.actual && error.expected) {
|
|
@@ -277,11 +278,11 @@ class Client {
|
|
|
277
278
|
const record = createCallsiteRecord({
|
|
278
279
|
forError: error,
|
|
279
280
|
isCallsiteFrame: frame => {
|
|
280
|
-
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
281
|
+
if (customFilter && minimatch(frame.getFileName(), customFilter)) return false;
|
|
281
282
|
if (hasFrame) return false;
|
|
282
283
|
if (isNotInternalFrame(frame)) hasFrame = true;
|
|
283
284
|
return hasFrame;
|
|
284
|
-
}
|
|
285
|
+
},
|
|
285
286
|
});
|
|
286
287
|
if (record && !record.filename.startsWith('http')) {
|
|
287
288
|
stack += record.renderSync({ stackFilter: isNotInternalFrame });
|
|
@@ -294,9 +295,11 @@ class Client {
|
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
function isNotInternalFrame(frame) {
|
|
297
|
-
return
|
|
298
|
+
return (
|
|
299
|
+
frame.getFileName().includes(sep) &&
|
|
298
300
|
!frame.getFileName().includes('node_modules') &&
|
|
299
301
|
!frame.getFileName().includes('internal')
|
|
302
|
+
);
|
|
300
303
|
}
|
|
301
304
|
|
|
302
305
|
module.exports = Client;
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -175,14 +175,13 @@ class TestomatioPipe {
|
|
|
175
175
|
* @returns
|
|
176
176
|
*/
|
|
177
177
|
addTest(data) {
|
|
178
|
+
debug('Adding test...');
|
|
178
179
|
if (!this.isEnabled) return;
|
|
179
180
|
if (!this.runId) return;
|
|
180
181
|
data.api_key = this.apiKey;
|
|
181
182
|
data.create = this.createNewTests;
|
|
182
183
|
const json = JsonCycle.stringify(data);
|
|
183
184
|
|
|
184
|
-
debug('Adding test', json);
|
|
185
|
-
|
|
186
185
|
return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, {
|
|
187
186
|
maxContentLength: Infinity,
|
|
188
187
|
maxBodyLength: Infinity,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const logger = require('./storages/logger');
|
|
2
|
-
const artifactStorage = require('./storages/artifact-storage');
|
|
3
|
-
const keyValueStorage = require('./storages/key-value-storage');
|
|
1
|
+
const { logger } = require('./storages/logger');
|
|
2
|
+
const { artifactStorage } = require('./storages/artifact-storage');
|
|
3
|
+
const { keyValueStorage } = require('./storages/key-value-storage');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Stores path to file as artifact and uploads it to the S3 storage
|
|
@@ -21,7 +21,7 @@ function logMessage(...args) {
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Similar to "log" function but marks message in report as a step
|
|
24
|
-
* @param {*} message
|
|
24
|
+
* @param {*} message
|
|
25
25
|
*/
|
|
26
26
|
function addStep(message) {
|
|
27
27
|
logger.step(message);
|
|
@@ -29,15 +29,26 @@ function addStep(message) {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Add key-value pair(s) to the test report
|
|
32
|
-
* @param {*} keyValue
|
|
32
|
+
* @param {*} keyValue
|
|
33
33
|
*/
|
|
34
34
|
function setKeyValue(keyValue) {
|
|
35
35
|
keyValueStorage.put(keyValue);
|
|
36
36
|
}
|
|
37
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);
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
module.exports = {
|
|
39
49
|
artifact: saveArtifact,
|
|
40
50
|
log: logMessage,
|
|
41
51
|
step: addStep,
|
|
42
52
|
keyValue: setKeyValue,
|
|
53
|
+
_setContext,
|
|
43
54
|
};
|
package/lib/reporter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const logger = require('./storages/logger');
|
|
1
|
+
const { logger } = require('./storages/logger');
|
|
2
2
|
const TestomatClient = require('./client');
|
|
3
3
|
const TRConstants = require('./constants');
|
|
4
4
|
|
|
@@ -20,4 +20,5 @@ module.exports.testomat = {
|
|
|
20
20
|
log: reporterFunctions.log,
|
|
21
21
|
step: reporterFunctions.step,
|
|
22
22
|
meta: reporterFunctions.keyValue,
|
|
23
|
+
_setContext: reporterFunctions._setContext,
|
|
23
24
|
};
|
|
@@ -5,24 +5,36 @@ const DataStorage = require('./data-storage');
|
|
|
5
5
|
* Artifact storage is supposed to store file paths
|
|
6
6
|
*/
|
|
7
7
|
class ArtifactStorage {
|
|
8
|
+
static #instance;
|
|
9
|
+
|
|
10
|
+
#context;
|
|
8
11
|
|
|
9
12
|
// there is autocompletion for the class methods if implemented singleton this way
|
|
10
13
|
constructor() {
|
|
11
14
|
this.dataStorage = new DataStorage('artifact');
|
|
12
|
-
|
|
15
|
+
|
|
13
16
|
// singleton
|
|
14
|
-
if (!ArtifactStorage
|
|
15
|
-
ArtifactStorage
|
|
17
|
+
if (!ArtifactStorage.#instance) {
|
|
18
|
+
ArtifactStorage.#instance = this;
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Singleton
|
|
24
|
+
* @returns {ArtifactStorage}
|
|
25
|
+
*/
|
|
20
26
|
static getInstance() {
|
|
21
|
-
if (!
|
|
22
|
-
|
|
27
|
+
if (!this.#instance) {
|
|
28
|
+
this.#instance = new ArtifactStorage();
|
|
23
29
|
}
|
|
30
|
+
return this.#instance;
|
|
31
|
+
}
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} context - suite title + test title
|
|
35
|
+
*/
|
|
36
|
+
setContext(context) {
|
|
37
|
+
this.#context = context;
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
/**
|
|
@@ -31,6 +43,7 @@ class ArtifactStorage {
|
|
|
31
43
|
* @param {*} context testId or test title
|
|
32
44
|
*/
|
|
33
45
|
put(data, context = null) {
|
|
46
|
+
context = context || this.#context;
|
|
34
47
|
if (!data) return;
|
|
35
48
|
debug('Save artifact:', data);
|
|
36
49
|
this.dataStorage.putData(data, context);
|
|
@@ -63,8 +76,4 @@ class ArtifactStorage {
|
|
|
63
76
|
}
|
|
64
77
|
}
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// const artifactStorage = ArtifactStorage.getInstance();
|
|
69
|
-
const artifactStorage = new ArtifactStorage();
|
|
70
|
-
module.exports = artifactStorage;
|
|
79
|
+
module.exports.artifactStorage = ArtifactStorage.getInstance();
|