@testomatio/reporter 2.7.0 → 2.7.2-beta.1

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 (51) hide show
  1. package/README.md +2 -1
  2. package/lib/adapter/codecept.js +81 -26
  3. package/lib/adapter/playwright.d.ts +1 -1
  4. package/lib/adapter/playwright.js +54 -34
  5. package/lib/adapter/utils/step-formatter.d.ts +134 -0
  6. package/lib/adapter/utils/step-formatter.js +237 -0
  7. package/lib/bin/cli.js +28 -31
  8. package/lib/bin/reportXml.js +5 -6
  9. package/lib/bin/startTest.js +2 -1
  10. package/lib/bin/uploadArtifacts.js +6 -6
  11. package/lib/client.d.ts +8 -0
  12. package/lib/client.js +71 -10
  13. package/lib/constants.d.ts +1 -0
  14. package/lib/constants.js +7 -1
  15. package/lib/pipe/bitbucket.js +2 -1
  16. package/lib/pipe/coverage.js +16 -15
  17. package/lib/pipe/debug.js +3 -3
  18. package/lib/pipe/github.js +3 -2
  19. package/lib/pipe/gitlab.js +2 -1
  20. package/lib/pipe/index.js +5 -5
  21. package/lib/pipe/testomatio.js +21 -24
  22. package/lib/uploader.js +3 -2
  23. package/lib/utils/log.d.ts +45 -0
  24. package/lib/utils/log.js +98 -0
  25. package/lib/utils/pipe_utils.js +5 -5
  26. package/lib/utils/utils.d.ts +10 -0
  27. package/lib/utils/utils.js +16 -1
  28. package/lib/xmlReader.js +5 -4
  29. package/package.json +1 -1
  30. package/src/adapter/codecept.js +99 -29
  31. package/src/adapter/playwright.js +64 -39
  32. package/src/adapter/utils/step-formatter.js +232 -0
  33. package/src/bin/cli.js +34 -31
  34. package/src/bin/reportXml.js +5 -6
  35. package/src/bin/startTest.js +3 -2
  36. package/src/bin/uploadArtifacts.js +6 -6
  37. package/src/client.js +76 -26
  38. package/src/constants.js +4 -0
  39. package/src/pipe/bitbucket.js +2 -1
  40. package/src/pipe/coverage.js +16 -15
  41. package/src/pipe/debug.js +3 -3
  42. package/src/pipe/github.js +4 -3
  43. package/src/pipe/gitlab.js +2 -1
  44. package/src/pipe/index.js +5 -7
  45. package/src/pipe/testomatio.js +32 -25
  46. package/src/uploader.js +3 -2
  47. package/src/utils/log.js +87 -0
  48. package/src/utils/pipe_utils.js +5 -5
  49. package/src/utils/utils.js +14 -0
  50. package/src/xmlReader.js +5 -4
  51. package/types/types.d.ts +3 -0
package/README.md CHANGED
@@ -63,7 +63,7 @@ yarn add @testomatio/reporter --dev
63
63
  ### 1️⃣ Attach Reporter to the Test Runner
64
64
 
65
65
  | | | |
66
- |-------------------------------------------------|-----------------------------------------------|-----------------------------------------------------------|
66
+ | ----------------------------------------------- | --------------------------------------------- | --------------------------------------------------------- |
67
67
  | [Playwright](./docs/frameworks.md#playwright) | [CodeceptJS](./docs/frameworks.md#codeceptjs) | [Cypress](./docs/frameworks.md#cypress) |
68
68
  | [Jest](./docs/frameworks.md#jest) | [Mocha](./docs/frameworks.md#mocha) | [WebDriverIO](./docs/frameworks.md#webdriverio) |
69
69
  | [TestCafe](./docs/frameworks.md#testcafe) | [Detox](./docs/frameworks.md#detox) | [Codeception](https://github.com/testomatio/php-reporter) |
@@ -135,6 +135,7 @@ Bring this reporter on CI and never lose test results again!
135
135
  - 🔂 [Workflows](./docs/workflows.md)
136
136
  - 🖊️ [Logger](./docs/logger.md)
137
137
  - 🪲 [Debug File Format](./docs/debug-file-format.md)
138
+ - 📊 [Control output logs](./docs/log-level.md)
138
139
 
139
140
  ## Development
140
141
 
@@ -11,7 +11,9 @@ 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
+ const step_formatter_js_1 = require("./utils/step-formatter.js");
14
15
  const codeceptjs_1 = __importDefault(require("codeceptjs"));
16
+ const log_js_1 = require("../utils/log.js");
15
17
  const debug = (0, debug_1.default)('@testomatio/reporter:adapter:codeceptjs');
16
18
  // @ts-ignore
17
19
  if (!global.codeceptjs) {
@@ -43,6 +45,7 @@ function CodeceptReporter(config) {
43
45
  let videos = [];
44
46
  let traces = [];
45
47
  const reportTestPromises = [];
48
+ let isRunFinalized = false;
46
49
  const testTimeMap = {};
47
50
  const { apiKey } = config;
48
51
  const client = new client_js_1.default({ apiKey });
@@ -70,6 +73,17 @@ function CodeceptReporter(config) {
70
73
  recorder.startUnlessRunning();
71
74
  const hookSteps = new Map();
72
75
  let currentHook = null;
76
+ const finalizeRun = async (origin) => {
77
+ if (isRunFinalized)
78
+ return;
79
+ isRunFinalized = true;
80
+ debug(`finalizing run from ${origin}`);
81
+ debug('waiting for all tests to be reported');
82
+ await Promise.allSettled(reportTestPromises);
83
+ await uploadAttachments(client, videos, '🎞️ Uploading', 'video');
84
+ await uploadAttachments(client, traces, '📁 Uploading', 'trace');
85
+ await client.updateRunStatus('finished');
86
+ };
73
87
  event.dispatcher.on(event.workers.before, () => {
74
88
  recorder.add('Creating new run', async () => {
75
89
  await client.createRun();
@@ -79,7 +93,9 @@ function CodeceptReporter(config) {
79
93
  });
80
94
  });
81
95
  event.dispatcher.on(event.workers.after, () => {
82
- client.updateRunStatus('finished');
96
+ recorder.add('Finishing run', async () => {
97
+ await finalizeRun('workers.after');
98
+ });
83
99
  });
84
100
  // Listening to events
85
101
  event.dispatcher.on(event.all.before, () => {
@@ -91,6 +107,8 @@ function CodeceptReporter(config) {
91
107
  });
92
108
  videos = [];
93
109
  traces = [];
110
+ isRunFinalized = false;
111
+ reportTestPromises.length = 0;
94
112
  if (!global.testomatioDataStore)
95
113
  global.testomatioDataStore = {};
96
114
  });
@@ -122,7 +140,7 @@ function CodeceptReporter(config) {
122
140
  return;
123
141
  const error = hook?.ctx?.currentTest?.err;
124
142
  for (const test of suite.tests) {
125
- client.addTestRun('failed', {
143
+ const reportTestPromise = client.addTestRun('failed', {
126
144
  ...stripExampleFromTitle(test.title),
127
145
  rid: test.uid,
128
146
  test_id: (0, utils_js_1.getTestomatIdFromTestTitle)(test.title),
@@ -130,6 +148,7 @@ function CodeceptReporter(config) {
130
148
  error,
131
149
  time: hook?.runnable?.duration,
132
150
  });
151
+ reportTestPromises.push(reportTestPromise);
133
152
  }
134
153
  });
135
154
  event.dispatcher.on(event.suite.before, suite => {
@@ -146,13 +165,10 @@ function CodeceptReporter(config) {
146
165
  index_js_1.services.setContext(test.fullTitle());
147
166
  testTimeMap[test.uid] = Date.now();
148
167
  });
149
- event.dispatcher.on(event.all.result, async (result) => {
150
- debug('waiting for all tests to be reported');
151
- // all tests were reported and we can upload videos
152
- await Promise.all(reportTestPromises);
153
- await uploadAttachments(client, videos, '🎞️ Uploading', 'video');
154
- await uploadAttachments(client, traces, '📁 Uploading', 'trace');
155
- client.updateRunStatus('finished');
168
+ event.dispatcher.on(event.all.after, () => {
169
+ recorder.add('Finishing run', async () => {
170
+ await finalizeRun('all.after');
171
+ });
156
172
  });
157
173
  event.dispatcher.on(event.test.after, test => {
158
174
  const { uid, tags, title, artifacts } = test.simplify();
@@ -163,10 +179,12 @@ function CodeceptReporter(config) {
163
179
  const logs = getTestLogs(test);
164
180
  const manuallyAttachedArtifacts = index_js_1.services.artifacts.get(test.fullTitle());
165
181
  const keyValues = index_js_1.services.keyValues.get(test.fullTitle());
166
- const stepHierarchy = buildUnifiedStepHierarchy(test.steps, hookSteps);
167
182
  const links = index_js_1.services.links.get(test.fullTitle());
183
+ const screenshotOnFailPath = artifacts.screenshot || null;
184
+ // Build step hierarchy with screenshot from screenshotOnFail
185
+ const stepHierarchy = buildUnifiedStepHierarchy(test.steps, hookSteps, screenshotOnFailPath);
168
186
  index_js_1.services.setContext(null);
169
- client.addTestRun(test.state, {
187
+ const reportTestPromise = client.addTestRun(test.state, {
170
188
  ...stripExampleFromTitle(title),
171
189
  rid: uid,
172
190
  test_id: (0, utils_js_1.getTestomatIdFromTestTitle)(`${title} ${tags?.join(' ')}`),
@@ -181,6 +199,7 @@ function CodeceptReporter(config) {
181
199
  manuallyAttachedArtifacts,
182
200
  meta: { ...keyValues, ...test.meta },
183
201
  });
202
+ reportTestPromises.push(reportTestPromise);
184
203
  processArtifactsForUpload(artifacts, uid, title, videos, traces);
185
204
  });
186
205
  event.dispatcher.on(event.step.started, step => {
@@ -196,7 +215,7 @@ async function uploadAttachments(client, attachments, messagePrefix, attachmentT
196
215
  if (!attachments?.length)
197
216
  return;
198
217
  if (client.uploader.isEnabled) {
199
- console.log(constants_js_1.APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
218
+ log_js_1.log.info(`Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
200
219
  }
201
220
  const promises = attachments.map(async (attachment) => {
202
221
  const { rid, title, path, type } = attachment;
@@ -322,13 +341,13 @@ function getTestLogs(test) {
322
341
  return logs;
323
342
  }
324
343
  // Build step hierarchy using CodeceptJS built-in methods
325
- function buildUnifiedStepHierarchy(steps, hookSteps) {
344
+ function buildUnifiedStepHierarchy(steps, hookSteps, screenshotOnFailPath = null) {
326
345
  const hierarchy = [];
327
346
  // Add pre-test hooks
328
347
  addHooksToHierarchy(hierarchy, hookSteps, HOOK_EXECUTION_ORDER.PRE_TEST);
329
348
  // Process test steps if they exist
330
349
  if (steps && steps.length > 0) {
331
- processTestSteps(steps, hierarchy);
350
+ processTestSteps(steps, hierarchy, screenshotOnFailPath);
332
351
  }
333
352
  // Add post-test hooks
334
353
  addHooksToHierarchy(hierarchy, hookSteps, HOOK_EXECUTION_ORDER.POST_TEST);
@@ -343,10 +362,16 @@ function addHooksToHierarchy(hierarchy, hookSteps, hookNames) {
343
362
  }
344
363
  }
345
364
  }
346
- function processTestSteps(steps, hierarchy) {
365
+ function processTestSteps(steps, hierarchy, screenshotOnFailPath = null) {
347
366
  const sectionMap = new Map();
367
+ let screenshotAttached = false;
348
368
  for (const step of steps) {
349
- const formattedStep = formatCodeceptStep(step);
369
+ let stepScreenshotPath = null;
370
+ if (screenshotOnFailPath && !screenshotAttached && step.status === 'failed') {
371
+ stepScreenshotPath = screenshotOnFailPath;
372
+ screenshotAttached = true;
373
+ }
374
+ const formattedStep = formatCodeceptStep(step, stepScreenshotPath);
350
375
  if (!formattedStep)
351
376
  continue;
352
377
  if (step.metaStep) {
@@ -397,24 +422,38 @@ function formatHookName(hookName) {
397
422
  return hookName.replace(/Hook$/, '');
398
423
  }
399
424
  // Format CodeceptJS step using its built-in methods
400
- function formatCodeceptStep(step) {
425
+ function formatCodeceptStep(step, screenshotOnFailPath = null) {
401
426
  if (!step)
402
427
  return null;
403
428
  const category = step.constructor.name === 'HelperStep' ? 'framework' : 'user';
404
- const title = (0, utils_js_1.truncate)(step); // Use built-in toString
405
- const duration = step.duration || 0; // Use built-in duration
406
- const formattedStep = {
429
+ const title = (0, utils_js_1.truncate)(String(step));
430
+ const duration = step.duration || 0;
431
+ const formattedStep = (0, step_formatter_js_1.formatStep)({
407
432
  category,
408
433
  title,
409
434
  duration,
410
- };
435
+ });
436
+ // Add status
437
+ (0, step_formatter_js_1.addStatusToStep)(formattedStep, step.status, step.err);
411
438
  // Add error if step failed
412
439
  if (step.status === 'failed' && step.err) {
413
440
  formattedStep.error = {
414
- message: step.err.message || 'Step failed',
415
- stack: step.err.stack || '',
441
+ message: (0, utils_js_1.truncate)(String(step.err.message || 'Step failed'), 250),
442
+ stack: (0, utils_js_1.truncate)(String(step.err.stack || ''), 250),
416
443
  };
417
444
  }
445
+ // Add artifacts
446
+ if (step.artifacts && constants_js_1.SCREENSHOTS_ON_STEPS) {
447
+ (0, step_formatter_js_1.addArtifactsToStep)(formattedStep, step.artifacts);
448
+ }
449
+ // Add screenshot from screenshotOnFail plugin
450
+ if (screenshotOnFailPath && constants_js_1.SCREENSHOTS_ON_STEPS) {
451
+ (0, step_formatter_js_1.addArtifactPathToStep)(formattedStep, screenshotOnFailPath);
452
+ }
453
+ // Add log if present
454
+ if (step.log) {
455
+ formattedStep.log = (0, utils_js_1.truncate)(String(step.log), 250);
456
+ }
418
457
  return formattedStep;
419
458
  }
420
459
  function formatHookStep(step) {
@@ -425,16 +464,32 @@ function formatHookStep(step) {
425
464
  if (step.actor && step.name) {
426
465
  title = `${step.actor} ${step.name}`;
427
466
  if (step.args && step.args.length > 0) {
428
- const argsStr = step.args.map(arg => (0, utils_js_1.truncate)(JSON.stringify(arg))).join(', ');
467
+ const argsStr = step.args.map(arg => (0, utils_js_1.truncate)(JSON.stringify(arg), 250)).join(', ');
429
468
  title += ` ${argsStr}`;
430
469
  }
431
470
  }
432
471
  title = (0, utils_js_1.truncate)(title);
433
- return {
472
+ const formattedStep = (0, step_formatter_js_1.formatStep)({
434
473
  category: 'hook',
435
474
  title,
436
475
  duration: step.duration || 0,
437
- };
476
+ });
477
+ (0, step_formatter_js_1.addStatusToStep)(formattedStep, step.status, step.err);
478
+ if (step.status === 'failed' && step.err) {
479
+ formattedStep.error = {
480
+ message: (0, utils_js_1.truncate)(String(step.err.message || 'Hook failed'), 250),
481
+ stack: (0, utils_js_1.truncate)(String(step.err.stack || ''), 250),
482
+ };
483
+ }
484
+ // Add artifacts
485
+ if (step.artifacts && constants_js_1.SCREENSHOTS_ON_STEPS) {
486
+ (0, step_formatter_js_1.addArtifactsToStep)(formattedStep, step.artifacts);
487
+ }
488
+ // Add log if present
489
+ if (step.log) {
490
+ formattedStep.log = (0, utils_js_1.truncate)(String(step.log), 250);
491
+ }
492
+ return formattedStep;
438
493
  }
439
494
  module.exports = CodeceptReporter;
440
495
 
@@ -7,7 +7,7 @@ declare class PlaywrightReporter {
7
7
  suite: any;
8
8
  config: any;
9
9
  onTestBegin(testInfo: any): void;
10
- onTestEnd(test: any, result: any): void;
10
+ onTestEnd(test: any, result: any): Promise<void>;
11
11
  onEnd(result: any): Promise<void>;
12
12
  #private;
13
13
  }
@@ -19,6 +19,8 @@ const constants_js_2 = require("../utils/constants.js");
19
19
  const picocolors_1 = __importDefault(require("picocolors"));
20
20
  const playwright_js_1 = require("./utils/playwright.js");
21
21
  Object.defineProperty(exports, "fetchLinksFromLogs", { enumerable: true, get: function () { return playwright_js_1.fetchLinksFromLogs; } });
22
+ const step_formatter_js_1 = require("./utils/step-formatter.js");
23
+ const log_js_1 = require("../utils/log.js");
22
24
  const reportTestPromises = [];
23
25
  class PlaywrightReporter {
24
26
  constructor(config = {}) {
@@ -38,7 +40,7 @@ class PlaywrightReporter {
38
40
  const fullTestTitle = getTestContextName(testInfo);
39
41
  data_storage_js_1.dataStorage.setContext(fullTestTitle);
40
42
  }
41
- onTestEnd(test, result) {
43
+ async onTestEnd(test, result) {
42
44
  // test.parent.project().__projectId
43
45
  if (!this.client)
44
46
  return;
@@ -53,13 +55,24 @@ class PlaywrightReporter {
53
55
  }))
54
56
  .filter(f => f.path);
55
57
  const suite_title = test.parent ? test.parent?.title : path_1.default.basename(test?.location?.file);
56
- const steps = [];
57
- for (const step of result.steps) {
58
- const appendedStep = appendStep(step);
59
- if (appendedStep) {
60
- steps.push(appendedStep);
61
- }
62
- }
58
+ const rid = test.id || test.testId || (0, uuid_1.v4)();
59
+ /**
60
+ * @type {{
61
+ * browser?: string,
62
+ * dependencies: string[],
63
+ * isMobile?: boolean
64
+ * metadata: Record<string, any>,
65
+ * name: string,
66
+ * }}
67
+ */
68
+ const project = {
69
+ browser: test.parent.project().use.defaultBrowserType,
70
+ dependencies: test.parent.project().dependencies,
71
+ isMobile: test.parent.project().use.isMobile,
72
+ metadata: test.parent.project().metadata,
73
+ name: test.parent.project().name,
74
+ };
75
+ const steps = result.steps.map(step => appendStep(step, 0)).filter(step => step !== null);
63
76
  // Extract and normalize tags
64
77
  const tags = extractTags(test);
65
78
  const fullTestTitle = getTestContextName(test);
@@ -81,23 +94,6 @@ class PlaywrightReporter {
81
94
  */
82
95
  const manuallyAttachedArtifacts = index_js_1.services.artifacts.get(fullTestTitle);
83
96
  const testMeta = index_js_1.services.keyValues.get(fullTestTitle);
84
- const rid = test.id || test.testId || (0, uuid_1.v4)();
85
- /**
86
- * @type {{
87
- * browser?: string,
88
- * dependencies: string[],
89
- * isMobile?: boolean
90
- * metadata: Record<string, any>,
91
- * name: string,
92
- * }}
93
- */
94
- const project = {
95
- browser: test.parent.project().use.defaultBrowserType,
96
- dependencies: test.parent.project().dependencies,
97
- isMobile: test.parent.project().use.isMobile,
98
- metadata: test.parent.project().metadata,
99
- name: test.parent.project().name,
100
- };
101
97
  let status = result.status;
102
98
  // process test.fail() annotation
103
99
  if (test.expectedStatus === 'failed') {
@@ -171,7 +167,7 @@ class PlaywrightReporter {
171
167
  await Promise.all(reportTestPromises);
172
168
  if (this.uploads.length) {
173
169
  if (this.client.uploader.isEnabled)
174
- console.log(constants_js_1.APP_PREFIX, `🎞️ Uploading ${this.uploads.length} files...`);
170
+ log_js_1.log.info(`🎞️ Uploading ${this.uploads.length} files...`);
175
171
  const promises = [];
176
172
  // ? possible move to addTestRun (needs investigation if files are ready)
177
173
  for (const upload of this.uploads) {
@@ -221,6 +217,38 @@ function appendStep(step, shift = 0) {
221
217
  default:
222
218
  newCategory = 'framework';
223
219
  }
220
+ const resultStep = (0, step_formatter_js_1.formatStep)({
221
+ category: newCategory,
222
+ title: step.title,
223
+ duration: step.duration,
224
+ });
225
+ // Add status based on error
226
+ (0, step_formatter_js_1.addStatusToStep)(resultStep, step.error ? 'failed' : 'passed', step.error);
227
+ // Add error if present
228
+ if (step.error !== undefined) {
229
+ if (typeof step.error === 'object') {
230
+ resultStep.error = {
231
+ message: (0, utils_js_1.truncate)(String(step.error.message), 250),
232
+ stack: (0, utils_js_1.truncate)(String(step.error.stack || ''), 250),
233
+ };
234
+ }
235
+ else {
236
+ resultStep.error = (0, utils_js_1.truncate)(String(step.error), 250);
237
+ }
238
+ }
239
+ // Add log if present
240
+ if (step.log) {
241
+ resultStep.log = (0, utils_js_1.truncate)(String(step.log), 250);
242
+ }
243
+ // Add artifacts from attachments
244
+ if (step.attachments && step.attachments.length > 0 && constants_js_1.SCREENSHOTS_ON_STEPS) {
245
+ const screenshotAttachment = step.attachments.find(att => att.contentType === 'image/png' && att.name === 'screenshot');
246
+ if (screenshotAttachment && screenshotAttachment.path) {
247
+ const artifacts = { screenshot: screenshotAttachment.path };
248
+ (0, step_formatter_js_1.addArtifactsToStep)(resultStep, artifacts);
249
+ }
250
+ }
251
+ // Process nested steps
224
252
  const formattedSteps = [];
225
253
  for (const child of step.steps || []) {
226
254
  const appendedChild = appendStep(child, shift + 2);
@@ -228,17 +256,9 @@ function appendStep(step, shift = 0) {
228
256
  formattedSteps.push(appendedChild);
229
257
  }
230
258
  }
231
- const resultStep = {
232
- category: newCategory,
233
- title: step.title,
234
- duration: step.duration,
235
- };
236
259
  if (formattedSteps.length) {
237
260
  resultStep.steps = formattedSteps.filter(s => !!s);
238
261
  }
239
- if (step.error !== undefined) {
240
- resultStep.error = step.error;
241
- }
242
262
  return resultStep;
243
263
  }
244
264
  function generateTmpFilepath(filename = '') {
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Generates a short unique filename from screenshot path
3
+ * If original filename is too long, uses hash-based name
4
+ *
5
+ * @param {string} screenshotPath - Path to screenshot file
6
+ * @returns {string} Short filename (max 80 chars)
7
+ */
8
+ export function generateShortFilename(screenshotPath: string): string;
9
+ /**
10
+ * Formats a step object according to Testomat.io Step Schema
11
+ *
12
+ * This function transforms a raw step object from test frameworks (CodeceptJS, Playwright, etc.)
13
+ * into a standardized format compatible with Testomat.io API. It ensures all text fields are
14
+ * truncated to 250 characters as defined in testomat-api-definition.yml.
15
+ *
16
+ * Processed fields:
17
+ * - category: step type (framework, user, hook) - defaults to 'user'
18
+ * - title: step name/description, truncated to 250 chars
19
+ * - duration: step execution time in seconds
20
+ * - log: optional log output, truncated to 250 chars
21
+ * - artifacts: optional array of artifact URLs (screenshots), each truncated to 250 chars
22
+ * - error: error details (message + stack) if step failed, each truncated to 250 chars
23
+ * - steps: recursively formats nested steps
24
+ *
25
+ * Schema reference: testomat-api-definition.yml (Step object)
26
+ *
27
+ * @param {Object} step - Raw step object from test framework
28
+ * @param {string} [step.category] - Step category: 'user', 'framework', or 'hook'
29
+ * @param {string} [step.title] - Step title/name
30
+ * @param {number} [step.duration] - Step duration in seconds
31
+ * @param {string} [step.log] - Log output for this step
32
+ * @param {string[]} [step.artifacts] - Array of artifact URLs (screenshots)
33
+ * @param {string|Object} [step.error] - Error details - can be string or object with message/stack
34
+ * @param {Object[]} [step.steps] - Array of nested child steps
35
+ * @returns {Object} Formatted step object matching Testomat.io Step Schema with:
36
+ * category, title, duration, and optional log, artifacts, error, and steps fields
37
+ *
38
+ * @example
39
+ * const rawStep = {
40
+ * category: 'user',
41
+ * title: 'I click on button',
42
+ * duration: 1.5,
43
+ * error: { message: 'Element not found', stack: 'at test.js:10:5' }
44
+ * };
45
+ * const formatted = formatStep(rawStep);
46
+ * // Returns: { category: 'user', title: 'I click on button', duration: 1.5, error: {...} }
47
+ */
48
+ export function formatStep(step: {
49
+ category?: string;
50
+ title?: string;
51
+ duration?: number;
52
+ log?: string;
53
+ artifacts?: string[];
54
+ error?: string | any;
55
+ steps?: any[];
56
+ }): any;
57
+ /**
58
+ * Adds status field to step
59
+ *
60
+ * Normalizes step status from test frameworks to Testomat.io standard format.
61
+ * Maps framework-specific statuses ('success', 'failed', 'passed') to Testomat.io
62
+ * standard values ('passed', 'failed').
63
+ *
64
+ * Status mapping:
65
+ * - 'success' → 'passed'
66
+ * - 'passed' → 'passed'
67
+ * - 'failed' → 'failed'
68
+ * - Any other value → 'passed' (default)
69
+ *
70
+ * If step already has a status, it won't be overwritten. If error is provided
71
+ * and step doesn't have status, it will be set to 'failed'.
72
+ *
73
+ * Schema reference: testomat-api-definition.yml (Step.status enum)
74
+ *
75
+ * @param {Object} step - Step object to add status to (modified in place)
76
+ * @param {string} [step.status] - Existing status (won't be overwritten if present)
77
+ * @param {string} status - Status from test framework: 'success', 'failed', or 'passed'
78
+ * @param {Error|Object|null} err - Error object if step failed
79
+ * @returns {Object} The same step object with added status field
80
+ *
81
+ * @example
82
+ * const step = { title: 'Click button' };
83
+ * addStatusToStep(step, 'success', null);
84
+ * // step.status === 'passed'
85
+ *
86
+ * @example
87
+ * const step2 = { title: 'Find element' };
88
+ * addStatusToStep(step2, 'failed', new Error('Not found'));
89
+ * // step2.status === 'failed'
90
+ */
91
+ export function addStatusToStep(step: {
92
+ status?: string;
93
+ }, status: string, err: Error | any | null): any;
94
+ /**
95
+ * Adds screenshot to step as artifacts array
96
+ *
97
+ * Extracts screenshot path from artifacts and adds it to the step's artifacts array.
98
+ * The actual upload will happen in the client's addTestRun method.
99
+ *
100
+ * Artifact format supports:
101
+ * - Array format: [{ screenshot: '/path/to/screenshot.png' }]
102
+ * - Object format: { screenshot: '/path/to/screenshot.png' }
103
+ *
104
+ * Screenshot path can be specified as:
105
+ * - Object with path property: { screenshot: { path: '/path/to/file.png' } }
106
+ * - Object with screenshot property: { screenshot: { screenshot: '/path/to/file.png' } }
107
+ * - Direct string path: { screenshot: '/path/to/file.png' }
108
+ *
109
+ * @param {Object} step - Step object to add artifacts to (modified in place)
110
+ * @param {string[]} [step.artifacts] - Existing artifacts array (won't be overwritten if present)
111
+ * @param {Object|Object[]|null} artifacts - Artifacts from test framework
112
+ * @returns {Object} The same step object with artifacts array added
113
+ *
114
+ * @example
115
+ * const step = { title: 'Click button' };
116
+ * const artifacts = { screenshot: '/tmp/screenshot.png' };
117
+ * addArtifactsToStep(step, artifacts);
118
+ * // step.artifacts === ['/tmp/screenshot.png']
119
+ */
120
+ export function addArtifactsToStep(step: {
121
+ artifacts?: string[];
122
+ }, artifacts: any | any[] | null): any;
123
+ /**
124
+ * Appends one artifact path to a step.
125
+ *
126
+ * Unlike addArtifactsToStep, this helper accepts a direct path (or URL-like string)
127
+ * and does not check file existence, so callers can attach fallback artifacts
128
+ * collected from logs or async trace outputs.
129
+ *
130
+ * @param {Object} step - Step object to update (modified in place)
131
+ * @param {string} artifactPath - Artifact path to append
132
+ * @returns {Object} The same step object with updated artifacts
133
+ */
134
+ export function addArtifactPathToStep(step: any, artifactPath: string): any;