@testomatio/reporter 2.1.0-beta-nightwatch → 2.1.0-beta.2-codeceptjs
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/lib/adapter/codecept.js +288 -202
- package/lib/adapter/cypress-plugin/index.js +0 -2
- package/lib/adapter/mocha.js +0 -1
- package/lib/adapter/nightwatch.js +5 -5
- package/lib/adapter/playwright.js +11 -3
- package/lib/adapter/webdriver.d.ts +1 -1
- package/lib/adapter/webdriver.js +18 -8
- package/lib/bin/cli.js +73 -8
- package/lib/bin/reportXml.js +4 -2
- package/lib/bin/startTest.js +3 -2
- package/lib/bin/uploadArtifacts.js +5 -4
- package/lib/client.js +31 -10
- package/lib/data-storage.d.ts +5 -5
- package/lib/data-storage.js +23 -13
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/pipe/bitbucket.d.ts +2 -0
- package/lib/pipe/bitbucket.js +38 -26
- package/lib/pipe/debug.js +27 -6
- package/lib/pipe/github.d.ts +2 -2
- package/lib/pipe/github.js +35 -3
- package/lib/pipe/gitlab.d.ts +2 -0
- package/lib/pipe/gitlab.js +27 -9
- package/lib/pipe/html.js +0 -3
- package/lib/pipe/index.js +17 -7
- package/lib/pipe/testomatio.d.ts +3 -2
- package/lib/pipe/testomatio.js +85 -75
- package/lib/replay.d.ts +31 -0
- package/lib/replay.js +259 -0
- package/lib/reporter-functions.d.ts +7 -0
- package/lib/reporter-functions.js +36 -0
- package/lib/reporter.d.ts +15 -12
- package/lib/reporter.js +4 -1
- package/lib/services/artifacts.d.ts +1 -1
- package/lib/services/index.d.ts +2 -0
- package/lib/services/index.js +2 -0
- package/lib/services/key-values.d.ts +1 -1
- package/lib/services/labels.d.ts +22 -0
- package/lib/services/labels.js +62 -0
- package/lib/services/logger.d.ts +1 -1
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +443 -68
- package/lib/uploader.js +10 -6
- package/lib/utils/constants.d.ts +12 -0
- package/lib/utils/constants.js +15 -0
- package/lib/utils/utils.d.ts +10 -1
- package/lib/utils/utils.js +70 -22
- package/lib/xmlReader.js +57 -19
- package/package.json +16 -11
- package/src/adapter/codecept.js +320 -214
- package/src/adapter/cypress-plugin/index.js +0 -2
- package/src/adapter/mocha.js +0 -1
- package/src/adapter/nightwatch.js +1 -1
- package/src/adapter/playwright.js +10 -7
- package/src/adapter/webdriver.js +13 -5
- package/src/bin/cli.js +78 -7
- package/src/bin/reportXml.js +4 -1
- package/src/bin/startTest.js +2 -1
- package/src/bin/uploadArtifacts.js +2 -1
- package/src/client.js +28 -5
- package/src/data-storage.js +6 -6
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +22 -24
- package/src/pipe/debug.js +26 -5
- package/src/pipe/github.js +1 -2
- package/src/pipe/gitlab.js +27 -9
- package/src/pipe/html.js +1 -4
- package/src/pipe/testomatio.js +112 -107
- package/src/replay.js +268 -0
- package/src/reporter-functions.js +41 -0
- package/src/reporter.js +3 -0
- package/src/services/index.js +2 -0
- package/src/services/labels.js +59 -0
- package/src/services/logger.js +1 -2
- package/src/template/testomatio.hbs +443 -68
- package/src/uploader.js +11 -6
- package/src/utils/constants.js +12 -0
- package/src/utils/utils.js +67 -15
- package/src/xmlReader.js +73 -18
package/README.md
CHANGED
|
@@ -133,6 +133,7 @@ Bring this reporter on CI and never lose test results again!
|
|
|
133
133
|
- 🗄️ [Artifacts](./docs/artifacts.md)
|
|
134
134
|
- 🔂 [Workflows](./docs/workflows.md)
|
|
135
135
|
- 🖊️ [Logger](./docs/logger.md)
|
|
136
|
+
- 🪲 [Debug File Format](./docs/debug-file-format.md)
|
|
136
137
|
|
|
137
138
|
## Development
|
|
138
139
|
|
package/lib/adapter/codecept.js
CHANGED
|
@@ -10,7 +10,7 @@ const client_js_1 = __importDefault(require("../client.js"));
|
|
|
10
10
|
const constants_js_1 = require("../constants.js");
|
|
11
11
|
const utils_js_1 = require("../utils/utils.js");
|
|
12
12
|
const index_js_1 = require("../services/index.js");
|
|
13
|
-
|
|
13
|
+
const data_storage_js_1 = require("../data-storage.js");
|
|
14
14
|
const codeceptjs_1 = __importDefault(require("codeceptjs"));
|
|
15
15
|
const debug = (0, debug_1.default)('@testomatio/reporter:adapter:codeceptjs');
|
|
16
16
|
// @ts-ignore
|
|
@@ -19,257 +19,152 @@ if (!global.codeceptjs) {
|
|
|
19
19
|
global.codeceptjs = codeceptjs_1.default;
|
|
20
20
|
}
|
|
21
21
|
// @ts-ignore
|
|
22
|
-
const { event, recorder, codecept } = global.codeceptjs;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
let stepStart = new Date();
|
|
30
|
-
const MAJOR_VERSION = parseInt(codecept.version().match(/\d/)[0], 10);
|
|
22
|
+
const { event, recorder, codecept, output } = global.codeceptjs;
|
|
23
|
+
const [, MAJOR_VERSION, MINOR_VERSION] = codecept.version().match(/(\d+)\.(\d+)/).map(Number);
|
|
24
|
+
// Constants for hook execution order
|
|
25
|
+
const HOOK_EXECUTION_ORDER = {
|
|
26
|
+
PRE_TEST: ['BeforeSuiteHook', 'BeforeHook'],
|
|
27
|
+
POST_TEST: ['AfterHook', 'AfterSuiteHook']
|
|
28
|
+
};
|
|
31
29
|
const DATA_REGEXP = /[|\s]+?(\{".*\}|\[.*\])/;
|
|
32
30
|
if (MAJOR_VERSION < 3) {
|
|
33
31
|
console.log('🔴 This reporter works with CodeceptJS 3+, please update your tests');
|
|
34
32
|
}
|
|
33
|
+
if (MAJOR_VERSION === 3 && MINOR_VERSION < 7) {
|
|
34
|
+
console.log('🔴 CodeceptJS 3.7+ is supported, please upgrade CodeceptJS or use 1.6 version of `@testomatio/reporter`');
|
|
35
|
+
}
|
|
35
36
|
function CodeceptReporter(config) {
|
|
36
|
-
|
|
37
|
+
const failedTests = [];
|
|
37
38
|
let videos = [];
|
|
38
39
|
let traces = [];
|
|
39
40
|
const reportTestPromises = [];
|
|
40
41
|
const testTimeMap = {};
|
|
41
42
|
const { apiKey } = config;
|
|
42
|
-
const getDuration = test => {
|
|
43
|
-
if (!test.uid)
|
|
44
|
-
return 0;
|
|
45
|
-
if (testTimeMap[test.uid]) {
|
|
46
|
-
return Date.now() - testTimeMap[test.uid];
|
|
47
|
-
}
|
|
48
|
-
return 0;
|
|
49
|
-
};
|
|
50
43
|
const client = new client_js_1.default({ apiKey });
|
|
44
|
+
// Store original output methods for fallback
|
|
45
|
+
const originalOutput = {
|
|
46
|
+
debug: output.debug,
|
|
47
|
+
log: output.log,
|
|
48
|
+
step: output.step,
|
|
49
|
+
say: output.say,
|
|
50
|
+
};
|
|
51
|
+
output.debug = function (msg) {
|
|
52
|
+
originalOutput.debug(msg);
|
|
53
|
+
data_storage_js_1.dataStorage.putData('log', repeat(this.stepShift) + picocolors_1.default.cyan(msg.toString()));
|
|
54
|
+
};
|
|
55
|
+
output.say = function (message, color = 'cyan') {
|
|
56
|
+
originalOutput.say(message, color);
|
|
57
|
+
const sayMsg = repeat(this.stepShift) + ` ${picocolors_1.default.bold(picocolors_1.default[color](message))}`;
|
|
58
|
+
data_storage_js_1.dataStorage.putData('log', sayMsg);
|
|
59
|
+
};
|
|
60
|
+
output.log = function (msg) {
|
|
61
|
+
originalOutput.log(msg);
|
|
62
|
+
data_storage_js_1.dataStorage.putData('log', repeat(this.stepShift) + picocolors_1.default.gray(msg));
|
|
63
|
+
};
|
|
51
64
|
recorder.startUnlessRunning();
|
|
65
|
+
const hookSteps = new Map();
|
|
66
|
+
let currentHook = null;
|
|
67
|
+
event.dispatcher.on(event.workers.before, () => {
|
|
68
|
+
recorder.add('Creating new run', async () => {
|
|
69
|
+
await client.createRun();
|
|
70
|
+
process.env.TESTOMATIO_RUN = client.runId;
|
|
71
|
+
process.env.TESTOMATIO_PROCEED = 'true';
|
|
72
|
+
debug('Run ID:', client.runId);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
event.dispatcher.on(event.workers.after, () => {
|
|
76
|
+
client.updateRunStatus('finished');
|
|
77
|
+
});
|
|
52
78
|
// Listening to events
|
|
53
79
|
event.dispatcher.on(event.all.before, () => {
|
|
54
80
|
// clear tmp dir
|
|
55
|
-
|
|
81
|
+
// fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
56
82
|
// recorder.add('Creating new run', () => );
|
|
57
|
-
|
|
83
|
+
recorder.add('Creating new run', () => {
|
|
84
|
+
return client.createRun();
|
|
85
|
+
});
|
|
58
86
|
videos = [];
|
|
59
87
|
traces = [];
|
|
60
88
|
if (!global.testomatioDataStore)
|
|
61
89
|
global.testomatioDataStore = {};
|
|
62
90
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
// Hook event listeners
|
|
92
|
+
event.dispatcher.on(event.hook.started, (hook) => {
|
|
93
|
+
output.stepShift = 2;
|
|
94
|
+
currentHook = hook.name;
|
|
95
|
+
let title = hook.hookName;
|
|
96
|
+
if (hook.suite)
|
|
97
|
+
title += ' ' + hook.suite.fullTitle();
|
|
98
|
+
if (hook.test)
|
|
99
|
+
title += ' ' + hook.test.fullTitle();
|
|
100
|
+
if (hook.ctx.currentTest)
|
|
101
|
+
title += ' ' + hook.ctx.currentTest.fullTitle();
|
|
102
|
+
index_js_1.services.setContext(title);
|
|
103
|
+
hookSteps.set(hook.name, []);
|
|
70
104
|
});
|
|
71
|
-
event.dispatcher.on(event.
|
|
105
|
+
event.dispatcher.on(event.hook.finished, () => {
|
|
106
|
+
currentHook = null;
|
|
107
|
+
output.stepShift = 2;
|
|
72
108
|
index_js_1.services.setContext(null);
|
|
73
109
|
});
|
|
74
|
-
event.dispatcher.on(event.
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
event.dispatcher.on(event.hook.passed, () => {
|
|
78
|
-
if (suiteHookRunning) {
|
|
79
|
-
hookSteps.push(...global.testomatioDataStore.steps);
|
|
80
|
-
index_js_1.services.setContext(null);
|
|
81
|
-
}
|
|
110
|
+
event.dispatcher.on(event.suite.before, suite => {
|
|
111
|
+
data_storage_js_1.dataStorage.setContext(suite.fullTitle());
|
|
82
112
|
});
|
|
83
|
-
event.dispatcher.on(event.
|
|
84
|
-
|
|
85
|
-
hookSteps.push(...global.testomatioDataStore.steps);
|
|
86
|
-
index_js_1.services.setContext(null);
|
|
87
|
-
}
|
|
113
|
+
event.dispatcher.on(event.suite.after, () => {
|
|
114
|
+
index_js_1.services.setContext(null);
|
|
88
115
|
});
|
|
89
116
|
event.dispatcher.on(event.test.before, test => {
|
|
90
|
-
|
|
91
|
-
global.testomatioDataStore.steps = [];
|
|
92
|
-
recorder.add(() => {
|
|
93
|
-
currentMetaStep = [];
|
|
94
|
-
// output.reset();
|
|
95
|
-
// output.start();
|
|
96
|
-
stepShift = 0;
|
|
97
|
-
});
|
|
98
|
-
if (!global.testomatioDataStore)
|
|
99
|
-
global.testomatioDataStore = {};
|
|
100
|
-
// reset steps
|
|
101
|
-
global.testomatioDataStore.steps = [];
|
|
117
|
+
initializeTestDataStore();
|
|
102
118
|
index_js_1.services.setContext(test.fullTitle());
|
|
103
119
|
});
|
|
104
120
|
event.dispatcher.on(event.test.started, test => {
|
|
105
121
|
index_js_1.services.setContext(test.fullTitle());
|
|
106
|
-
testTimeMap[test.id] = Date.now();
|
|
107
|
-
if (!test.uid)
|
|
108
|
-
return;
|
|
109
122
|
testTimeMap[test.uid] = Date.now();
|
|
110
123
|
});
|
|
111
|
-
event.dispatcher.on(event.all.result, async () => {
|
|
124
|
+
event.dispatcher.on(event.all.result, async (result) => {
|
|
112
125
|
debug('waiting for all tests to be reported');
|
|
113
126
|
// all tests were reported and we can upload videos
|
|
114
127
|
await Promise.all(reportTestPromises);
|
|
115
128
|
await uploadAttachments(client, videos, '🎞️ Uploading', 'video');
|
|
116
129
|
await uploadAttachments(client, traces, '📁 Uploading', 'trace');
|
|
117
|
-
|
|
118
|
-
// @ts-ignore
|
|
119
|
-
client.updateRunStatus(status);
|
|
120
|
-
});
|
|
121
|
-
event.dispatcher.on(event.test.passed, test => {
|
|
122
|
-
const { uid, tags, title } = test;
|
|
123
|
-
if (uid && failedTests.includes(uid)) {
|
|
124
|
-
failedTests = failedTests.filter(failed => uid !== failed);
|
|
125
|
-
}
|
|
126
|
-
const testObj = getTestAndMessage(title);
|
|
127
|
-
const logs = getTestLogs(test);
|
|
128
|
-
const manuallyAttachedArtifacts = index_js_1.services.artifacts.get(test.fullTitle());
|
|
129
|
-
const keyValues = index_js_1.services.keyValues.get(test.fullTitle());
|
|
130
|
-
index_js_1.services.setContext(null);
|
|
131
|
-
client.addTestRun(constants_js_1.STATUS.PASSED, {
|
|
132
|
-
...stripExampleFromTitle(title),
|
|
133
|
-
rid: uid,
|
|
134
|
-
suite_title: test.parent && test.parent.title,
|
|
135
|
-
message: testObj.message,
|
|
136
|
-
time: getDuration(test),
|
|
137
|
-
steps: global.testomatioDataStore.steps.join('\n') || null,
|
|
138
|
-
test_id: (0, utils_js_1.getTestomatIdFromTestTitle)(`${title} ${tags?.join(' ')}`),
|
|
139
|
-
logs,
|
|
140
|
-
manuallyAttachedArtifacts,
|
|
141
|
-
meta: keyValues,
|
|
142
|
-
});
|
|
143
|
-
// output.stop();
|
|
144
|
-
});
|
|
145
|
-
event.dispatcher.on(event.test.failed, (test, err) => {
|
|
146
|
-
error = err;
|
|
147
|
-
});
|
|
148
|
-
event.dispatcher.on(event.hook.failed, (suite, err) => {
|
|
149
|
-
error = err;
|
|
150
|
-
if (!suite)
|
|
151
|
-
return;
|
|
152
|
-
if (!suite.tests)
|
|
153
|
-
return;
|
|
154
|
-
for (const test of suite.tests) {
|
|
155
|
-
const { uid, tags, title } = test;
|
|
156
|
-
failedTests.push(uid || title);
|
|
157
|
-
const testId = (0, utils_js_1.getTestomatIdFromTestTitle)(`${title} ${tags?.join(' ')}`);
|
|
158
|
-
client.addTestRun(constants_js_1.STATUS.FAILED, {
|
|
159
|
-
rid: uid,
|
|
160
|
-
...stripExampleFromTitle(title),
|
|
161
|
-
suite_title: suite.title,
|
|
162
|
-
test_id: testId,
|
|
163
|
-
error,
|
|
164
|
-
time: 0,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
// output.stop();
|
|
130
|
+
client.updateRunStatus('finished');
|
|
168
131
|
});
|
|
169
132
|
event.dispatcher.on(event.test.after, test => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (test.err)
|
|
173
|
-
error = test.err;
|
|
174
|
-
const { uid, tags, title, artifacts } = test;
|
|
133
|
+
const { uid, tags, title, artifacts } = test.simplify();
|
|
134
|
+
const error = test.err || null;
|
|
175
135
|
failedTests.push(uid || title);
|
|
176
136
|
const testObj = getTestAndMessage(title);
|
|
177
|
-
const files =
|
|
178
|
-
if (artifacts.screenshot)
|
|
179
|
-
files.push({ path: artifacts.screenshot, type: 'image/png' });
|
|
180
|
-
// todo: video must be uploaded later....
|
|
137
|
+
const files = buildArtifactFiles(artifacts);
|
|
181
138
|
const logs = getTestLogs(test);
|
|
182
139
|
const manuallyAttachedArtifacts = index_js_1.services.artifacts.get(test.fullTitle());
|
|
183
140
|
const keyValues = index_js_1.services.keyValues.get(test.fullTitle());
|
|
141
|
+
const stepHierarchy = buildUnifiedStepHierarchy(test.steps, hookSteps);
|
|
142
|
+
const labels = index_js_1.services.labels.get(test.fullTitle());
|
|
184
143
|
index_js_1.services.setContext(null);
|
|
185
|
-
client.addTestRun(
|
|
144
|
+
client.addTestRun(test.state, {
|
|
186
145
|
...stripExampleFromTitle(title),
|
|
187
146
|
rid: uid,
|
|
188
147
|
test_id: (0, utils_js_1.getTestomatIdFromTestTitle)(`${title} ${tags?.join(' ')}`),
|
|
189
|
-
suite_title: test.parent && test.parent.title,
|
|
148
|
+
suite_title: test.parent && stripTagsFromTitle(stripExampleFromTitle(test.parent.title).title),
|
|
190
149
|
error,
|
|
191
150
|
message: testObj.message,
|
|
192
|
-
time:
|
|
151
|
+
time: test.duration,
|
|
193
152
|
files,
|
|
194
|
-
steps:
|
|
153
|
+
steps: stepHierarchy, // Array of step objects per API schema
|
|
195
154
|
logs,
|
|
155
|
+
labels,
|
|
196
156
|
manuallyAttachedArtifacts,
|
|
197
|
-
meta: keyValues,
|
|
198
|
-
});
|
|
199
|
-
debug('artifacts', artifacts);
|
|
200
|
-
for (const aid in artifacts) {
|
|
201
|
-
if (aid.startsWith('video'))
|
|
202
|
-
videos.push({ rid: uid, title, path: artifacts[aid], type: 'video/webm' });
|
|
203
|
-
if (aid.startsWith('trace'))
|
|
204
|
-
traces.push({ rid: uid, title, path: artifacts[aid], type: 'application/zip' });
|
|
205
|
-
}
|
|
206
|
-
// output.stop();
|
|
207
|
-
});
|
|
208
|
-
event.dispatcher.on(event.test.skipped, test => {
|
|
209
|
-
const { uid, tags, title } = test;
|
|
210
|
-
if (failedTests.includes(uid || title))
|
|
211
|
-
return;
|
|
212
|
-
const testObj = getTestAndMessage(title);
|
|
213
|
-
client.addTestRun(constants_js_1.STATUS.SKIPPED, {
|
|
214
|
-
rid: uid,
|
|
215
|
-
...stripExampleFromTitle(title),
|
|
216
|
-
test_id: (0, utils_js_1.getTestomatIdFromTestTitle)(`${title} ${tags?.join(' ')}`),
|
|
217
|
-
suite_title: test.parent && test.parent.title,
|
|
218
|
-
message: testObj.message,
|
|
219
|
-
time: getDuration(test),
|
|
157
|
+
meta: { ...keyValues, ...test.meta },
|
|
220
158
|
});
|
|
221
|
-
|
|
159
|
+
processArtifactsForUpload(artifacts, uid, title, videos, traces);
|
|
222
160
|
});
|
|
223
161
|
event.dispatcher.on(event.step.started, step => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
stepStart = new Date();
|
|
162
|
+
const stepText = `${repeat(output.stepShift)} ${step.toCliStyled ? step.toCliStyled() : step.toString()}`;
|
|
163
|
+
data_storage_js_1.dataStorage.putData('log', stepText);
|
|
227
164
|
});
|
|
228
165
|
event.dispatcher.on(event.step.finished, step => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
let processingStep = step;
|
|
232
|
-
const metaSteps = [];
|
|
233
|
-
while (processingStep.metaStep) {
|
|
234
|
-
metaSteps.unshift(processingStep.metaStep);
|
|
235
|
-
processingStep = processingStep.metaStep;
|
|
236
|
-
}
|
|
237
|
-
const shift = metaSteps.length;
|
|
238
|
-
for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
|
|
239
|
-
if (currentMetaStep[i] !== metaSteps[i]) {
|
|
240
|
-
stepShift = 2 * i;
|
|
241
|
-
// eslint-disable-next-line no-continue
|
|
242
|
-
if (!metaSteps[i])
|
|
243
|
-
continue;
|
|
244
|
-
if (metaSteps[i].isBDD()) {
|
|
245
|
-
// output.push(repeat(stepShift) + pc.bold(metaSteps[i].toString()) + metaSteps[i].comment);
|
|
246
|
-
global.testomatioDataStore?.steps?.push(repeat(stepShift) + picocolors_1.default.bold(metaSteps[i].toString()) + metaSteps[i].comment);
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
// output.push(repeat(stepShift) + pc.green.bold(metaSteps[i].toString()));
|
|
250
|
-
global.testomatioDataStore?.steps?.push(repeat(stepShift) + picocolors_1.default.green(picocolors_1.default.bold(metaSteps[i].toString())));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
currentMetaStep = metaSteps;
|
|
255
|
-
stepShift = 2 * shift;
|
|
256
|
-
const durationMs = +new Date() - +stepStart;
|
|
257
|
-
let duration = '';
|
|
258
|
-
if (durationMs) {
|
|
259
|
-
duration = repeat(1) + picocolors_1.default.gray(`(${durationMs}ms)`);
|
|
260
|
-
}
|
|
261
|
-
if (step.status === constants_js_1.STATUS.FAILED) {
|
|
262
|
-
// output.push(repeat(stepShift) + pc.red(step.toString()) + duration);
|
|
263
|
-
global.testomatioDataStore?.steps?.push(repeat(stepShift) + picocolors_1.default.red(step.toString()) + duration);
|
|
264
|
-
}
|
|
265
|
-
else {
|
|
266
|
-
// output.push(repeat(stepShift) + step.toString() + duration);
|
|
267
|
-
global.testomatioDataStore?.steps?.push(repeat(stepShift) + step.toString() + duration);
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
event.dispatcher.on(event.step.comment, step => {
|
|
271
|
-
// output.push(pc.cyan.bold(step.toString()));
|
|
272
|
-
global.testomatioDataStore?.steps?.push(picocolors_1.default.cyan(picocolors_1.default.bold(step.toString())));
|
|
166
|
+
processMetaStepsForDisplay(step);
|
|
167
|
+
captureHookStep(step, currentHook, hookSteps);
|
|
273
168
|
});
|
|
274
169
|
}
|
|
275
170
|
async function uploadAttachments(client, attachments, messagePrefix, attachmentType) {
|
|
@@ -302,28 +197,219 @@ function stripExampleFromTitle(title) {
|
|
|
302
197
|
const res = title.match(DATA_REGEXP);
|
|
303
198
|
if (!res)
|
|
304
199
|
return { title, example: null };
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
200
|
+
try {
|
|
201
|
+
const example = JSON.parse(res[1]);
|
|
202
|
+
title = title.replace(DATA_REGEXP, '').trim();
|
|
203
|
+
return { title, example };
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
// If JSON parsing fails, return title without example
|
|
207
|
+
debug('Failed to parse example JSON:', res[1], e.message);
|
|
208
|
+
return { title: title.replace(DATA_REGEXP, '').trim(), example: null };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function stripTagsFromTitle(title) {
|
|
212
|
+
// Remove @tags from the end of titles (e.g., "Hooks Test Suite @hooks" -> "Hooks Test Suite")
|
|
213
|
+
return title.replace(/\s+@[\w-]+\s*$/, '').trim();
|
|
308
214
|
}
|
|
309
215
|
function repeat(num) {
|
|
310
216
|
return ''.padStart(num, ' ');
|
|
311
217
|
}
|
|
218
|
+
// Helper functions for cleaner event handling
|
|
219
|
+
function initializeTestDataStore() {
|
|
220
|
+
if (!global.testomatioDataStore)
|
|
221
|
+
global.testomatioDataStore = {};
|
|
222
|
+
global.testomatioDataStore.steps = [];
|
|
223
|
+
}
|
|
224
|
+
function buildArtifactFiles(artifacts) {
|
|
225
|
+
const files = [];
|
|
226
|
+
if (artifacts.screenshot) {
|
|
227
|
+
files.push({ path: artifacts.screenshot, type: 'image/png' });
|
|
228
|
+
}
|
|
229
|
+
return files;
|
|
230
|
+
}
|
|
231
|
+
function processArtifactsForUpload(artifacts, uid, title, videos, traces) {
|
|
232
|
+
for (const aid in artifacts) {
|
|
233
|
+
if (aid.startsWith('video')) {
|
|
234
|
+
videos.push({ rid: uid, title, path: artifacts[aid], type: 'video/webm' });
|
|
235
|
+
}
|
|
236
|
+
if (aid.startsWith('trace')) {
|
|
237
|
+
traces.push({ rid: uid, title, path: artifacts[aid], type: 'application/zip' });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function processMetaStepsForDisplay(step) {
|
|
242
|
+
const metaSteps = [];
|
|
243
|
+
let processingStep = step;
|
|
244
|
+
while (processingStep.metaStep) {
|
|
245
|
+
metaSteps.unshift(processingStep.metaStep);
|
|
246
|
+
processingStep = processingStep.metaStep;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function captureHookStep(step, currentHook, hookSteps) {
|
|
250
|
+
if (!currentHook)
|
|
251
|
+
return;
|
|
252
|
+
const startTime = step.startTime;
|
|
253
|
+
const endTime = step.endTime;
|
|
254
|
+
const hookStepsArray = hookSteps.get(currentHook) || [];
|
|
255
|
+
hookStepsArray.push({
|
|
256
|
+
name: step.name,
|
|
257
|
+
actor: step.actor,
|
|
258
|
+
args: step.args,
|
|
259
|
+
status: step.status,
|
|
260
|
+
startTime,
|
|
261
|
+
endTime,
|
|
262
|
+
helperMethod: step.helperMethod
|
|
263
|
+
});
|
|
264
|
+
hookSteps.set(currentHook, hookStepsArray);
|
|
265
|
+
}
|
|
312
266
|
// TODO: think about moving to some common utils
|
|
313
267
|
function getTestLogs(test) {
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
const
|
|
268
|
+
// Contexts for each log section
|
|
269
|
+
const suiteTitle = test.parent.fullTitle();
|
|
270
|
+
const testTitle = test.fullTitle();
|
|
271
|
+
const beforeSuiteLogsArr = index_js_1.services.logger.getLogs(`BeforeSuite ${suiteTitle}`);
|
|
272
|
+
const beforeLogsArr = index_js_1.services.logger.getLogs(`Before ${testTitle}`);
|
|
273
|
+
const testLogsArr = index_js_1.services.logger.getLogs(testTitle);
|
|
274
|
+
const afterLogsArr = index_js_1.services.logger.getLogs(`After ${testTitle}`);
|
|
275
|
+
const afterSuiteLogsArr = index_js_1.services.logger.getLogs(`AfterSuite ${suiteTitle}`);
|
|
276
|
+
const beforeSuiteLogs = beforeSuiteLogsArr ? beforeSuiteLogsArr.join('\n').trim() : '';
|
|
277
|
+
const beforeLogs = beforeLogsArr ? beforeLogsArr.join('\n').trim() : '';
|
|
317
278
|
const testLogs = testLogsArr ? testLogsArr.join('\n').trim() : '';
|
|
279
|
+
const afterLogs = afterLogsArr ? afterLogsArr.join('\n').trim() : '';
|
|
280
|
+
const afterSuiteLogs = afterSuiteLogsArr ? afterSuiteLogsArr.join('\n').trim() : '';
|
|
318
281
|
let logs = '';
|
|
319
|
-
if (
|
|
320
|
-
logs += `${picocolors_1.default.bold('
|
|
282
|
+
if (beforeSuiteLogs) {
|
|
283
|
+
logs += `${picocolors_1.default.bold('--- BeforeSuite ---')}\n${beforeSuiteLogs}`;
|
|
284
|
+
}
|
|
285
|
+
if (beforeLogs) {
|
|
286
|
+
logs += `\n${picocolors_1.default.bold('--- Before ---')}\n${beforeLogs}`;
|
|
321
287
|
}
|
|
322
288
|
if (testLogs) {
|
|
323
|
-
logs += `\n${picocolors_1.default.bold('
|
|
289
|
+
logs += `\n${picocolors_1.default.bold('--- Test ---')}\n${testLogs}`;
|
|
290
|
+
}
|
|
291
|
+
if (afterLogs) {
|
|
292
|
+
logs += `\n${picocolors_1.default.bold('--- After ---')}\n${afterLogs}`;
|
|
293
|
+
}
|
|
294
|
+
if (afterSuiteLogs) {
|
|
295
|
+
logs += `\n${picocolors_1.default.bold('--- AfterSuite ---')}\n${afterSuiteLogs}`;
|
|
324
296
|
}
|
|
325
297
|
return logs;
|
|
326
298
|
}
|
|
299
|
+
// Build step hierarchy using CodeceptJS built-in methods
|
|
300
|
+
function buildUnifiedStepHierarchy(steps, hookSteps) {
|
|
301
|
+
const hierarchy = [];
|
|
302
|
+
// Add pre-test hooks
|
|
303
|
+
addHooksToHierarchy(hierarchy, hookSteps, HOOK_EXECUTION_ORDER.PRE_TEST);
|
|
304
|
+
// Process test steps if they exist
|
|
305
|
+
if (steps && steps.length > 0) {
|
|
306
|
+
processTestSteps(steps, hierarchy);
|
|
307
|
+
}
|
|
308
|
+
// Add post-test hooks
|
|
309
|
+
addHooksToHierarchy(hierarchy, hookSteps, HOOK_EXECUTION_ORDER.POST_TEST);
|
|
310
|
+
return hierarchy;
|
|
311
|
+
}
|
|
312
|
+
function addHooksToHierarchy(hierarchy, hookSteps, hookNames) {
|
|
313
|
+
for (const hookName of hookNames) {
|
|
314
|
+
if (hookSteps.has(hookName)) {
|
|
315
|
+
const hookSection = createHookSection(hookName, hookSteps.get(hookName));
|
|
316
|
+
if (hookSection)
|
|
317
|
+
hierarchy.push(hookSection);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function processTestSteps(steps, hierarchy) {
|
|
322
|
+
const sectionMap = new Map();
|
|
323
|
+
for (const step of steps) {
|
|
324
|
+
const formattedStep = formatCodeceptStep(step);
|
|
325
|
+
if (!formattedStep)
|
|
326
|
+
continue;
|
|
327
|
+
if (step.metaStep) {
|
|
328
|
+
// Step belongs to a section (meta step)
|
|
329
|
+
const sectionKey = step.metaStep;
|
|
330
|
+
let sectionStep = sectionMap.get(sectionKey);
|
|
331
|
+
if (!sectionStep) {
|
|
332
|
+
sectionStep = createSectionStep(step.metaStep);
|
|
333
|
+
sectionMap.set(sectionKey, sectionStep);
|
|
334
|
+
hierarchy.push(sectionStep);
|
|
335
|
+
}
|
|
336
|
+
sectionStep.steps.push(formattedStep);
|
|
337
|
+
sectionStep.duration += formattedStep.duration || 0;
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
// Regular step
|
|
341
|
+
hierarchy.push(formattedStep);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function createSectionStep(metaStep) {
|
|
346
|
+
return {
|
|
347
|
+
category: 'user',
|
|
348
|
+
title: metaStep.toString(), // Use built-in toString method
|
|
349
|
+
duration: metaStep.duration || 0, // Use built-in duration
|
|
350
|
+
steps: []
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function createHookSection(hookName, steps) {
|
|
354
|
+
if (!steps || steps.length === 0)
|
|
355
|
+
return null;
|
|
356
|
+
const hookSection = {
|
|
357
|
+
category: 'hook',
|
|
358
|
+
title: formatHookName(hookName),
|
|
359
|
+
duration: 0,
|
|
360
|
+
steps: []
|
|
361
|
+
};
|
|
362
|
+
for (const step of steps) {
|
|
363
|
+
const formattedStep = formatHookStep(step);
|
|
364
|
+
if (formattedStep) {
|
|
365
|
+
hookSection.steps.push(formattedStep);
|
|
366
|
+
hookSection.duration += formattedStep.duration || 0;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return hookSection.steps.length > 0 ? hookSection : null;
|
|
370
|
+
}
|
|
371
|
+
function formatHookName(hookName) {
|
|
372
|
+
return hookName.replace(/Hook$/, '');
|
|
373
|
+
}
|
|
374
|
+
// Format CodeceptJS step using its built-in methods
|
|
375
|
+
function formatCodeceptStep(step) {
|
|
376
|
+
if (!step)
|
|
377
|
+
return null;
|
|
378
|
+
const category = step.constructor.name === 'HelperStep' ? 'framework' : 'user';
|
|
379
|
+
const title = step.toString(); // Use built-in toString
|
|
380
|
+
const duration = step.duration || 0; // Use built-in duration
|
|
381
|
+
const formattedStep = {
|
|
382
|
+
category,
|
|
383
|
+
title,
|
|
384
|
+
duration
|
|
385
|
+
};
|
|
386
|
+
// Add error if step failed
|
|
387
|
+
if (step.status === 'failed' && step.err) {
|
|
388
|
+
formattedStep.error = {
|
|
389
|
+
message: step.err.message || 'Step failed',
|
|
390
|
+
stack: step.err.stack || ''
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return formattedStep;
|
|
394
|
+
}
|
|
395
|
+
function formatHookStep(step) {
|
|
396
|
+
if (!step)
|
|
397
|
+
return null;
|
|
398
|
+
// For hook steps, construct title from available properties
|
|
399
|
+
let title = step.name;
|
|
400
|
+
if (step.actor && step.name) {
|
|
401
|
+
title = `${step.actor}.${step.name}`;
|
|
402
|
+
if (step.args && step.args.length > 0) {
|
|
403
|
+
const argsStr = step.args.map(arg => JSON.stringify(arg)).join(', ');
|
|
404
|
+
title += `(${argsStr})`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
category: 'hook',
|
|
409
|
+
title,
|
|
410
|
+
duration: step.duration || 0
|
|
411
|
+
};
|
|
412
|
+
}
|
|
327
413
|
module.exports = CodeceptReporter;
|
|
328
414
|
|
|
329
415
|
module.exports.CodeceptReporter = CodeceptReporter;
|
|
@@ -40,7 +40,6 @@ const testomatioReporter = on => {
|
|
|
40
40
|
const suiteId = (0, utils_js_1.parseSuite)(suiteTitle);
|
|
41
41
|
if (!error && test.displayError) {
|
|
42
42
|
error = { message: test.displayError };
|
|
43
|
-
// eslint-disable-next-line
|
|
44
43
|
error.inspect = function () {
|
|
45
44
|
return this.message;
|
|
46
45
|
};
|
|
@@ -50,7 +49,6 @@ const testomatioReporter = on => {
|
|
|
50
49
|
message: error.message,
|
|
51
50
|
name: error.name,
|
|
52
51
|
inspect: error.inspect ||
|
|
53
|
-
// eslint-disable-next-line
|
|
54
52
|
function () {
|
|
55
53
|
return this.message;
|
|
56
54
|
},
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
7
6
|
const mocha_1 = __importDefault(require("mocha"));
|
|
8
7
|
const client_js_1 = __importDefault(require("../client.js"));
|
|
9
8
|
const constants_js_1 = require("../constants.js");
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const client_js_1 = __importDefault(require("../client.js"));
|
|
7
7
|
const config_js_1 = require("../config.js");
|
|
8
|
-
const
|
|
8
|
+
const constants_js_1 = require("../constants.js");
|
|
9
9
|
const utils_js_1 = require("../utils/utils.js");
|
|
10
10
|
const apiKey = config_js_1.config.TESTOMATIO;
|
|
11
11
|
const client = new client_js_1.default({ apiKey });
|
|
@@ -29,14 +29,14 @@ module.exports = {
|
|
|
29
29
|
let status;
|
|
30
30
|
switch (test.status) {
|
|
31
31
|
case 'pass':
|
|
32
|
-
status =
|
|
32
|
+
status = constants_js_1.STATUS.PASSED;
|
|
33
33
|
break;
|
|
34
34
|
case 'fail':
|
|
35
|
-
status =
|
|
35
|
+
status = constants_js_1.STATUS.FAILED;
|
|
36
36
|
break;
|
|
37
37
|
// probably not required (because skipped tests are in separate array), but just in case
|
|
38
38
|
case 'skip':
|
|
39
|
-
status =
|
|
39
|
+
status = constants_js_1.STATUS.SKIPPED;
|
|
40
40
|
console.info('Skipped test is in completed tests array:', test, 'Not expected behavior.');
|
|
41
41
|
break;
|
|
42
42
|
default:
|
|
@@ -58,7 +58,7 @@ module.exports = {
|
|
|
58
58
|
}
|
|
59
59
|
// just array with skipped tests titles, no any other info
|
|
60
60
|
for (const testTitle of skippedTests) {
|
|
61
|
-
client.addTestRun(
|
|
61
|
+
client.addTestRun(constants_js_1.STATUS.SKIPPED, {
|
|
62
62
|
suite_title: suiteTitle,
|
|
63
63
|
tags,
|
|
64
64
|
rid: `${testModule.uuid || ''}_${testTitle || ''}`,
|