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