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.
Files changed (264) hide show
  1. package/.github/workflows/acceptance-linux.yml +75 -0
  2. package/.github/workflows/acceptance-sdk-tests.yml +133 -0
  3. package/.vscode/settings.json +5 -1
  4. package/MIGRATION.md +389 -0
  5. package/PLUGIN_MIGRATION.md +222 -0
  6. package/PROMPT_CACHE.md +200 -0
  7. package/SDK_LOGGING.md +222 -0
  8. package/SDK_MIGRATION.md +474 -0
  9. package/SDK_README.md +1122 -0
  10. package/{testdriver → _testdriver}/acceptance/drag-and-drop.yaml +2 -2
  11. package/{testdriver → _testdriver}/acceptance/snippets/login.yaml +1 -1
  12. package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
  13. package/{testdriver → _testdriver}/examples/web/lifecycle/prerun.yaml +6 -1
  14. package/{testdriver → _testdriver}/lifecycle/postrun.yaml +3 -2
  15. package/_testdriver/lifecycle/prerun.yaml +15 -0
  16. package/{testdriver → _testdriver}/lifecycle/provision.yaml +7 -2
  17. package/agent/index.js +258 -68
  18. package/agent/interface.js +15 -0
  19. package/agent/lib/cache.js +142 -0
  20. package/agent/lib/commander.js +1 -39
  21. package/agent/lib/commands.js +143 -188
  22. package/agent/lib/redraw.js +6 -3
  23. package/agent/lib/sandbox.js +19 -5
  24. package/agent/lib/sdk.js +1 -0
  25. package/agent/lib/system.js +0 -3
  26. package/agent/lib/validation.js +1 -7
  27. package/debug-locate-response.js +82 -0
  28. package/debug-screenshot-1763401388589.png +0 -0
  29. package/debugger/index.html +15 -4
  30. package/docs/ARCHITECTURE.md +424 -0
  31. package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
  32. package/docs/QUICK_START_TEST_RECORDING.md +215 -0
  33. package/docs/SDK_AWESOME_LOGS.md +468 -0
  34. package/docs/TEST_RECORDING.md +388 -0
  35. package/docs/docs.json +232 -152
  36. package/docs/sdk-browser-rendering.md +167 -0
  37. package/docs/v6/getting-started/self-hosting.mdx +407 -0
  38. package/docs/{guide → v6/guide}/dashcam.mdx +1 -1
  39. package/docs/{guide → v6/guide}/environment-variables.mdx +4 -5
  40. package/docs/{guide → v6/guide}/lifecycle.mdx +1 -1
  41. package/docs/v6/overview/comparison.mdx +101 -0
  42. package/docs/v7/README.md +135 -0
  43. package/docs/v7/api/ai.mdx +205 -0
  44. package/docs/v7/api/assert.mdx +285 -0
  45. package/docs/v7/api/assertions.mdx +403 -0
  46. package/docs/v7/api/click.mdx +287 -0
  47. package/docs/v7/api/client.mdx +322 -0
  48. package/docs/v7/api/elements.mdx +479 -0
  49. package/docs/v7/api/exec.mdx +346 -0
  50. package/docs/v7/api/find.mdx +316 -0
  51. package/docs/v7/api/focusApplication.mdx +294 -0
  52. package/docs/v7/api/hover.mdx +279 -0
  53. package/docs/v7/api/pressKeys.mdx +349 -0
  54. package/docs/v7/api/sandbox.mdx +404 -0
  55. package/docs/v7/api/scroll.mdx +300 -0
  56. package/docs/v7/api/type.mdx +314 -0
  57. package/docs/v7/commands/assert.mdx +45 -0
  58. package/docs/v7/commands/exec.mdx +282 -0
  59. package/docs/v7/commands/focus-application.mdx +44 -0
  60. package/docs/v7/commands/hover-image.mdx +69 -0
  61. package/docs/v7/commands/hover-text.mdx +47 -0
  62. package/docs/v7/commands/if.mdx +53 -0
  63. package/docs/v7/commands/match-image.mdx +67 -0
  64. package/docs/v7/commands/press-keys.mdx +87 -0
  65. package/docs/v7/commands/remember.mdx +49 -0
  66. package/docs/v7/commands/run.mdx +44 -0
  67. package/docs/v7/commands/scroll-until-image.mdx +66 -0
  68. package/docs/v7/commands/scroll-until-text.mdx +60 -0
  69. package/docs/v7/commands/scroll.mdx +69 -0
  70. package/docs/v7/commands/type.mdx +45 -0
  71. package/docs/v7/commands/wait-for-image.mdx +54 -0
  72. package/docs/v7/commands/wait-for-text.mdx +48 -0
  73. package/docs/v7/commands/wait.mdx +45 -0
  74. package/docs/v7/getting-started/quickstart.mdx +199 -0
  75. package/docs/v7/guides/migration.mdx +562 -0
  76. package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
  77. package/docs/v7/playwright.mdx +342 -0
  78. package/eslint.config.js +19 -1
  79. package/examples/run-tests-with-recording.sh +70 -0
  80. package/examples/screenshot-example.js +63 -0
  81. package/examples/sdk-awesome-logs-demo.js +177 -0
  82. package/examples/sdk-cache-thresholds.js +96 -0
  83. package/examples/sdk-element-properties.js +155 -0
  84. package/examples/sdk-simple-example.js +65 -0
  85. package/examples/test-recording-example.test.js +166 -0
  86. package/interfaces/cli/lib/base.js +10 -4
  87. package/interfaces/logger.js +2 -1
  88. package/interfaces/shared-test-state.mjs +69 -0
  89. package/interfaces/vitest-plugin.mjs +744 -0
  90. package/mcp-server/AI_GUIDELINES.md +57 -0
  91. package/package.json +18 -5
  92. package/schema.json +8 -29
  93. package/scripts/view-test-results.mjs +96 -0
  94. package/sdk-log-formatter.js +714 -0
  95. package/sdk.d.ts +735 -0
  96. package/sdk.js +1906 -0
  97. package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
  98. package/setup/aws/cloudformation.yaml +9 -2
  99. package/test/mcp-example-test.yaml +27 -0
  100. package/test-find-api.js +73 -0
  101. package/test-prompt-cache.js +96 -0
  102. package/test-sandbox-render.js +28 -0
  103. package/test-sdk-methods.js +15 -0
  104. package/test-sdk-refactor.js +53 -0
  105. package/test-stack-trace.mjs +57 -0
  106. package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +61 -0
  107. package/testdriver/acceptance-sdk/README.md +128 -0
  108. package/testdriver/acceptance-sdk/TEST_REPORTING.md +245 -0
  109. package/testdriver/acceptance-sdk/assert.test.mjs +44 -0
  110. package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +70 -0
  111. package/testdriver/acceptance-sdk/element-not-found.test.mjs +38 -0
  112. package/testdriver/acceptance-sdk/exec-js.test.mjs +55 -0
  113. package/testdriver/acceptance-sdk/exec-output.test.mjs +71 -0
  114. package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +69 -0
  115. package/testdriver/acceptance-sdk/focus-window.test.mjs +48 -0
  116. package/testdriver/acceptance-sdk/formatted-logging.test.mjs +41 -0
  117. package/testdriver/acceptance-sdk/hover-image.test.mjs +43 -0
  118. package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +50 -0
  119. package/testdriver/acceptance-sdk/hover-text.test.mjs +41 -0
  120. package/testdriver/acceptance-sdk/match-image.test.mjs +48 -0
  121. package/testdriver/acceptance-sdk/press-keys.test.mjs +64 -0
  122. package/testdriver/acceptance-sdk/prompt.test.mjs +45 -0
  123. package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +52 -0
  124. package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +51 -0
  125. package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +42 -0
  126. package/testdriver/acceptance-sdk/scroll.test.mjs +50 -0
  127. package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
  128. package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +239 -0
  129. package/testdriver/acceptance-sdk/setup/testHelpers.mjs +648 -0
  130. package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
  131. package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
  132. package/testdriver/acceptance-sdk/type.test.mjs +84 -0
  133. package/verify-element-api.js +89 -0
  134. package/verify-types.js +0 -0
  135. package/vitest.config.example.js +19 -0
  136. package/vitest.config.mjs +65 -0
  137. package/vitest.config.mjs.bak +44 -0
  138. package/.github/workflows/acceptance-v6.yml +0 -169
  139. package/docs/overview/comparison.mdx +0 -82
  140. package/testdriver/lifecycle/prerun.yaml +0 -17
  141. /package/{testdriver/examples/desktop/lifecycle/prerun.yaml → .env.example} +0 -0
  142. /package/{testdriver → _testdriver}/acceptance/assert.yaml +0 -0
  143. /package/{testdriver → _testdriver}/acceptance/dashcam.yaml +0 -0
  144. /package/{testdriver → _testdriver}/acceptance/embed.yaml +0 -0
  145. /package/{testdriver → _testdriver}/acceptance/exec-js.yaml +0 -0
  146. /package/{testdriver → _testdriver}/acceptance/exec-output.yaml +0 -0
  147. /package/{testdriver → _testdriver}/acceptance/exec-shell.yaml +0 -0
  148. /package/{testdriver → _testdriver}/acceptance/focus-window.yaml +0 -0
  149. /package/{testdriver → _testdriver}/acceptance/hover-image.yaml +0 -0
  150. /package/{testdriver → _testdriver}/acceptance/hover-text-with-description.yaml +0 -0
  151. /package/{testdriver → _testdriver}/acceptance/hover-text.yaml +0 -0
  152. /package/{testdriver → _testdriver}/acceptance/if-else.yaml +0 -0
  153. /package/{testdriver → _testdriver}/acceptance/match-image.yaml +0 -0
  154. /package/{testdriver → _testdriver}/acceptance/press-keys.yaml +0 -0
  155. /package/{testdriver → _testdriver}/acceptance/prompt.yaml +0 -0
  156. /package/{testdriver → _testdriver}/acceptance/remember.yaml +0 -0
  157. /package/{testdriver → _testdriver}/acceptance/screenshots/cart.png +0 -0
  158. /package/{testdriver → _testdriver}/acceptance/scroll-keyboard.yaml +0 -0
  159. /package/{testdriver → _testdriver}/acceptance/scroll-until-image.yaml +0 -0
  160. /package/{testdriver → _testdriver}/acceptance/scroll-until-text.yaml +0 -0
  161. /package/{testdriver → _testdriver}/acceptance/scroll.yaml +0 -0
  162. /package/{testdriver → _testdriver}/acceptance/snippets/match-cart.yaml +0 -0
  163. /package/{testdriver → _testdriver}/acceptance/type.yaml +0 -0
  164. /package/{testdriver → _testdriver}/behavior/failure.yaml +0 -0
  165. /package/{testdriver → _testdriver}/behavior/hover-text.yaml +0 -0
  166. /package/{testdriver → _testdriver}/behavior/lifecycle/postrun.yaml +0 -0
  167. /package/{testdriver → _testdriver}/behavior/lifecycle/prerun.yaml +0 -0
  168. /package/{testdriver → _testdriver}/behavior/lifecycle/provision.yaml +0 -0
  169. /package/{testdriver → _testdriver}/behavior/secrets.yaml +0 -0
  170. /package/{testdriver → _testdriver}/edge-cases/dashcam-chrome.yaml +0 -0
  171. /package/{testdriver → _testdriver}/edge-cases/exec-pwsh-multiline.yaml +0 -0
  172. /package/{testdriver → _testdriver}/edge-cases/js-exception.yaml +0 -0
  173. /package/{testdriver → _testdriver}/edge-cases/js-promise.yaml +0 -0
  174. /package/{testdriver → _testdriver}/edge-cases/lifecycle/postrun.yaml +0 -0
  175. /package/{testdriver → _testdriver}/edge-cases/prompt-in-middle.yaml +0 -0
  176. /package/{testdriver → _testdriver}/edge-cases/prompt-nested.yaml +0 -0
  177. /package/{testdriver → _testdriver}/edge-cases/success-test.yaml +0 -0
  178. /package/{testdriver → _testdriver}/examples/android/example.yaml +0 -0
  179. /package/{testdriver → _testdriver}/examples/android/lifecycle/postrun.yaml +0 -0
  180. /package/{testdriver → _testdriver}/examples/android/lifecycle/provision.yaml +0 -0
  181. /package/{testdriver → _testdriver}/examples/android/readme.md +0 -0
  182. /package/{testdriver → _testdriver}/examples/chrome-extension/lifecycle/provision.yaml +0 -0
  183. /package/{testdriver → _testdriver}/examples/desktop/lifecycle/provision.yaml +0 -0
  184. /package/{testdriver → _testdriver}/examples/vscode-extension/lifecycle/provision.yaml +0 -0
  185. /package/{testdriver → _testdriver}/examples/web/lifecycle/postrun.yaml +0 -0
  186. /package/docs/{account → v6/account}/dashboard.mdx +0 -0
  187. /package/docs/{account → v6/account}/enterprise.mdx +0 -0
  188. /package/docs/{account → v6/account}/pricing.mdx +0 -0
  189. /package/docs/{account → v6/account}/projects.mdx +0 -0
  190. /package/docs/{account → v6/account}/team.mdx +0 -0
  191. /package/docs/{action → v6/action}/ami.mdx +0 -0
  192. /package/docs/{action → v6/action}/performance.mdx +0 -0
  193. /package/docs/{action → v6/action}/secrets.mdx +0 -0
  194. /package/docs/{apps → v6/apps}/chrome-extensions.mdx +0 -0
  195. /package/docs/{apps → v6/apps}/desktop-apps.mdx +0 -0
  196. /package/docs/{apps → v6/apps}/mobile-apps.mdx +0 -0
  197. /package/docs/{apps → v6/apps}/static-websites.mdx +0 -0
  198. /package/docs/{apps → v6/apps}/tauri-apps.mdx +0 -0
  199. /package/docs/{bugs → v6/bugs}/jira.mdx +0 -0
  200. /package/docs/{cli → v6/cli}/overview.mdx +0 -0
  201. /package/docs/{commands → v6/commands}/assert.mdx +0 -0
  202. /package/docs/{commands → v6/commands}/exec.mdx +0 -0
  203. /package/docs/{commands → v6/commands}/focus-application.mdx +0 -0
  204. /package/docs/{commands → v6/commands}/hover-image.mdx +0 -0
  205. /package/docs/{commands → v6/commands}/hover-text.mdx +0 -0
  206. /package/docs/{commands → v6/commands}/if.mdx +0 -0
  207. /package/docs/{commands → v6/commands}/match-image.mdx +0 -0
  208. /package/docs/{commands → v6/commands}/press-keys.mdx +0 -0
  209. /package/docs/{commands → v6/commands}/remember.mdx +0 -0
  210. /package/docs/{commands → v6/commands}/run.mdx +0 -0
  211. /package/docs/{commands → v6/commands}/scroll-until-image.mdx +0 -0
  212. /package/docs/{commands → v6/commands}/scroll-until-text.mdx +0 -0
  213. /package/docs/{commands → v6/commands}/scroll.mdx +0 -0
  214. /package/docs/{commands → v6/commands}/type.mdx +0 -0
  215. /package/docs/{commands → v6/commands}/wait-for-image.mdx +0 -0
  216. /package/docs/{commands → v6/commands}/wait-for-text.mdx +0 -0
  217. /package/docs/{commands → v6/commands}/wait.mdx +0 -0
  218. /package/docs/{exporting → v6/exporting}/junit.mdx +0 -0
  219. /package/docs/{exporting → v6/exporting}/playwright.mdx +0 -0
  220. /package/docs/{features → v6/features}/auto-healing.mdx +0 -0
  221. /package/docs/{features → v6/features}/generation.mdx +0 -0
  222. /package/docs/{features → v6/features}/parallel-testing.mdx +0 -0
  223. /package/docs/{features → v6/features}/reusable-snippets.mdx +0 -0
  224. /package/docs/{features → v6/features}/selectorless.mdx +0 -0
  225. /package/docs/{features → v6/features}/visual-assertions.mdx +0 -0
  226. /package/docs/{getting-started → v6/getting-started}/ci.mdx +0 -0
  227. /package/docs/{getting-started → v6/getting-started}/cli.mdx +0 -0
  228. /package/docs/{getting-started → v6/getting-started}/editing.mdx +0 -0
  229. /package/docs/{getting-started → v6/getting-started}/playwright.mdx +0 -0
  230. /package/docs/{getting-started → v6/getting-started}/running.mdx +0 -0
  231. /package/docs/{getting-started → v6/getting-started}/vscode.mdx +0 -0
  232. /package/docs/{guide → v6/guide}/assertions.mdx +0 -0
  233. /package/docs/{guide → v6/guide}/authentication.mdx +0 -0
  234. /package/docs/{guide → v6/guide}/code.mdx +0 -0
  235. /package/docs/{guide → v6/guide}/locating.mdx +0 -0
  236. /package/docs/{guide → v6/guide}/protips.mdx +0 -0
  237. /package/docs/{guide → v6/guide}/variables.mdx +0 -0
  238. /package/docs/{guide → v6/guide}/waiting.mdx +0 -0
  239. /package/docs/{importing → v6/importing}/csv.mdx +0 -0
  240. /package/docs/{importing → v6/importing}/gherkin.mdx +0 -0
  241. /package/docs/{importing → v6/importing}/jira.mdx +0 -0
  242. /package/docs/{importing → v6/importing}/testrail.mdx +0 -0
  243. /package/docs/{integrations → v6/integrations}/electron.mdx +0 -0
  244. /package/docs/{integrations → v6/integrations}/netlify.mdx +0 -0
  245. /package/docs/{integrations → v6/integrations}/vercel.mdx +0 -0
  246. /package/docs/{interactive → v6/interactive}/explore.mdx +0 -0
  247. /package/docs/{interactive → v6/interactive}/run.mdx +0 -0
  248. /package/docs/{interactive → v6/interactive}/save.mdx +0 -0
  249. /package/docs/{overview → v6/overview}/faq.mdx +0 -0
  250. /package/docs/{overview → v6/overview}/performance.mdx +0 -0
  251. /package/docs/{overview → v6/overview}/quickstart.mdx +0 -0
  252. /package/docs/{overview → v6/overview}/what-is-testdriver.mdx +0 -0
  253. /package/docs/{scenarios → v6/scenarios}/ai-chatbot.mdx +0 -0
  254. /package/docs/{scenarios → v6/scenarios}/cookie-banner.mdx +0 -0
  255. /package/docs/{scenarios → v6/scenarios}/file-upload.mdx +0 -0
  256. /package/docs/{scenarios → v6/scenarios}/form-filling.mdx +0 -0
  257. /package/docs/{scenarios → v6/scenarios}/log-in.mdx +0 -0
  258. /package/docs/{scenarios → v6/scenarios}/pdf-generation.mdx +0 -0
  259. /package/docs/{scenarios → v6/scenarios}/spell-check.mdx +0 -0
  260. /package/docs/{security → v6/security}/action.mdx +0 -0
  261. /package/docs/{security → v6/security}/agent.mdx +0 -0
  262. /package/docs/{security → v6/security}/platform.mdx +0 -0
  263. /package/docs/{tutorials → v6/tutorials}/advanced-test.mdx +0 -0
  264. /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
+ };