@testomatio/reporter 2.7.1 → 2.7.2
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 +2 -1
- package/lib/adapter/codecept.js +81 -26
- package/lib/adapter/playwright.d.ts +1 -1
- package/lib/adapter/playwright.js +54 -34
- package/lib/adapter/utils/step-formatter.d.ts +134 -0
- package/lib/adapter/utils/step-formatter.js +237 -0
- package/lib/adapter/vitest.d.ts +9 -0
- package/lib/adapter/vitest.js +75 -29
- package/lib/bin/cli.js +28 -31
- package/lib/bin/reportXml.js +5 -6
- package/lib/bin/uploadArtifacts.js +6 -6
- package/lib/client.d.ts +8 -0
- package/lib/client.js +71 -10
- package/lib/constants.d.ts +1 -0
- package/lib/constants.js +7 -1
- package/lib/pipe/bitbucket.js +2 -1
- package/lib/pipe/coverage.js +16 -15
- package/lib/pipe/debug.js +3 -3
- package/lib/pipe/github.js +3 -2
- package/lib/pipe/gitlab.js +2 -1
- package/lib/pipe/index.js +5 -5
- package/lib/pipe/testomatio.js +21 -24
- package/lib/uploader.js +3 -2
- package/lib/utils/log.d.ts +45 -0
- package/lib/utils/log.js +98 -0
- package/lib/utils/pipe_utils.js +5 -5
- package/lib/utils/utils.d.ts +10 -0
- package/lib/utils/utils.js +16 -1
- package/lib/xmlReader.js +5 -4
- package/package.json +1 -1
- package/src/adapter/codecept.js +99 -29
- package/src/adapter/playwright.js +64 -39
- package/src/adapter/utils/step-formatter.js +232 -0
- package/src/adapter/vitest.js +70 -26
- package/src/bin/cli.js +34 -31
- package/src/bin/reportXml.js +5 -6
- package/src/bin/uploadArtifacts.js +6 -6
- package/src/client.js +76 -26
- package/src/constants.js +4 -0
- package/src/pipe/bitbucket.js +2 -1
- package/src/pipe/coverage.js +16 -15
- package/src/pipe/debug.js +3 -3
- package/src/pipe/github.js +4 -3
- package/src/pipe/gitlab.js +2 -1
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +32 -25
- package/src/uploader.js +3 -2
- package/src/utils/log.js +87 -0
- package/src/utils/pipe_utils.js +5 -5
- package/src/utils/utils.js +14 -0
- package/src/xmlReader.js +5 -4
- 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
|
|
package/lib/adapter/codecept.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|
|
405
|
-
const duration = step.duration || 0;
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
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;
|