@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.
Files changed (80) hide show
  1. package/README.md +1 -0
  2. package/lib/adapter/codecept.js +288 -202
  3. package/lib/adapter/cypress-plugin/index.js +0 -2
  4. package/lib/adapter/mocha.js +0 -1
  5. package/lib/adapter/nightwatch.js +5 -5
  6. package/lib/adapter/playwright.js +11 -3
  7. package/lib/adapter/webdriver.d.ts +1 -1
  8. package/lib/adapter/webdriver.js +18 -8
  9. package/lib/bin/cli.js +73 -8
  10. package/lib/bin/reportXml.js +4 -2
  11. package/lib/bin/startTest.js +3 -2
  12. package/lib/bin/uploadArtifacts.js +5 -4
  13. package/lib/client.js +30 -10
  14. package/lib/data-storage.d.ts +5 -5
  15. package/lib/data-storage.js +23 -13
  16. package/lib/junit-adapter/csharp.d.ts +1 -0
  17. package/lib/junit-adapter/csharp.js +11 -1
  18. package/lib/pipe/bitbucket.d.ts +2 -0
  19. package/lib/pipe/bitbucket.js +38 -26
  20. package/lib/pipe/debug.js +27 -6
  21. package/lib/pipe/github.d.ts +2 -2
  22. package/lib/pipe/github.js +35 -3
  23. package/lib/pipe/gitlab.d.ts +2 -0
  24. package/lib/pipe/gitlab.js +27 -9
  25. package/lib/pipe/html.js +0 -3
  26. package/lib/pipe/index.js +17 -7
  27. package/lib/pipe/testomatio.d.ts +3 -2
  28. package/lib/pipe/testomatio.js +85 -75
  29. package/lib/replay.d.ts +31 -0
  30. package/lib/replay.js +255 -0
  31. package/lib/reporter-functions.d.ts +7 -0
  32. package/lib/reporter-functions.js +36 -0
  33. package/lib/reporter.d.ts +15 -12
  34. package/lib/reporter.js +4 -1
  35. package/lib/services/artifacts.d.ts +1 -1
  36. package/lib/services/index.d.ts +2 -0
  37. package/lib/services/index.js +2 -0
  38. package/lib/services/key-values.d.ts +1 -1
  39. package/lib/services/labels.d.ts +22 -0
  40. package/lib/services/labels.js +62 -0
  41. package/lib/services/logger.d.ts +1 -1
  42. package/lib/services/logger.js +1 -2
  43. package/lib/template/testomatio.hbs +443 -68
  44. package/lib/uploader.js +10 -6
  45. package/lib/utils/constants.d.ts +12 -0
  46. package/lib/utils/constants.js +15 -0
  47. package/lib/utils/utils.d.ts +10 -1
  48. package/lib/utils/utils.js +70 -22
  49. package/lib/xmlReader.js +54 -19
  50. package/package.json +16 -11
  51. package/src/adapter/codecept.js +320 -214
  52. package/src/adapter/cypress-plugin/index.js +0 -2
  53. package/src/adapter/mocha.js +0 -1
  54. package/src/adapter/nightwatch.js +1 -1
  55. package/src/adapter/playwright.js +10 -7
  56. package/src/adapter/webdriver.js +2 -2
  57. package/src/bin/cli.js +70 -2
  58. package/src/bin/reportXml.js +4 -1
  59. package/src/bin/startTest.js +2 -1
  60. package/src/bin/uploadArtifacts.js +2 -1
  61. package/src/client.js +18 -3
  62. package/src/data-storage.js +6 -6
  63. package/src/junit-adapter/csharp.js +13 -1
  64. package/src/pipe/bitbucket.js +22 -24
  65. package/src/pipe/debug.js +26 -5
  66. package/src/pipe/github.js +1 -2
  67. package/src/pipe/gitlab.js +27 -9
  68. package/src/pipe/html.js +1 -4
  69. package/src/pipe/testomatio.js +106 -105
  70. package/src/replay.js +262 -0
  71. package/src/reporter-functions.js +41 -0
  72. package/src/reporter.js +3 -0
  73. package/src/services/index.js +2 -0
  74. package/src/services/labels.js +59 -0
  75. package/src/services/logger.js +1 -2
  76. package/src/template/testomatio.hbs +443 -68
  77. package/src/uploader.js +11 -6
  78. package/src/utils/constants.js +12 -0
  79. package/src/utils/utils.js +46 -13
  80. package/src/xmlReader.js +70 -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
 
@@ -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
- // eslint-disable-next-line
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
- let currentMetaStep = [];
24
- let error;
25
- let stepShift = 0;
26
- // const output = new Output({
27
- // filterFn: stack => !stack.includes('codeceptjs/lib/output'), // output from codeceptjs
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
- let failedTests = [];
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
- utils_js_1.fileSystem.clearDir(constants_js_1.TESTOMAT_TMP_STORAGE_DIR);
81
+ // fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
56
82
  // recorder.add('Creating new run', () => );
57
- client.createRun();
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
- let hookSteps = [];
64
- let suiteHookRunning = false;
65
- event.dispatcher.on(event.suite.before, suite => {
66
- suiteHookRunning = true;
67
- hookSteps = [];
68
- global.testomatioDataStore.steps = [];
69
- index_js_1.services.setContext(suite.fullTitle());
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.suite.after, () => {
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.hook.started, () => {
75
- // global.testomatioDataStore.steps = [];
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.hook.failed, () => {
84
- if (suiteHookRunning) {
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
- suiteHookRunning = false;
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
- const status = failedTests.length === 0 ? constants_js_1.STATUS.PASSED : constants_js_1.STATUS.FAILED;
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
- if (test.state && test.state !== constants_js_1.STATUS.FAILED)
171
- return;
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(constants_js_1.STATUS.FAILED, {
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: getDuration(test),
151
+ time: test.duration,
193
152
  files,
194
- steps: global.testomatioDataStore?.steps?.join('\n') || null,
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
- // output.stop();
159
+ processArtifactsForUpload(artifacts, uid, title, videos, traces);
222
160
  });
223
161
  event.dispatcher.on(event.step.started, step => {
224
- stepShift = 0;
225
- step.started = true;
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
- if (!step.started)
230
- return;
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
- const example = JSON.parse(res[1]);
306
- title = title.replace(DATA_REGEXP, '').trim();
307
- return { title, example };
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
- const suiteLogsArr = index_js_1.services.logger.getLogs(test.parent.fullTitle());
315
- const suiteLogs = suiteLogsArr ? suiteLogsArr.join('\n').trim() : '';
316
- const testLogsArr = index_js_1.services.logger.getLogs(test.fullTitle());
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 (suiteLogs) {
320
- logs += `${picocolors_1.default.bold('\t--- BeforeSuite ---')}\n${suiteLogs}`;
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('\t--- Test ---')}\n${testLogs}`;
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
  },
@@ -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 constants_1 = require("../constants");
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 = constants_1.STATUS.PASSED;
32
+ status = constants_js_1.STATUS.PASSED;
33
33
  break;
34
34
  case 'fail':
35
- status = constants_1.STATUS.FAILED;
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 = constants_1.STATUS.SKIPPED;
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(constants_1.STATUS.SKIPPED, {
61
+ client.addTestRun(constants_js_1.STATUS.SKIPPED, {
62
62
  suite_title: suiteTitle,
63
63
  tags,
64
64
  rid: `${testModule.uuid || ''}_${testTitle || ''}`,