testdriverai 6.2.2 → 7.0.0
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/.github/workflows/acceptance-linux.yml +75 -0
- package/.github/workflows/acceptance-sdk-tests.yml +133 -0
- package/.vscode/settings.json +5 -1
- package/MIGRATION.md +389 -0
- package/PLUGIN_MIGRATION.md +222 -0
- package/PROMPT_CACHE.md +200 -0
- package/SDK_LOGGING.md +222 -0
- package/SDK_MIGRATION.md +474 -0
- package/SDK_README.md +1122 -0
- package/{testdriver → _testdriver}/acceptance/drag-and-drop.yaml +2 -2
- package/{testdriver → _testdriver}/acceptance/snippets/login.yaml +1 -1
- package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
- package/{testdriver → _testdriver}/examples/web/lifecycle/prerun.yaml +6 -1
- package/{testdriver → _testdriver}/lifecycle/postrun.yaml +3 -2
- package/_testdriver/lifecycle/prerun.yaml +15 -0
- package/{testdriver → _testdriver}/lifecycle/provision.yaml +7 -2
- package/agent/index.js +258 -68
- package/agent/interface.js +15 -0
- package/agent/lib/cache.js +142 -0
- package/agent/lib/commander.js +1 -39
- package/agent/lib/commands.js +143 -188
- package/agent/lib/redraw.js +6 -3
- package/agent/lib/sandbox.js +19 -5
- package/agent/lib/sdk.js +1 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debug-screenshot-1763401388589.png +0 -0
- package/debugger/index.html +15 -4
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
- package/docs/QUICK_START_TEST_RECORDING.md +215 -0
- package/docs/SDK_AWESOME_LOGS.md +468 -0
- package/docs/TEST_RECORDING.md +388 -0
- package/docs/docs.json +232 -152
- package/docs/sdk-browser-rendering.md +167 -0
- package/docs/v6/getting-started/self-hosting.mdx +407 -0
- package/docs/{guide → v6/guide}/dashcam.mdx +1 -1
- package/docs/{guide → v6/guide}/environment-variables.mdx +4 -5
- package/docs/{guide → v6/guide}/lifecycle.mdx +1 -1
- package/docs/v6/overview/comparison.mdx +101 -0
- package/docs/v7/README.md +135 -0
- package/docs/v7/api/ai.mdx +205 -0
- package/docs/v7/api/assert.mdx +285 -0
- package/docs/v7/api/assertions.mdx +403 -0
- package/docs/v7/api/click.mdx +287 -0
- package/docs/v7/api/client.mdx +322 -0
- package/docs/v7/api/elements.mdx +479 -0
- package/docs/v7/api/exec.mdx +346 -0
- package/docs/v7/api/find.mdx +316 -0
- package/docs/v7/api/focusApplication.mdx +294 -0
- package/docs/v7/api/hover.mdx +279 -0
- package/docs/v7/api/pressKeys.mdx +349 -0
- package/docs/v7/api/sandbox.mdx +404 -0
- package/docs/v7/api/scroll.mdx +300 -0
- package/docs/v7/api/type.mdx +314 -0
- package/docs/v7/commands/assert.mdx +45 -0
- package/docs/v7/commands/exec.mdx +282 -0
- package/docs/v7/commands/focus-application.mdx +44 -0
- package/docs/v7/commands/hover-image.mdx +69 -0
- package/docs/v7/commands/hover-text.mdx +47 -0
- package/docs/v7/commands/if.mdx +53 -0
- package/docs/v7/commands/match-image.mdx +67 -0
- package/docs/v7/commands/press-keys.mdx +87 -0
- package/docs/v7/commands/remember.mdx +49 -0
- package/docs/v7/commands/run.mdx +44 -0
- package/docs/v7/commands/scroll-until-image.mdx +66 -0
- package/docs/v7/commands/scroll-until-text.mdx +60 -0
- package/docs/v7/commands/scroll.mdx +69 -0
- package/docs/v7/commands/type.mdx +45 -0
- package/docs/v7/commands/wait-for-image.mdx +54 -0
- package/docs/v7/commands/wait-for-text.mdx +48 -0
- package/docs/v7/commands/wait.mdx +45 -0
- package/docs/v7/getting-started/quickstart.mdx +199 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/playwright.mdx +342 -0
- package/eslint.config.js +19 -1
- package/examples/run-tests-with-recording.sh +70 -0
- package/examples/screenshot-example.js +63 -0
- package/examples/sdk-awesome-logs-demo.js +177 -0
- package/examples/sdk-cache-thresholds.js +96 -0
- package/examples/sdk-element-properties.js +155 -0
- package/examples/sdk-simple-example.js +65 -0
- package/examples/test-recording-example.test.js +166 -0
- package/interfaces/cli/lib/base.js +10 -4
- package/interfaces/logger.js +2 -1
- package/interfaces/shared-test-state.mjs +69 -0
- package/interfaces/vitest-plugin.mjs +744 -0
- package/mcp-server/AI_GUIDELINES.md +57 -0
- package/package.json +18 -5
- package/schema.json +8 -29
- package/scripts/view-test-results.mjs +96 -0
- package/sdk-log-formatter.js +714 -0
- package/sdk.d.ts +735 -0
- package/sdk.js +1906 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/test/mcp-example-test.yaml +27 -0
- package/test-find-api.js +73 -0
- package/test-prompt-cache.js +96 -0
- package/test-sandbox-render.js +28 -0
- package/test-sdk-methods.js +15 -0
- package/test-sdk-refactor.js +53 -0
- package/test-stack-trace.mjs +57 -0
- package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +61 -0
- package/testdriver/acceptance-sdk/README.md +128 -0
- package/testdriver/acceptance-sdk/TEST_REPORTING.md +245 -0
- package/testdriver/acceptance-sdk/assert.test.mjs +44 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +70 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +38 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +55 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +71 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +69 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +48 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +41 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +43 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +50 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +41 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +48 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +64 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +45 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +52 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +51 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +42 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +50 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +239 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +648 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +84 -0
- package/verify-element-api.js +89 -0
- package/verify-types.js +0 -0
- package/vitest.config.example.js +19 -0
- package/vitest.config.mjs +65 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- package/docs/overview/comparison.mdx +0 -82
- package/testdriver/lifecycle/prerun.yaml +0 -17
- /package/{testdriver/examples/desktop/lifecycle/prerun.yaml → .env.example} +0 -0
- /package/{testdriver → _testdriver}/acceptance/assert.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/dashcam.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/embed.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-js.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-output.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-shell.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/focus-window.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text-with-description.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/if-else.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/match-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/press-keys.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/prompt.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/remember.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/screenshots/cart.png +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-keyboard.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/snippets/match-cart.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/type.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/failure.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/prerun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/secrets.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/dashcam-chrome.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/exec-pwsh-multiline.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-exception.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-promise.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-in-middle.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-nested.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/success-test.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/example.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/readme.md +0 -0
- /package/{testdriver → _testdriver}/examples/chrome-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/desktop/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/vscode-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/web/lifecycle/postrun.yaml +0 -0
- /package/docs/{account → v6/account}/dashboard.mdx +0 -0
- /package/docs/{account → v6/account}/enterprise.mdx +0 -0
- /package/docs/{account → v6/account}/pricing.mdx +0 -0
- /package/docs/{account → v6/account}/projects.mdx +0 -0
- /package/docs/{account → v6/account}/team.mdx +0 -0
- /package/docs/{action → v6/action}/ami.mdx +0 -0
- /package/docs/{action → v6/action}/performance.mdx +0 -0
- /package/docs/{action → v6/action}/secrets.mdx +0 -0
- /package/docs/{apps → v6/apps}/chrome-extensions.mdx +0 -0
- /package/docs/{apps → v6/apps}/desktop-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/mobile-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/static-websites.mdx +0 -0
- /package/docs/{apps → v6/apps}/tauri-apps.mdx +0 -0
- /package/docs/{bugs → v6/bugs}/jira.mdx +0 -0
- /package/docs/{cli → v6/cli}/overview.mdx +0 -0
- /package/docs/{commands → v6/commands}/assert.mdx +0 -0
- /package/docs/{commands → v6/commands}/exec.mdx +0 -0
- /package/docs/{commands → v6/commands}/focus-application.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/if.mdx +0 -0
- /package/docs/{commands → v6/commands}/match-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/press-keys.mdx +0 -0
- /package/docs/{commands → v6/commands}/remember.mdx +0 -0
- /package/docs/{commands → v6/commands}/run.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll.mdx +0 -0
- /package/docs/{commands → v6/commands}/type.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/junit.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/playwright.mdx +0 -0
- /package/docs/{features → v6/features}/auto-healing.mdx +0 -0
- /package/docs/{features → v6/features}/generation.mdx +0 -0
- /package/docs/{features → v6/features}/parallel-testing.mdx +0 -0
- /package/docs/{features → v6/features}/reusable-snippets.mdx +0 -0
- /package/docs/{features → v6/features}/selectorless.mdx +0 -0
- /package/docs/{features → v6/features}/visual-assertions.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/ci.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/cli.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/editing.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/playwright.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/running.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/vscode.mdx +0 -0
- /package/docs/{guide → v6/guide}/assertions.mdx +0 -0
- /package/docs/{guide → v6/guide}/authentication.mdx +0 -0
- /package/docs/{guide → v6/guide}/code.mdx +0 -0
- /package/docs/{guide → v6/guide}/locating.mdx +0 -0
- /package/docs/{guide → v6/guide}/protips.mdx +0 -0
- /package/docs/{guide → v6/guide}/variables.mdx +0 -0
- /package/docs/{guide → v6/guide}/waiting.mdx +0 -0
- /package/docs/{importing → v6/importing}/csv.mdx +0 -0
- /package/docs/{importing → v6/importing}/gherkin.mdx +0 -0
- /package/docs/{importing → v6/importing}/jira.mdx +0 -0
- /package/docs/{importing → v6/importing}/testrail.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/electron.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/netlify.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/vercel.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/explore.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/run.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/save.mdx +0 -0
- /package/docs/{overview → v6/overview}/faq.mdx +0 -0
- /package/docs/{overview → v6/overview}/performance.mdx +0 -0
- /package/docs/{overview → v6/overview}/quickstart.mdx +0 -0
- /package/docs/{overview → v6/overview}/what-is-testdriver.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/ai-chatbot.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/cookie-banner.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/file-upload.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/form-filling.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/log-in.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/pdf-generation.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/spell-check.mdx +0 -0
- /package/docs/{security → v6/security}/action.mdx +0 -0
- /package/docs/{security → v6/security}/agent.mdx +0 -0
- /package/docs/{security → v6/security}/platform.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/advanced-test.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/basic-test.mdx +0 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AWESOME Log formatter for TestDriver SDK 🎨
|
|
5
|
+
* Provides beautiful, emoji-rich formatting with great DX for logs sent to dashcam
|
|
6
|
+
* ANSI codes are preserved through the log pipeline: SDK → sandbox → /tmp/testdriver.log → dashcam
|
|
7
|
+
*
|
|
8
|
+
* Now with full UTF-8 and emoji support! 🚀
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class SDKLogFormatter {
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.testContext = {
|
|
14
|
+
currentTest: null,
|
|
15
|
+
currentFile: null,
|
|
16
|
+
startTime: null,
|
|
17
|
+
};
|
|
18
|
+
this.eventCount = 0;
|
|
19
|
+
this.useColors = options.colors !== false;
|
|
20
|
+
this.useEmojis = options.emojis !== false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Set the current test context from Vitest
|
|
25
|
+
* @param {Object} context - Test context with file, test name, etc.
|
|
26
|
+
*/
|
|
27
|
+
setTestContext(context) {
|
|
28
|
+
if (context.file) this.testContext.currentFile = context.file;
|
|
29
|
+
if (context.test) this.testContext.currentTest = context.test;
|
|
30
|
+
if (context.startTime) this.testContext.startTime = context.startTime;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get elapsed time since test start
|
|
35
|
+
* @returns {string} Formatted elapsed time
|
|
36
|
+
*/
|
|
37
|
+
getElapsedTime() {
|
|
38
|
+
if (!this.testContext.startTime) return "";
|
|
39
|
+
const elapsed = Date.now() - this.testContext.startTime;
|
|
40
|
+
const seconds = (elapsed / 1000).toFixed(2);
|
|
41
|
+
return `[${seconds}s]`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format a log message in Vitest style
|
|
46
|
+
* @param {string} type - Log type (info, success, error, action, debug)
|
|
47
|
+
* @param {string} message - The message to format
|
|
48
|
+
* @param {Object} meta - Additional metadata
|
|
49
|
+
* @returns {string} Formatted log message
|
|
50
|
+
*/
|
|
51
|
+
format(type, message, meta = {}) {
|
|
52
|
+
this.eventCount++;
|
|
53
|
+
|
|
54
|
+
const parts = [];
|
|
55
|
+
|
|
56
|
+
// Add timestamp/elapsed time
|
|
57
|
+
const timeStr = this.getElapsedTime();
|
|
58
|
+
if (timeStr) parts.push(timeStr);
|
|
59
|
+
|
|
60
|
+
// Add type prefix with color
|
|
61
|
+
const prefix = this.getPrefix(type);
|
|
62
|
+
if (prefix) parts.push(prefix);
|
|
63
|
+
|
|
64
|
+
// Add message
|
|
65
|
+
parts.push(this.formatMessage(type, message));
|
|
66
|
+
|
|
67
|
+
// Add metadata if present
|
|
68
|
+
if (meta.duration) {
|
|
69
|
+
parts.push(chalk.dim(`(${meta.duration})`));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return parts.join(" ");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get prefix for log type with AWESOME colors and emojis 🎨
|
|
77
|
+
* @param {string} type - Log type
|
|
78
|
+
* @returns {string} Colored prefix with emoji
|
|
79
|
+
*/
|
|
80
|
+
getPrefix(type) {
|
|
81
|
+
if (!this.useEmojis) {
|
|
82
|
+
// Fallback to simple symbols without emojis
|
|
83
|
+
const simplePrefixes = {
|
|
84
|
+
info: chalk.blue("ℹ"),
|
|
85
|
+
success: chalk.green("✓"),
|
|
86
|
+
error: chalk.red("✖"),
|
|
87
|
+
action: chalk.cyan("→"),
|
|
88
|
+
debug: chalk.gray("⚙"),
|
|
89
|
+
find: chalk.magenta("⌕"),
|
|
90
|
+
click: chalk.cyan("▸"),
|
|
91
|
+
type: chalk.yellow("⌨"),
|
|
92
|
+
assert: chalk.green("✓"),
|
|
93
|
+
scroll: chalk.blue("↕"),
|
|
94
|
+
hover: chalk.cyan("→"),
|
|
95
|
+
wait: chalk.yellow("⏱"),
|
|
96
|
+
connect: chalk.green("⚡"),
|
|
97
|
+
disconnect: chalk.red("⏹"),
|
|
98
|
+
};
|
|
99
|
+
return simplePrefixes[type] || chalk.gray("•");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const prefixes = {
|
|
103
|
+
// Core actions - hand gestures
|
|
104
|
+
info: chalk.blue("ℹ️"),
|
|
105
|
+
success: chalk.green("✅"),
|
|
106
|
+
error: chalk.red("❌"),
|
|
107
|
+
warning: chalk.yellow("⚠️"),
|
|
108
|
+
|
|
109
|
+
// Finding elements
|
|
110
|
+
find: chalk.magenta("🔍"),
|
|
111
|
+
findAll: chalk.magenta("🔎"),
|
|
112
|
+
|
|
113
|
+
// Mouse actions
|
|
114
|
+
click: chalk.cyan("👆"),
|
|
115
|
+
doubleClick: chalk.cyan("👆👆"),
|
|
116
|
+
rightClick: chalk.cyan("🖱️"),
|
|
117
|
+
hover: chalk.cyan("👉"),
|
|
118
|
+
drag: chalk.cyan("✊"),
|
|
119
|
+
|
|
120
|
+
// Keyboard actions
|
|
121
|
+
type: chalk.yellow("⌨️"),
|
|
122
|
+
pressKeys: chalk.yellow("🎹"),
|
|
123
|
+
|
|
124
|
+
// Navigation
|
|
125
|
+
scroll: chalk.blue("📜"),
|
|
126
|
+
scrollUp: chalk.blue("⬆️"),
|
|
127
|
+
scrollDown: chalk.blue("⬇️"),
|
|
128
|
+
navigate: chalk.blue("🧭"),
|
|
129
|
+
|
|
130
|
+
// Validation
|
|
131
|
+
assert: chalk.green("✅"),
|
|
132
|
+
verify: chalk.green("🔍"),
|
|
133
|
+
remember: chalk.blue("🧠"),
|
|
134
|
+
|
|
135
|
+
// System
|
|
136
|
+
connect: chalk.green("🔌"),
|
|
137
|
+
disconnect: chalk.red("🔌"),
|
|
138
|
+
screenshot: chalk.blue("📸"),
|
|
139
|
+
wait: chalk.yellow("⏳"),
|
|
140
|
+
|
|
141
|
+
// Focus & Windows
|
|
142
|
+
focusApplication: chalk.cyan("🎯"),
|
|
143
|
+
|
|
144
|
+
// Cache
|
|
145
|
+
cacheHit: chalk.yellow("⚡"),
|
|
146
|
+
cacheMiss: chalk.gray("💤"),
|
|
147
|
+
|
|
148
|
+
// Debug
|
|
149
|
+
debug: chalk.gray("🔧"),
|
|
150
|
+
|
|
151
|
+
// Default
|
|
152
|
+
action: chalk.cyan("▶️"),
|
|
153
|
+
};
|
|
154
|
+
return prefixes[type] || chalk.gray("•");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Format the message content with appropriate styling
|
|
159
|
+
* @param {string} type - Log type
|
|
160
|
+
* @param {string} message - Raw message
|
|
161
|
+
* @returns {string} Formatted message
|
|
162
|
+
*/
|
|
163
|
+
formatMessage(type, message) {
|
|
164
|
+
if (!this.useColors) return message;
|
|
165
|
+
|
|
166
|
+
const formatters = {
|
|
167
|
+
success: (msg) => chalk.green(msg),
|
|
168
|
+
error: (msg) => chalk.red(msg),
|
|
169
|
+
debug: (msg) => chalk.dim(msg),
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return formatters[type] ? formatters[type](message) : message;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Format an element finding message (when search starts) 🔍
|
|
177
|
+
* @param {string} description - Element description
|
|
178
|
+
* @returns {string} Formatted message
|
|
179
|
+
*/
|
|
180
|
+
formatElementFinding(description) {
|
|
181
|
+
const parts = [];
|
|
182
|
+
|
|
183
|
+
// Time and icon on same line
|
|
184
|
+
const timeStr = this.getElapsedTime();
|
|
185
|
+
if (timeStr) {
|
|
186
|
+
parts.push(chalk.dim(timeStr));
|
|
187
|
+
}
|
|
188
|
+
parts.push(this.getPrefix("find"));
|
|
189
|
+
|
|
190
|
+
// Main message with emphasis
|
|
191
|
+
parts.push(chalk.bold.cyan("Finding"));
|
|
192
|
+
parts.push(chalk.cyan(`"${description}"`));
|
|
193
|
+
|
|
194
|
+
return parts.join(" ");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Format an element found message with AWESOME styling 🎯
|
|
199
|
+
* @param {string} description - Element description
|
|
200
|
+
* @param {Object} meta - Element metadata (coordinates, duration, cache hit)
|
|
201
|
+
* @returns {string} Formatted message
|
|
202
|
+
*/
|
|
203
|
+
formatElementFound(description, meta = {}) {
|
|
204
|
+
const parts = [];
|
|
205
|
+
|
|
206
|
+
// Time and icon on same line
|
|
207
|
+
const timeStr = this.getElapsedTime();
|
|
208
|
+
if (timeStr) {
|
|
209
|
+
parts.push(chalk.dim(timeStr));
|
|
210
|
+
}
|
|
211
|
+
parts.push(this.getPrefix("find"));
|
|
212
|
+
|
|
213
|
+
// Main message with emphasis
|
|
214
|
+
parts.push(chalk.bold.green("Found"));
|
|
215
|
+
parts.push(chalk.cyan(`"${description}"`));
|
|
216
|
+
|
|
217
|
+
// Metadata on same line with subtle styling
|
|
218
|
+
const metaParts = [];
|
|
219
|
+
if (meta.x !== undefined && meta.y !== undefined) {
|
|
220
|
+
metaParts.push(chalk.dim.gray(`📍 (${meta.x}, ${meta.y})`));
|
|
221
|
+
}
|
|
222
|
+
if (meta.duration) {
|
|
223
|
+
const durationMs = parseInt(meta.duration);
|
|
224
|
+
const durationColor =
|
|
225
|
+
durationMs < 100
|
|
226
|
+
? chalk.green
|
|
227
|
+
: durationMs < 500
|
|
228
|
+
? chalk.yellow
|
|
229
|
+
: chalk.red;
|
|
230
|
+
metaParts.push(chalk.dim(`⏱️ ${durationColor(meta.duration)}`));
|
|
231
|
+
}
|
|
232
|
+
if (meta.cacheHit) {
|
|
233
|
+
metaParts.push(chalk.bold.yellow("⚡ cached"));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (metaParts.length > 0) {
|
|
237
|
+
parts.push(chalk.dim("·"));
|
|
238
|
+
parts.push(metaParts.join(chalk.dim(" · ")));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return parts.join(" ");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Format a finding all message (when search starts) 🔎
|
|
246
|
+
* @param {string} description - Element description
|
|
247
|
+
* @returns {string} Formatted message
|
|
248
|
+
*/
|
|
249
|
+
formatElementsFinding(description) {
|
|
250
|
+
const parts = [];
|
|
251
|
+
|
|
252
|
+
// Time and icon on same line
|
|
253
|
+
const timeStr = this.getElapsedTime();
|
|
254
|
+
if (timeStr) {
|
|
255
|
+
parts.push(chalk.dim(timeStr));
|
|
256
|
+
}
|
|
257
|
+
parts.push(this.getPrefix("findAll"));
|
|
258
|
+
|
|
259
|
+
// Main message with emphasis
|
|
260
|
+
parts.push(chalk.bold.cyan("Finding All"));
|
|
261
|
+
parts.push(chalk.cyan(`"${description}"`));
|
|
262
|
+
|
|
263
|
+
return parts.join(" ");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Format a found all message with AWESOME styling 🎯
|
|
268
|
+
* @param {string} description - Element description
|
|
269
|
+
* @param {number} count - Number of elements found
|
|
270
|
+
* @param {Object} meta - Metadata (duration, cache hit)
|
|
271
|
+
* @returns {string} Formatted message
|
|
272
|
+
*/
|
|
273
|
+
formatElementsFound(description, count, meta = {}) {
|
|
274
|
+
const parts = [];
|
|
275
|
+
|
|
276
|
+
// Time and icon on same line
|
|
277
|
+
const timeStr = this.getElapsedTime();
|
|
278
|
+
if (timeStr) {
|
|
279
|
+
parts.push(chalk.dim(timeStr));
|
|
280
|
+
}
|
|
281
|
+
parts.push(this.getPrefix("findAll"));
|
|
282
|
+
|
|
283
|
+
// Main message with emphasis
|
|
284
|
+
parts.push(chalk.bold.green("Found"));
|
|
285
|
+
parts.push(chalk.cyan(`${count}`));
|
|
286
|
+
parts.push(chalk.cyan(`"${description}"`));
|
|
287
|
+
|
|
288
|
+
// Metadata on same line with subtle styling
|
|
289
|
+
const metaParts = [];
|
|
290
|
+
if (meta.duration) {
|
|
291
|
+
const durationMs = parseInt(meta.duration);
|
|
292
|
+
const durationColor =
|
|
293
|
+
durationMs < 100
|
|
294
|
+
? chalk.green
|
|
295
|
+
: durationMs < 500
|
|
296
|
+
? chalk.yellow
|
|
297
|
+
: chalk.red;
|
|
298
|
+
metaParts.push(chalk.dim(`⏱️ ${durationColor(meta.duration)}`));
|
|
299
|
+
}
|
|
300
|
+
if (meta.cacheHit) {
|
|
301
|
+
metaParts.push(chalk.bold.yellow("⚡ cached"));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (metaParts.length > 0) {
|
|
305
|
+
parts.push(chalk.dim("·"));
|
|
306
|
+
parts.push(metaParts.join(chalk.dim(" · ")));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return parts.join(" ");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Format an asserting message (when assertion starts) ✓
|
|
314
|
+
* @param {string} assertion - What is being asserted
|
|
315
|
+
* @returns {string} Formatted message
|
|
316
|
+
*/
|
|
317
|
+
formatAsserting(assertion) {
|
|
318
|
+
const parts = [];
|
|
319
|
+
|
|
320
|
+
// Time and icon
|
|
321
|
+
const timeStr = this.getElapsedTime();
|
|
322
|
+
if (timeStr) {
|
|
323
|
+
parts.push(chalk.dim(timeStr));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
parts.push(this.getPrefix("assert"));
|
|
327
|
+
parts.push(chalk.bold.cyan("Asserting"));
|
|
328
|
+
parts.push(chalk.cyan(`"${assertion}"`));
|
|
329
|
+
|
|
330
|
+
return parts.join(" ");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Format an action message with AWESOME emojis! 🎬
|
|
335
|
+
* @param {string} action - Action type
|
|
336
|
+
* @param {string} description - Description or target
|
|
337
|
+
* @param {Object} meta - Action metadata
|
|
338
|
+
* @returns {string} Formatted message
|
|
339
|
+
*/
|
|
340
|
+
formatAction(action, description, meta = {}) {
|
|
341
|
+
const parts = [];
|
|
342
|
+
|
|
343
|
+
// Time and icon
|
|
344
|
+
const timeStr = this.getElapsedTime();
|
|
345
|
+
if (timeStr) {
|
|
346
|
+
parts.push(chalk.dim(timeStr));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Use action-specific prefix
|
|
350
|
+
const actionKey = action.toLowerCase().replace(/\s+/g, "");
|
|
351
|
+
parts.push(this.getPrefix(actionKey));
|
|
352
|
+
|
|
353
|
+
// Action text with emphasis and color coding
|
|
354
|
+
const actionText =
|
|
355
|
+
action.charAt(0).toUpperCase() + action.slice(1).toLowerCase();
|
|
356
|
+
const actionColors = {
|
|
357
|
+
click: chalk.bold.cyan,
|
|
358
|
+
hover: chalk.bold.blue,
|
|
359
|
+
type: chalk.bold.yellow,
|
|
360
|
+
scroll: chalk.bold.magenta,
|
|
361
|
+
assert: chalk.bold.green,
|
|
362
|
+
wait: chalk.bold.yellow,
|
|
363
|
+
};
|
|
364
|
+
const colorFn = actionColors[actionKey] || chalk.bold.white;
|
|
365
|
+
parts.push(colorFn(actionText));
|
|
366
|
+
|
|
367
|
+
// Target with color
|
|
368
|
+
if (description) {
|
|
369
|
+
parts.push(chalk.cyan(`"${description}"`));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Additional metadata
|
|
373
|
+
const metaParts = [];
|
|
374
|
+
if (meta.text) {
|
|
375
|
+
metaParts.push(chalk.gray(`→ ${chalk.white(meta.text)}`));
|
|
376
|
+
}
|
|
377
|
+
if (meta.duration) {
|
|
378
|
+
const durationMs = parseInt(meta.duration);
|
|
379
|
+
const durationColor =
|
|
380
|
+
durationMs < 50
|
|
381
|
+
? chalk.green
|
|
382
|
+
: durationMs < 200
|
|
383
|
+
? chalk.yellow
|
|
384
|
+
: chalk.red;
|
|
385
|
+
metaParts.push(chalk.dim(`⏱️ ${durationColor(meta.duration)}`));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (metaParts.length > 0) {
|
|
389
|
+
parts.push(chalk.dim("·"));
|
|
390
|
+
parts.push(metaParts.join(chalk.dim(" · ")));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return parts.join(" ");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Format an assertion message with beautiful status indicators 🎯
|
|
398
|
+
* @param {string} assertion - What is being asserted
|
|
399
|
+
* @param {boolean} passed - Whether assertion passed
|
|
400
|
+
* @param {Object} meta - Assertion metadata
|
|
401
|
+
* @returns {string} Formatted message
|
|
402
|
+
*/
|
|
403
|
+
formatAssertion(assertion, passed, meta = {}) {
|
|
404
|
+
const parts = [];
|
|
405
|
+
|
|
406
|
+
// Time and icon
|
|
407
|
+
const timeStr = this.getElapsedTime();
|
|
408
|
+
if (timeStr) {
|
|
409
|
+
parts.push(chalk.dim(timeStr));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (passed) {
|
|
413
|
+
parts.push(this.getPrefix("success"));
|
|
414
|
+
parts.push(chalk.bold.green("Assert"));
|
|
415
|
+
parts.push(chalk.cyan(`"${assertion}"`));
|
|
416
|
+
parts.push(chalk.dim("·"));
|
|
417
|
+
parts.push(chalk.bold.green("✓ PASSED"));
|
|
418
|
+
} else {
|
|
419
|
+
parts.push(this.getPrefix("error"));
|
|
420
|
+
parts.push(chalk.bold.red("Assert"));
|
|
421
|
+
parts.push(chalk.cyan(`"${assertion}"`));
|
|
422
|
+
parts.push(chalk.dim("·"));
|
|
423
|
+
parts.push(chalk.bold.red("✗ FAILED"));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (meta.duration) {
|
|
427
|
+
const durationMs = parseInt(meta.duration);
|
|
428
|
+
const durationColor =
|
|
429
|
+
durationMs < 100
|
|
430
|
+
? chalk.green
|
|
431
|
+
: durationMs < 500
|
|
432
|
+
? chalk.yellow
|
|
433
|
+
: chalk.red;
|
|
434
|
+
parts.push(chalk.dim("·"));
|
|
435
|
+
parts.push(chalk.dim(`⏱️ ${durationColor(meta.duration)}`));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return parts.join(" ");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Format an error message with clear visual indicators 🚨
|
|
443
|
+
* @param {string} message - Error message
|
|
444
|
+
* @param {Error} error - Error object
|
|
445
|
+
* @returns {string} Formatted error
|
|
446
|
+
*/
|
|
447
|
+
formatError(message, error) {
|
|
448
|
+
const parts = [];
|
|
449
|
+
|
|
450
|
+
const timeStr = this.getElapsedTime();
|
|
451
|
+
if (timeStr) {
|
|
452
|
+
parts.push(chalk.dim(timeStr));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
parts.push(this.getPrefix("error"));
|
|
456
|
+
parts.push(chalk.red.bold(message));
|
|
457
|
+
|
|
458
|
+
if (error && error.message) {
|
|
459
|
+
parts.push(chalk.dim("→"));
|
|
460
|
+
parts.push(chalk.red(error.message));
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return parts.join(" ");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Format a connection/disconnection message 🔌
|
|
468
|
+
* @param {string} type - 'connect' or 'disconnect'
|
|
469
|
+
* @param {Object} meta - Connection metadata
|
|
470
|
+
* @returns {string} Formatted message
|
|
471
|
+
*/
|
|
472
|
+
formatConnection(type, meta = {}) {
|
|
473
|
+
const parts = [];
|
|
474
|
+
|
|
475
|
+
const timeStr = this.getElapsedTime();
|
|
476
|
+
if (timeStr) {
|
|
477
|
+
parts.push(chalk.dim(timeStr));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
parts.push(this.getPrefix(type));
|
|
481
|
+
|
|
482
|
+
if (type === "connect") {
|
|
483
|
+
parts.push(chalk.bold.green("Connected"));
|
|
484
|
+
if (meta.sandboxId) {
|
|
485
|
+
parts.push(chalk.dim("·"));
|
|
486
|
+
parts.push(chalk.cyan(`Sandbox: ${meta.sandboxId}`));
|
|
487
|
+
}
|
|
488
|
+
if (meta.os) {
|
|
489
|
+
parts.push(chalk.dim("·"));
|
|
490
|
+
parts.push(chalk.gray(`OS: ${meta.os}`));
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
parts.push(chalk.bold.yellow("Disconnected"));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return parts.join(" ");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Format a screenshot message 📸
|
|
501
|
+
* @param {Object} meta - Screenshot metadata
|
|
502
|
+
* @returns {string} Formatted message
|
|
503
|
+
*/
|
|
504
|
+
formatScreenshot(meta = {}) {
|
|
505
|
+
const parts = [];
|
|
506
|
+
|
|
507
|
+
const timeStr = this.getElapsedTime();
|
|
508
|
+
if (timeStr) {
|
|
509
|
+
parts.push(chalk.dim(timeStr));
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
parts.push(this.getPrefix("screenshot"));
|
|
513
|
+
parts.push(chalk.bold.blue("Screenshot"));
|
|
514
|
+
|
|
515
|
+
if (meta.path) {
|
|
516
|
+
parts.push(chalk.dim("·"));
|
|
517
|
+
parts.push(chalk.cyan(meta.path));
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (meta.size) {
|
|
521
|
+
parts.push(chalk.dim("·"));
|
|
522
|
+
parts.push(chalk.gray(`${meta.size}`));
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return parts.join(" ");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Format a cache status message ⚡
|
|
530
|
+
* @param {boolean} hit - Whether it was a cache hit
|
|
531
|
+
* @param {Object} meta - Cache metadata
|
|
532
|
+
* @returns {string} Formatted message
|
|
533
|
+
*/
|
|
534
|
+
formatCacheStatus(hit, meta = {}) {
|
|
535
|
+
const parts = [];
|
|
536
|
+
|
|
537
|
+
parts.push(this.getPrefix(hit ? "cacheHit" : "cacheMiss"));
|
|
538
|
+
|
|
539
|
+
if (hit) {
|
|
540
|
+
parts.push(chalk.bold.yellow("Cache HIT"));
|
|
541
|
+
if (meta.similarity !== undefined) {
|
|
542
|
+
const similarity = (meta.similarity * 100).toFixed(1);
|
|
543
|
+
parts.push(chalk.dim("·"));
|
|
544
|
+
parts.push(chalk.green(`${similarity}% similar`));
|
|
545
|
+
}
|
|
546
|
+
} else {
|
|
547
|
+
parts.push(chalk.dim.gray("Cache MISS"));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (meta.strategy) {
|
|
551
|
+
parts.push(chalk.dim("·"));
|
|
552
|
+
parts.push(chalk.gray(meta.strategy));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return parts.join(" ");
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Create a beautiful section header with box drawing 📦
|
|
560
|
+
* @param {string} title - Section title
|
|
561
|
+
* @param {string} emoji - Optional emoji to prefix
|
|
562
|
+
* @returns {string} Formatted header
|
|
563
|
+
*/
|
|
564
|
+
formatHeader(title, emoji = "✨") {
|
|
565
|
+
const width = Math.min(60, Math.max(title.length + 4, 40));
|
|
566
|
+
const topLine = chalk.dim("╭" + "─".repeat(width - 2) + "╮");
|
|
567
|
+
const titleLine =
|
|
568
|
+
`${chalk.dim("│")} ${emoji} ${chalk.bold.white(title)}`.padEnd(
|
|
569
|
+
width + 20,
|
|
570
|
+
) + chalk.dim("│");
|
|
571
|
+
const bottomLine = chalk.dim("╰" + "─".repeat(width - 2) + "╯");
|
|
572
|
+
return `\n${topLine}\n${titleLine}\n${bottomLine}\n`;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Format a simple divider
|
|
577
|
+
* @param {string} char - Character to use for divider
|
|
578
|
+
* @returns {string} Formatted divider
|
|
579
|
+
*/
|
|
580
|
+
formatDivider(char = "─") {
|
|
581
|
+
return chalk.dim(char.repeat(60));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Format a beautiful summary line with stats 📊
|
|
586
|
+
* @param {Object} stats - Test statistics
|
|
587
|
+
* @returns {string} Formatted summary
|
|
588
|
+
*/
|
|
589
|
+
formatSummary(stats) {
|
|
590
|
+
const parts = [];
|
|
591
|
+
|
|
592
|
+
if (stats.passed > 0) {
|
|
593
|
+
parts.push(chalk.bold.green(`✓ ${stats.passed} passed`));
|
|
594
|
+
}
|
|
595
|
+
if (stats.failed > 0) {
|
|
596
|
+
parts.push(chalk.bold.red(`✗ ${stats.failed} failed`));
|
|
597
|
+
}
|
|
598
|
+
if (stats.skipped > 0) {
|
|
599
|
+
parts.push(chalk.yellow(`⊘ ${stats.skipped} skipped`));
|
|
600
|
+
}
|
|
601
|
+
if (stats.total > 0) {
|
|
602
|
+
parts.push(chalk.dim(`${stats.total} total`));
|
|
603
|
+
}
|
|
604
|
+
if (stats.duration) {
|
|
605
|
+
parts.push(chalk.dim(`⏱️ ${stats.duration}`));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const separator = chalk.dim(" │ ");
|
|
609
|
+
return `\n${chalk.dim("─".repeat(60))}\n${parts.join(separator)}\n${chalk.dim("─".repeat(60))}\n`;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Format a progress indicator 📈
|
|
614
|
+
* @param {number} current - Current step
|
|
615
|
+
* @param {number} total - Total steps
|
|
616
|
+
* @param {string} message - Progress message
|
|
617
|
+
* @returns {string} Formatted progress
|
|
618
|
+
*/
|
|
619
|
+
formatProgress(current, total, message = "") {
|
|
620
|
+
const percentage = Math.round((current / total) * 100);
|
|
621
|
+
const barWidth = 20;
|
|
622
|
+
const filled = Math.round((current / total) * barWidth);
|
|
623
|
+
const empty = barWidth - filled;
|
|
624
|
+
|
|
625
|
+
const bar = chalk.green("█".repeat(filled)) + chalk.dim("░".repeat(empty));
|
|
626
|
+
const stats = chalk.dim(`${current}/${total}`);
|
|
627
|
+
|
|
628
|
+
const parts = [
|
|
629
|
+
chalk.bold("Progress"),
|
|
630
|
+
bar,
|
|
631
|
+
chalk.cyan(`${percentage}%`),
|
|
632
|
+
stats,
|
|
633
|
+
];
|
|
634
|
+
|
|
635
|
+
if (message) {
|
|
636
|
+
parts.push(chalk.dim("·"));
|
|
637
|
+
parts.push(chalk.gray(message));
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return parts.join(" ");
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Format a waiting/loading message ⏳
|
|
645
|
+
* @param {string} message - What we're waiting for
|
|
646
|
+
* @param {number} elapsed - Elapsed time in ms
|
|
647
|
+
* @returns {string} Formatted waiting message
|
|
648
|
+
*/
|
|
649
|
+
formatWaiting(message, elapsed) {
|
|
650
|
+
const parts = [];
|
|
651
|
+
|
|
652
|
+
parts.push(this.getPrefix("wait"));
|
|
653
|
+
parts.push(chalk.bold.yellow("Waiting"));
|
|
654
|
+
parts.push(chalk.cyan(message));
|
|
655
|
+
|
|
656
|
+
if (elapsed) {
|
|
657
|
+
const seconds = (elapsed / 1000).toFixed(1);
|
|
658
|
+
parts.push(chalk.dim("·"));
|
|
659
|
+
parts.push(chalk.gray(`${seconds}s`));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return parts.join(" ");
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Format test start message 🚀
|
|
667
|
+
* @param {string} testName - Name of the test
|
|
668
|
+
* @returns {string} Formatted test start
|
|
669
|
+
*/
|
|
670
|
+
formatTestStart(testName) {
|
|
671
|
+
return `\n${chalk.bold.cyan("▶️ Running:")} ${chalk.white(testName)}\n`;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Format test end message with result 🏁
|
|
676
|
+
* @param {string} testName - Name of the test
|
|
677
|
+
* @param {boolean} passed - Whether test passed
|
|
678
|
+
* @param {number} duration - Test duration in ms
|
|
679
|
+
* @returns {string} Formatted test end
|
|
680
|
+
*/
|
|
681
|
+
formatTestEnd(testName, passed, duration) {
|
|
682
|
+
const parts = [];
|
|
683
|
+
|
|
684
|
+
if (passed) {
|
|
685
|
+
parts.push(chalk.bold.green("✅ PASSED"));
|
|
686
|
+
} else {
|
|
687
|
+
parts.push(chalk.bold.red("❌ FAILED"));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
parts.push(chalk.white(testName));
|
|
691
|
+
|
|
692
|
+
if (duration) {
|
|
693
|
+
const seconds = (duration / 1000).toFixed(2);
|
|
694
|
+
const durationColor =
|
|
695
|
+
duration < 1000
|
|
696
|
+
? chalk.green
|
|
697
|
+
: duration < 5000
|
|
698
|
+
? chalk.yellow
|
|
699
|
+
: chalk.red;
|
|
700
|
+
parts.push(chalk.dim("·"));
|
|
701
|
+
parts.push(durationColor(`${seconds}s`));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return `\n${parts.join(" ")}\n`;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Export singleton instance
|
|
709
|
+
const formatter = new SDKLogFormatter();
|
|
710
|
+
|
|
711
|
+
module.exports = {
|
|
712
|
+
SDKLogFormatter,
|
|
713
|
+
formatter,
|
|
714
|
+
};
|