@superblocksteam/vite-plugin-file-sync 2.0.72-next.0 → 2.0.72-next.3

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 (218) hide show
  1. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +2 -1
  2. package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
  3. package/dist/ai-service/agent/prompts/build-base-system-prompt.js +18 -2
  4. package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
  5. package/dist/ai-service/agent/subagents/testing/index.d.ts +3 -0
  6. package/dist/ai-service/agent/subagents/testing/index.d.ts.map +1 -0
  7. package/dist/ai-service/agent/subagents/testing/index.js +2 -0
  8. package/dist/ai-service/agent/subagents/testing/index.js.map +1 -0
  9. package/dist/ai-service/agent/subagents/testing/prompt-builder.d.ts +10 -0
  10. package/dist/ai-service/agent/subagents/testing/prompt-builder.d.ts.map +1 -0
  11. package/dist/ai-service/agent/subagents/testing/prompt-builder.js +67 -0
  12. package/dist/ai-service/agent/subagents/testing/prompt-builder.js.map +1 -0
  13. package/dist/ai-service/agent/subagents/testing/types.d.ts +47 -0
  14. package/dist/ai-service/agent/subagents/testing/types.d.ts.map +1 -0
  15. package/dist/ai-service/agent/subagents/testing/types.js +2 -0
  16. package/dist/ai-service/agent/subagents/testing/types.js.map +1 -0
  17. package/dist/ai-service/agent/subagents/types.d.ts +9 -8
  18. package/dist/ai-service/agent/subagents/types.d.ts.map +1 -1
  19. package/dist/ai-service/agent/subagents/types.js +9 -9
  20. package/dist/ai-service/agent/subagents/types.js.map +1 -1
  21. package/dist/ai-service/agent/tool-message-utils.d.ts +7 -2
  22. package/dist/ai-service/agent/tool-message-utils.d.ts.map +1 -1
  23. package/dist/ai-service/agent/tool-message-utils.js +21 -2
  24. package/dist/ai-service/agent/tool-message-utils.js.map +1 -1
  25. package/dist/ai-service/agent/tools/apis/test-api.js +1 -1
  26. package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
  27. package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts +1 -0
  28. package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
  29. package/dist/ai-service/agent/tools/build-capture-screenshot.js +4 -2
  30. package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
  31. package/dist/ai-service/agent/tools/build-delete-file.d.ts +1 -1
  32. package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +1 -1
  33. package/dist/ai-service/agent/tools/{build-read-files.d.ts → build-read-file.d.ts} +10 -6
  34. package/dist/ai-service/agent/tools/build-read-file.d.ts.map +1 -0
  35. package/dist/ai-service/agent/tools/build-read-file.js +139 -0
  36. package/dist/ai-service/agent/tools/build-read-file.js.map +1 -0
  37. package/dist/ai-service/agent/tools/build-reload-file.d.ts +4 -1
  38. package/dist/ai-service/agent/tools/build-reload-file.d.ts.map +1 -1
  39. package/dist/ai-service/agent/tools/build-reload-file.js +18 -8
  40. package/dist/ai-service/agent/tools/build-reload-file.js.map +1 -1
  41. package/dist/ai-service/agent/tools/get-console-logs.js +1 -1
  42. package/dist/ai-service/agent/tools/get-console-logs.js.map +1 -1
  43. package/dist/ai-service/agent/tools/get-runtime-errors.js +1 -1
  44. package/dist/ai-service/agent/tools/get-runtime-errors.js.map +1 -1
  45. package/dist/ai-service/agent/tools/index.d.ts +4 -1
  46. package/dist/ai-service/agent/tools/index.d.ts.map +1 -1
  47. package/dist/ai-service/agent/tools/index.js +4 -1
  48. package/dist/ai-service/agent/tools/index.js.map +1 -1
  49. package/dist/ai-service/agent/tools.d.ts.map +1 -1
  50. package/dist/ai-service/agent/tools.js +89 -29
  51. package/dist/ai-service/agent/tools.js.map +1 -1
  52. package/dist/ai-service/agent/tools2/access-control.d.ts +23 -1
  53. package/dist/ai-service/agent/tools2/access-control.d.ts.map +1 -1
  54. package/dist/ai-service/agent/tools2/access-control.js +67 -1
  55. package/dist/ai-service/agent/tools2/access-control.js.map +1 -1
  56. package/dist/ai-service/agent/tools2/entity-permissions.d.ts +26 -0
  57. package/dist/ai-service/agent/tools2/entity-permissions.d.ts.map +1 -1
  58. package/dist/ai-service/agent/tools2/entity-permissions.js +15 -0
  59. package/dist/ai-service/agent/tools2/entity-permissions.js.map +1 -1
  60. package/dist/ai-service/agent/tools2/example.d.ts.map +1 -1
  61. package/dist/ai-service/agent/tools2/example.js +2 -4
  62. package/dist/ai-service/agent/tools2/example.js.map +1 -1
  63. package/dist/ai-service/agent/tools2/index.d.ts +1 -1
  64. package/dist/ai-service/agent/tools2/index.d.ts.map +1 -1
  65. package/dist/ai-service/agent/tools2/index.js +1 -1
  66. package/dist/ai-service/agent/tools2/index.js.map +1 -1
  67. package/dist/ai-service/agent/tools2/registry.d.ts.map +1 -1
  68. package/dist/ai-service/agent/tools2/registry.js +37 -23
  69. package/dist/ai-service/agent/tools2/registry.js.map +1 -1
  70. package/dist/ai-service/agent/tools2/tools/end-test-run.d.ts +31 -0
  71. package/dist/ai-service/agent/tools2/tools/end-test-run.d.ts.map +1 -0
  72. package/dist/ai-service/agent/tools2/tools/end-test-run.js +105 -0
  73. package/dist/ai-service/agent/tools2/tools/end-test-run.js.map +1 -0
  74. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts +1 -0
  75. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
  76. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +135 -11
  77. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
  78. package/dist/ai-service/agent/tools2/tools/start-test-run.d.ts +24 -0
  79. package/dist/ai-service/agent/tools2/tools/start-test-run.d.ts.map +1 -0
  80. package/dist/ai-service/agent/tools2/tools/start-test-run.js +258 -0
  81. package/dist/ai-service/agent/tools2/tools/start-test-run.js.map +1 -0
  82. package/dist/ai-service/agent/tools2/tools/update-test-case-status.d.ts +29 -0
  83. package/dist/ai-service/agent/tools2/tools/update-test-case-status.d.ts.map +1 -0
  84. package/dist/ai-service/agent/tools2/tools/update-test-case-status.js +87 -0
  85. package/dist/ai-service/agent/tools2/tools/update-test-case-status.js.map +1 -0
  86. package/dist/ai-service/agent/tools2/types.d.ts +6 -24
  87. package/dist/ai-service/agent/tools2/types.d.ts.map +1 -1
  88. package/dist/ai-service/agent/tools2/types.js +4 -15
  89. package/dist/ai-service/agent/tools2/types.js.map +1 -1
  90. package/dist/ai-service/agent/utils.d.ts +10 -0
  91. package/dist/ai-service/agent/utils.d.ts.map +1 -1
  92. package/dist/ai-service/agent/utils.js +115 -1
  93. package/dist/ai-service/agent/utils.js.map +1 -1
  94. package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
  95. package/dist/ai-service/chat/chat-session-store.js +122 -1
  96. package/dist/ai-service/chat/chat-session-store.js.map +1 -1
  97. package/dist/ai-service/features.d.ts +4 -0
  98. package/dist/ai-service/features.d.ts.map +1 -1
  99. package/dist/ai-service/features.js +4 -0
  100. package/dist/ai-service/features.js.map +1 -1
  101. package/dist/ai-service/index.d.ts.map +1 -1
  102. package/dist/ai-service/index.js +14 -8
  103. package/dist/ai-service/index.js.map +1 -1
  104. package/dist/ai-service/judge/integration/mcp-client.d.ts +3 -6
  105. package/dist/ai-service/judge/integration/mcp-client.d.ts.map +1 -1
  106. package/dist/ai-service/judge/integration/mcp-client.js.map +1 -1
  107. package/dist/ai-service/judge/tools/playwright-action.d.ts +1 -1
  108. package/dist/ai-service/judge/tools/submit-feedback.d.ts +1 -1
  109. package/dist/ai-service/llm/client.d.ts +6 -0
  110. package/dist/ai-service/llm/client.d.ts.map +1 -1
  111. package/dist/ai-service/llm/client.js +9 -0
  112. package/dist/ai-service/llm/client.js.map +1 -1
  113. package/dist/ai-service/llm/context/constants.d.ts +8 -0
  114. package/dist/ai-service/llm/context/constants.d.ts.map +1 -1
  115. package/dist/ai-service/llm/context/constants.js +8 -0
  116. package/dist/ai-service/llm/context/constants.js.map +1 -1
  117. package/dist/ai-service/llm/context/context.d.ts.map +1 -1
  118. package/dist/ai-service/llm/context/context.js +12 -9
  119. package/dist/ai-service/llm/context/context.js.map +1 -1
  120. package/dist/ai-service/llm/context/manager.d.ts +1 -1
  121. package/dist/ai-service/llm/context/manager.js +1 -1
  122. package/dist/ai-service/llm/context/utils/message-utils.d.ts +10 -0
  123. package/dist/ai-service/llm/context/utils/message-utils.d.ts.map +1 -1
  124. package/dist/ai-service/llm/context/utils/message-utils.js +92 -0
  125. package/dist/ai-service/llm/context/utils/message-utils.js.map +1 -1
  126. package/dist/ai-service/llm/interaction/provider.d.ts +1 -0
  127. package/dist/ai-service/llm/interaction/provider.d.ts.map +1 -1
  128. package/dist/ai-service/llm/stream/event-bus.d.ts +5 -0
  129. package/dist/ai-service/llm/stream/event-bus.d.ts.map +1 -1
  130. package/dist/ai-service/llm/stream/event-bus.js.map +1 -1
  131. package/dist/ai-service/llm/stream/observers/llmobs.d.ts +4 -1
  132. package/dist/ai-service/llm/stream/observers/llmobs.d.ts.map +1 -1
  133. package/dist/ai-service/llm/stream/observers/llmobs.js +194 -10
  134. package/dist/ai-service/llm/stream/observers/llmobs.js.map +1 -1
  135. package/dist/ai-service/llm/stream/observers/logging.d.ts +1 -0
  136. package/dist/ai-service/llm/stream/observers/logging.d.ts.map +1 -1
  137. package/dist/ai-service/llm/stream/observers/logging.js +92 -20
  138. package/dist/ai-service/llm/stream/observers/logging.js.map +1 -1
  139. package/dist/ai-service/llm/stream/orchestrator.d.ts +7 -1
  140. package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
  141. package/dist/ai-service/llm/stream/orchestrator.js +24 -4
  142. package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
  143. package/dist/ai-service/llm/stream/session.d.ts +12 -2
  144. package/dist/ai-service/llm/stream/session.d.ts.map +1 -1
  145. package/dist/ai-service/llm/stream/session.js +9 -26
  146. package/dist/ai-service/llm/stream/session.js.map +1 -1
  147. package/dist/ai-service/llmobs/tracer.d.ts +0 -5
  148. package/dist/ai-service/llmobs/tracer.d.ts.map +1 -1
  149. package/dist/ai-service/llmobs/tracer.js +1 -40
  150. package/dist/ai-service/llmobs/tracer.js.map +1 -1
  151. package/dist/ai-service/llmobs/types.d.ts +1 -0
  152. package/dist/ai-service/llmobs/types.d.ts.map +1 -1
  153. package/dist/ai-service/mcp/adapter/mcp-tool-adapter.d.ts +1 -1
  154. package/dist/ai-service/mcp/adapter/mcp-tool-adapter.d.ts.map +1 -1
  155. package/dist/ai-service/mcp/adapter/mcp-tool-adapter.js +5 -2
  156. package/dist/ai-service/mcp/adapter/mcp-tool-adapter.js.map +1 -1
  157. package/dist/ai-service/mcp/embedded-playwright-mcp-server.d.ts +16 -1
  158. package/dist/ai-service/mcp/embedded-playwright-mcp-server.d.ts.map +1 -1
  159. package/dist/ai-service/mcp/embedded-playwright-mcp-server.js +625 -89
  160. package/dist/ai-service/mcp/embedded-playwright-mcp-server.js.map +1 -1
  161. package/dist/ai-service/mcp/playwright-server.d.ts +10 -0
  162. package/dist/ai-service/mcp/playwright-server.d.ts.map +1 -1
  163. package/dist/ai-service/mcp/playwright-server.js +3 -0
  164. package/dist/ai-service/mcp/playwright-server.js.map +1 -1
  165. package/dist/ai-service/mcp/types.d.ts +4 -0
  166. package/dist/ai-service/mcp/types.d.ts.map +1 -1
  167. package/dist/ai-service/prompts/explain-code.d.ts +2 -2
  168. package/dist/ai-service/prompts/explain-code.d.ts.map +1 -1
  169. package/dist/ai-service/prompts/explain-code.js +2 -2
  170. package/dist/ai-service/prompts/explain-code.js.map +1 -1
  171. package/dist/ai-service/state-machine/clark-fsm.d.ts +18 -1
  172. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  173. package/dist/ai-service/state-machine/clark-fsm.js +15 -0
  174. package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
  175. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  176. package/dist/ai-service/state-machine/handlers/agent-planning.js +36 -6
  177. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  178. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  179. package/dist/ai-service/state-machine/handlers/llm-generating.js +87 -34
  180. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  181. package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
  182. package/dist/ai-service/state-machine/mocks.js +2 -0
  183. package/dist/ai-service/state-machine/mocks.js.map +1 -1
  184. package/dist/ai-service/state-machine/traced-fsm.d.ts +2 -0
  185. package/dist/ai-service/state-machine/traced-fsm.d.ts.map +1 -1
  186. package/dist/ai-service/state-machine/traced-fsm.js +18 -0
  187. package/dist/ai-service/state-machine/traced-fsm.js.map +1 -1
  188. package/dist/ai-service/util/safe-parse.d.ts +2 -0
  189. package/dist/ai-service/util/safe-parse.d.ts.map +1 -0
  190. package/dist/ai-service/util/safe-parse.js +9 -0
  191. package/dist/ai-service/util/safe-parse.js.map +1 -0
  192. package/dist/ai-service/util/safe-stringify.d.ts.map +1 -1
  193. package/dist/ai-service/util/safe-stringify.js +7 -0
  194. package/dist/ai-service/util/safe-stringify.js.map +1 -1
  195. package/dist/ai-service/util/stop-condition.d.ts +1 -0
  196. package/dist/ai-service/util/stop-condition.d.ts.map +1 -1
  197. package/dist/ai-service/util/stop-condition.js +5 -0
  198. package/dist/ai-service/util/stop-condition.js.map +1 -1
  199. package/dist/ai-service/util/strip-content.d.ts +2 -0
  200. package/dist/ai-service/util/strip-content.d.ts.map +1 -0
  201. package/dist/ai-service/util/strip-content.js +31 -0
  202. package/dist/ai-service/util/strip-content.js.map +1 -0
  203. package/dist/ai-service/util/tool-signature.d.ts +13 -0
  204. package/dist/ai-service/util/tool-signature.d.ts.map +1 -0
  205. package/dist/ai-service/util/tool-signature.js +38 -0
  206. package/dist/ai-service/util/tool-signature.js.map +1 -0
  207. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  208. package/dist/file-sync-vite-plugin.js +3 -0
  209. package/dist/file-sync-vite-plugin.js.map +1 -1
  210. package/dist/injected-index.js +2 -2
  211. package/dist/injected-index.js.map +1 -1
  212. package/dist/parsing/jsx.d.ts.map +1 -1
  213. package/dist/parsing/jsx.js +0 -2
  214. package/dist/parsing/jsx.js.map +1 -1
  215. package/package.json +9 -9
  216. package/dist/ai-service/agent/tools/build-read-files.d.ts.map +0 -1
  217. package/dist/ai-service/agent/tools/build-read-files.js +0 -67
  218. package/dist/ai-service/agent/tools/build-read-files.js.map +0 -1
@@ -2,6 +2,8 @@ import { once } from "node:events";
2
2
  import fs from "node:fs";
3
3
  import http from "node:http";
4
4
  import { chromium, firefox, webkit, } from "playwright";
5
+ /** Default viewport dimensions for consistent screenshots */
6
+ const DEFAULT_VIEWPORT = { width: 1280, height: 800 };
5
7
  const PLAYWRIGHT_ACTIONS = [
6
8
  "navigate",
7
9
  "click",
@@ -12,36 +14,37 @@ const PLAYWRIGHT_ACTIONS = [
12
14
  "evaluate",
13
15
  "getUrl",
14
16
  "reload",
17
+ "getConsoleLogs",
18
+ "scroll",
19
+ "scrollIntoView",
15
20
  ];
21
+ let capturedConsoleLogs = [];
16
22
  export async function startEmbeddedPlaywrightMcpServer(options) {
23
+ const logger = options.logger;
17
24
  let browser;
18
25
  let context = null;
19
26
  let shouldCloseBrowser = true;
20
27
  if (options?.connectWsEndpoint) {
21
- console.log(`🎬 MCP Server: Connecting to existing browser at ${options.connectWsEndpoint}`);
22
- browser = await chromium.connect({ wsEndpoint: options.connectWsEndpoint });
28
+ browser = await chromium.connect({
29
+ wsEndpoint: options.connectWsEndpoint,
30
+ });
23
31
  shouldCloseBrowser = false;
24
32
  }
25
33
  else {
26
- console.log("🎬 MCP Server: Launching new browser with storage state");
27
34
  browser = await launchBrowser();
28
35
  }
29
- console.log(`🎬 MCP Server: Browser contexts available before selection: ${browser.contexts().length}`);
30
36
  // Reuse an existing context when connecting to an existing browser
31
37
  if (options?.connectWsEndpoint) {
32
38
  const existing = browser.contexts();
33
39
  if (existing.length > 0) {
34
40
  context = existing[0];
35
- console.log("🎬 MCP Server: Reusing existing browser context");
36
- }
37
- else {
38
- console.warn("🎬 MCP Server: No existing context found on shared browser; falling back to new context with storage/JWT");
39
41
  }
40
42
  }
41
43
  if (!context) {
42
- const contextOptions = {};
44
+ const contextOptions = {
45
+ viewport: DEFAULT_VIEWPORT,
46
+ };
43
47
  if (options?.storageStateData) {
44
- console.log("🎬 MCP Server: Using provided storageStateData");
45
48
  contextOptions.storageState = options.storageStateData;
46
49
  // Duplicate auth cookies onto app host if needed (e.g., when original domain is auth0)
47
50
  if (options.appUrl && options.storageStateData.cookies?.length) {
@@ -62,12 +65,8 @@ export async function startEmbeddedPlaywrightMcpServer(options) {
62
65
  }
63
66
  else if (options?.storageStatePath) {
64
67
  if (fs.existsSync(options.storageStatePath)) {
65
- console.log(`🎬 MCP Server: Loading storage state from ${options.storageStatePath}`);
66
68
  contextOptions.storageState = options.storageStatePath;
67
69
  }
68
- else {
69
- console.warn(`🎬 MCP Server: Storage state not found at ${options.storageStatePath}, starting without it`);
70
- }
71
70
  }
72
71
  context = await browser.newContext(contextOptions);
73
72
  }
@@ -75,6 +74,154 @@ export async function startEmbeddedPlaywrightMcpServer(options) {
75
74
  if (!context) {
76
75
  throw new Error("Failed to create or reuse a Playwright context");
77
76
  }
77
+ // Inject sb-init and sb-bootstrap-response data for direct iframe access
78
+ // This simulates the parent window sending these messages to the iframe
79
+ if (options?.initData || options?.bootstrapData) {
80
+ const initData = options.initData;
81
+ const bootstrapData = options.bootstrapData;
82
+ /**
83
+ * CRITICAL: Mock Parent Window Setup for Playwright Browser Automation
84
+ *
85
+ * WHY THIS IS NECESSARY:
86
+ * The Superblocks library code (in @superblocksteam/library) expects to run inside an iframe
87
+ * embedded by the Superblocks editor. It uses `window.parent.postMessage()` to communicate
88
+ * with the editor and relies on `window.parent !== window` to detect the embedded state.
89
+ *
90
+ * When running in Playwright for E2E testing, there is no parent iframe - the page runs
91
+ * standalone. Without this mock, the library would:
92
+ * 1. Fail the `isEmbeddedBySuperblocksFirstParty()` check
93
+ * 2. Never receive the `sb-init` message needed to establish socket connections
94
+ * 3. Never receive the `sb-bootstrap-response` with auth tokens and app data
95
+ *
96
+ * HOW IT WORKS:
97
+ * 1. We override `window.parent` with a mock object BEFORE library code loads (via addInitScript)
98
+ * 2. The mock intercepts `postMessage` calls from the library
99
+ * 3. When the library sends "sb-ready", we respond with "sb-init" containing peerId/auth data
100
+ * 4. When the library sends "sb-editor-request-bootstrap", we respond with bootstrap data
101
+ * 5. We also send initial messages proactively in case the library sets up listeners late
102
+ *
103
+ * WHAT COULD BREAK THIS:
104
+ * - If the library changes how it detects parent window embedding (e.g., different checks)
105
+ * - If the message types or payload shapes change in the library
106
+ * - If the library adds additional security checks (e.g., origin verification on parent)
107
+ * - If timing changes require different setTimeout delays
108
+ *
109
+ * TESTING NOTE:
110
+ * Integration tests for this setup should verify:
111
+ * 1. The app successfully initializes and shows content
112
+ * 2. API calls are authenticated (tokens were passed correctly)
113
+ * 3. Real-time features work (socket connection established via peerId)
114
+ */
115
+ await context.addInitScript((payload) => {
116
+ const { initData: init, bootstrapData: bootstrap } = payload;
117
+ /**
118
+ * Mock parent window object that intercepts postMessage calls from the library.
119
+ * When the app sends messages expecting a parent response, we handle them here.
120
+ */
121
+ const mockParent = {
122
+ postMessage: (message, _targetOrigin) => {
123
+ // When the app sends sb-ready, we respond with sb-init
124
+ // This triggers the library to establish its socket connection
125
+ if (message?.type === "sb-ready" && init) {
126
+ setTimeout(() => {
127
+ window.postMessage({
128
+ type: "sb-init",
129
+ payload: {
130
+ peerId: init.peerId,
131
+ userId: init.userId,
132
+ devServerAuthorization: init.devServerAuthorization,
133
+ appId: init.appId,
134
+ windowOriginUrl: init.windowOriginUrl,
135
+ },
136
+ startTime: Date.now(),
137
+ }, "*");
138
+ }, 10);
139
+ }
140
+ // When the app sends sb-editor-request-bootstrap, we respond with bootstrap data
141
+ // This provides auth tokens and configuration needed by the API manager
142
+ if (message?.type === "sb-editor-request-bootstrap" && bootstrap) {
143
+ setTimeout(() => {
144
+ window.postMessage({
145
+ type: "sb-bootstrap-response",
146
+ payload: bootstrap,
147
+ startTime: Date.now(),
148
+ }, "*");
149
+ }, 10);
150
+ }
151
+ // When the app sends authenticate-api-request, we respond with resolve-promise
152
+ // This allows API calls to proceed in test mode without actual auth
153
+ if (message?.type === "authenticate-api-request") {
154
+ const { callbackId } = message.payload || {};
155
+ if (callbackId) {
156
+ setTimeout(() => {
157
+ window.postMessage({
158
+ type: "resolve-promise",
159
+ callbackId,
160
+ payload: {}, // Empty success result - no auth errors
161
+ }, "*");
162
+ }, 10);
163
+ }
164
+ }
165
+ },
166
+ // These properties make the mock look like a real window object
167
+ // which helps pass any instanceof or property existence checks
168
+ window: window,
169
+ document: document,
170
+ location: window.location,
171
+ };
172
+ // Override window.parent to point to our mock
173
+ // This must happen before the library code runs (hence addInitScript)
174
+ Object.defineProperty(window, "parent", {
175
+ value: mockParent,
176
+ writable: false,
177
+ configurable: true,
178
+ });
179
+ // Send initial messages proactively after a delay
180
+ // This handles the case where the library sets up listeners after checking window.parent
181
+ if (init) {
182
+ setTimeout(() => {
183
+ window.postMessage({
184
+ type: "sb-init",
185
+ payload: {
186
+ peerId: init.peerId,
187
+ userId: init.userId,
188
+ devServerAuthorization: init.devServerAuthorization,
189
+ appId: init.appId,
190
+ windowOriginUrl: init.windowOriginUrl,
191
+ },
192
+ startTime: Date.now(),
193
+ }, "*");
194
+ }, 100);
195
+ }
196
+ if (bootstrap) {
197
+ setTimeout(() => {
198
+ window.postMessage({
199
+ type: "sb-bootstrap-response",
200
+ payload: bootstrap,
201
+ startTime: Date.now(),
202
+ }, "*");
203
+ }, 200);
204
+ // Send sb-global-sync with profiles data for API execution
205
+ // This sets superblocksContext.profiles which is needed for profileId
206
+ if (bootstrap.profiles) {
207
+ setTimeout(() => {
208
+ window.postMessage({
209
+ type: "sb-global-sync",
210
+ payload: {
211
+ global: {
212
+ profiles: bootstrap.profiles,
213
+ },
214
+ },
215
+ startTime: Date.now(),
216
+ }, "*");
217
+ }, 300);
218
+ }
219
+ }
220
+ }, {
221
+ initData,
222
+ bootstrapData,
223
+ });
224
+ }
78
225
  // Seed a cookie/localStorage with JWT for app domain if provided
79
226
  if (options?.jwt && options?.appUrl) {
80
227
  try {
@@ -145,7 +292,13 @@ export async function startEmbeddedPlaywrightMcpServer(options) {
145
292
  await context.addCookies(cookies);
146
293
  }
147
294
  catch (error) {
148
- console.warn(`🎬 MCP Server: Failed to seed JWT cookie/localStorage: ${String(error)}`);
295
+ logger.error(`[MCP-Server] Failed to seed JWT cookie/localStorage: ${String(error)}`, {
296
+ error: {
297
+ kind: "McpServerSeedJwtError",
298
+ message: String(error),
299
+ stack: error instanceof Error ? error.stack : undefined,
300
+ },
301
+ });
149
302
  }
150
303
  }
151
304
  // Seed session storage for app origin if provided
@@ -165,8 +318,8 @@ export async function startEmbeddedPlaywrightMcpServer(options) {
165
318
  }
166
319
  }, { targetOrigin: origin, entries: items });
167
320
  }
168
- catch (error) {
169
- console.warn(`🎬 MCP Server: Failed to seed sessionStorage: ${String(error)}`);
321
+ catch {
322
+ // ignore session storage seeding errors
170
323
  }
171
324
  }
172
325
  // Seed additional origins storage (local/session) if provided
@@ -202,8 +355,8 @@ export async function startEmbeddedPlaywrightMcpServer(options) {
202
355
  ssEntries: seedSession,
203
356
  });
204
357
  }
205
- catch (error) {
206
- console.warn(`🎬 MCP Server: Failed to seed extra origin storage for ${origin}: ${String(error)}`);
358
+ catch {
359
+ // ignore extra origin storage seeding errors
207
360
  }
208
361
  }
209
362
  }
@@ -212,6 +365,35 @@ export async function startEmbeddedPlaywrightMcpServer(options) {
212
365
  // Prefer an existing page in the reused context (e.g., the CLI-authenticated one)
213
366
  const existingPages = context.pages();
214
367
  const activePage = existingPages.length > 0 ? existingPages[0] : page;
368
+ // Set up console log capture for the page
369
+ capturedConsoleLogs = []; // Reset logs for new session
370
+ activePage.on("console", (msg) => {
371
+ capturedConsoleLogs.push({
372
+ type: msg.type(),
373
+ text: msg.text(),
374
+ timestamp: Date.now(),
375
+ });
376
+ });
377
+ // Auto-navigate to the app URL if provided, so the agent doesn't need to navigate manually
378
+ if (options?.appUrl) {
379
+ try {
380
+ await activePage.goto(options.appUrl, {
381
+ waitUntil: "load",
382
+ timeout: 60000,
383
+ });
384
+ }
385
+ catch (error) {
386
+ // Don't throw - let the agent handle navigation if auto-nav fails
387
+ // Log the error for debugging purposes
388
+ logger.warn(`[MCP-Server] Auto-navigation to ${options.appUrl} failed: ${String(error)}`, {
389
+ error: {
390
+ kind: "McpServerAutoNavError",
391
+ message: String(error),
392
+ stack: error instanceof Error ? error.stack : undefined,
393
+ },
394
+ });
395
+ }
396
+ }
215
397
  const server = http.createServer((req, res) => {
216
398
  void (async () => {
217
399
  try {
@@ -312,14 +494,8 @@ async function readRequestBody(req) {
312
494
  }
313
495
  async function handleJsonRpcRequest(request, page) {
314
496
  const { method, id } = request;
315
- console.log("🎬 MCP Server: Received request", {
316
- method,
317
- id,
318
- hasParams: !!request.params,
319
- });
320
497
  try {
321
498
  if (method === "tools/list") {
322
- console.log("🎬 MCP Server: Listing tools");
323
499
  return {
324
500
  jsonrpc: "2.0",
325
501
  id,
@@ -327,16 +503,102 @@ async function handleJsonRpcRequest(request, page) {
327
503
  tools: [
328
504
  {
329
505
  name: "playwright_action",
330
- description: "Execute Playwright browser automation actions (navigate, click, fill, screenshot, etc.)",
506
+ description: `Execute Playwright browser automation actions. The browser is already navigated to the app.
507
+
508
+ ACTIONS:
509
+ - screenshot: Take a screenshot. Params: { fullPage?: boolean }
510
+ - click: Click an element. Params: { selector: string }
511
+ - fill: Type into an input. Params: { selector: string, value: string }
512
+ - getText: Get text content. Params: { selector: string }
513
+ - waitForSelector: Wait for element. Params: { selector: string }
514
+ - evaluate: Run JavaScript. Params: { script: string }
515
+ - getUrl: Get current URL. No params.
516
+ - reload: Reload the page. No params.
517
+ - navigate: Go to URL. Params: { url: string }
518
+ - getConsoleLogs: Get browser console logs. Params: { clear?: boolean }
519
+ - scroll: Scroll the page. Params: { x?: number, y?: number, deltaX?: number, deltaY?: number }
520
+ - scrollIntoView: Scroll element into view. Params: { selector: string }
521
+
522
+ SELECTOR EXAMPLES:
523
+ - By testid: [data-testid="login-form"]
524
+ - By name: input[name="username"], textarea[name="bio"]
525
+ - By placeholder: input[placeholder="Enter name"]
526
+ - By type: input[type="email"], button[type="submit"]
527
+ - By text content: button:has-text("Submit"), a:has-text("Click here")
528
+ - By ID: #submitBtn, #loginForm
529
+ - By class: .btn-primary, .form-input
530
+ - Combining: form#login input[name="password"]
531
+
532
+ Any selector that playwright supports can be used.
533
+
534
+ CSS SELECTOR SYNTAX (IMPORTANT):
535
+ ✓ CORRECT: input[name="email"], button[type="submit"], #myId, .myClass
536
+ ✗ WRONG: input [name="email"] (no space before bracket!)
537
+ ✗ WRONG: input[name=email] (quotes required around value)
538
+
539
+ TIPS:
540
+ 1. Use specific selectors: prefer [name="x"] or [placeholder="x"] over generic .class
541
+ 2. If click/fill fails, the element may not be visible - try waitForSelector first
542
+ 3. For dynamic content, use waitForSelector before interacting
543
+ 4. Navigate, click, and fill will automatically take a screenshot of the page after the action is performed`,
331
544
  inputSchema: {
332
545
  type: "object",
333
546
  properties: {
334
547
  action: {
335
548
  type: "string",
336
549
  enum: PLAYWRIGHT_ACTIONS,
550
+ description: "The action to perform. Most actions require a 'selector' parameter.",
551
+ },
552
+ description: {
553
+ type: "string",
554
+ description: "Brief description of what this test step is verifying (e.g., 'Verify login form appears', 'Test submit button functionality'). This will be displayed in the test report.",
555
+ },
556
+ selector: {
557
+ type: "string",
558
+ description: 'CSS selector for the target element. NO SPACE before brackets! Example: input[name="email"] NOT input [name="email"]',
559
+ },
560
+ value: {
561
+ type: "string",
562
+ description: "Value to fill (for 'fill' action)",
563
+ },
564
+ url: {
565
+ type: "string",
566
+ description: "URL to navigate to (for 'navigate' action)",
567
+ },
568
+ script: {
569
+ type: "string",
570
+ description: "JavaScript to execute in browser (for 'evaluate' action). No imports allowed.",
571
+ },
572
+ fullPage: {
573
+ type: "boolean",
574
+ description: "Capture full scrollable page (for 'screenshot' action)",
575
+ },
576
+ clear: {
577
+ type: "boolean",
578
+ description: "Clear logs after retrieving (for 'getConsoleLogs' action)",
579
+ },
580
+ x: {
581
+ type: "number",
582
+ description: "Absolute horizontal scroll position in pixels (for 'scroll' action)",
583
+ },
584
+ y: {
585
+ type: "number",
586
+ description: "Absolute vertical scroll position in pixels (for 'scroll' action)",
587
+ },
588
+ deltaX: {
589
+ type: "number",
590
+ description: "Relative horizontal scroll amount in pixels (for 'scroll' action)",
591
+ },
592
+ deltaY: {
593
+ type: "number",
594
+ description: "Relative vertical scroll amount in pixels (for 'scroll' action)",
595
+ },
596
+ testCaseId: {
597
+ type: "string",
598
+ description: "ID of the test case this action belongs to. Required when test cases are predefined. Links this action to a specific test case for status tracking.",
337
599
  },
338
600
  },
339
- required: ["action"],
601
+ required: ["action", "description"],
340
602
  },
341
603
  },
342
604
  ],
@@ -344,13 +606,7 @@ async function handleJsonRpcRequest(request, page) {
344
606
  };
345
607
  }
346
608
  if (method === "tools/call") {
347
- console.log("🎬 MCP Server: tools/call method");
348
609
  const params = request.params;
349
- console.log("🎬 MCP Server: Tool call params", {
350
- toolName: params?.name,
351
- hasArguments: !!params?.arguments,
352
- action: params?.arguments?.action,
353
- });
354
610
  if (!params || params.name !== "playwright_action") {
355
611
  throw new Error(`Unknown tool: ${params?.name ?? "undefined"}`);
356
612
  }
@@ -358,12 +614,12 @@ async function handleJsonRpcRequest(request, page) {
358
614
  if (!action || !PLAYWRIGHT_ACTIONS.includes(action)) {
359
615
  throw new Error(`Unsupported Playwright action: ${String(action)}`);
360
616
  }
361
- console.log(`🎬 MCP Server: Executing action: ${action}`);
362
617
  const result = await executePlaywrightAction(action, page, params.arguments ?? {});
363
- console.log(`🎬 MCP Server: Action ${action} completed`, {
364
- success: result?.success,
365
- hasResult: !!result,
366
- });
618
+ // Include testCaseId in the result if provided
619
+ const testCaseId = params.arguments?.testCaseId;
620
+ if (testCaseId) {
621
+ result.testCaseId = testCaseId;
622
+ }
367
623
  return {
368
624
  jsonrpc: "2.0",
369
625
  id,
@@ -392,96 +648,238 @@ async function handleJsonRpcRequest(request, page) {
392
648
  };
393
649
  }
394
650
  }
395
- async function executePlaywrightAction(action, page, params) {
396
- console.log("🎬 MCP Server: executePlaywrightAction called", {
397
- action,
398
- paramKeys: Object.keys(params),
399
- url: page.url(),
651
+ /**
652
+ * Captures a screenshot in PNG format.
653
+ *
654
+ * TODO: Re-enable WebP conversion using sharp once the build issue is resolved.
655
+ * WebP provides ~25-34% smaller file sizes compared to JPEG at equivalent
656
+ * visual quality, with better compression than PNG.
657
+ *
658
+ * @param page - The Playwright page to capture
659
+ * @returns Screenshot data as base64 string with format identifier
660
+ */
661
+ async function captureScreenshot(page) {
662
+ try {
663
+ await page.evaluate(() => document.fonts.ready);
664
+ await page.waitForTimeout(100);
665
+ }
666
+ catch {
667
+ // Continue anyway if fonts.ready fails
668
+ }
669
+ const pngBuffer = await page.screenshot({
670
+ fullPage: false,
671
+ scale: "css",
672
+ type: "png",
400
673
  });
674
+ // TODO: Re-enable WebP conversion once sharp build issue is resolved
675
+ // const webpBuffer = await sharp(pngBuffer).webp({ quality: 80 }).toBuffer();
676
+ // return { data: webpBuffer.toString("base64"), format: "webp" };
677
+ return {
678
+ data: pngBuffer.toString("base64"),
679
+ format: "png",
680
+ };
681
+ }
682
+ async function executePlaywrightAction(action, page, params) {
401
683
  switch (action) {
402
684
  case "navigate": {
403
685
  const url = params.url;
686
+ const description = params.description || "Navigate to page";
404
687
  if (!url)
405
688
  throw new Error("Missing url parameter for navigate action");
406
- console.log(`🎬 MCP Server: Navigating to ${url}`);
407
689
  try {
408
690
  await page.goto(url, {
409
691
  waitUntil: "load",
410
692
  timeout: 60000, // 60 second timeout
411
693
  });
412
- console.log(`🎬 MCP Server: Navigation complete`);
413
- return { success: true, url };
694
+ // Auto-capture screenshot after navigation
695
+ const screenshot = await captureScreenshot(page);
696
+ return {
697
+ success: true,
698
+ action: "navigate",
699
+ description,
700
+ screenshot: screenshot.data,
701
+ format: screenshot.format,
702
+ context: {
703
+ url,
704
+ },
705
+ };
414
706
  }
415
707
  catch (error) {
416
708
  console.error(`🎬 MCP Server: Navigation failed: ${String(error)}`);
417
- // Return error but don't throw - let the judge handle it
709
+ // Capture screenshot even on error
710
+ let errorScreenshot = null;
711
+ try {
712
+ errorScreenshot = await captureScreenshot(page);
713
+ }
714
+ catch {
715
+ // Ignore screenshot errors
716
+ }
418
717
  return {
419
718
  success: false,
719
+ action: "navigate",
720
+ description,
721
+ screenshot: errorScreenshot?.data,
722
+ format: errorScreenshot?.format ?? "png",
723
+ context: {
724
+ url,
725
+ },
420
726
  error: `Navigation to ${url} failed: ${String(error)}`,
421
- url,
422
727
  };
423
728
  }
424
729
  }
425
730
  case "click": {
426
731
  const selector = params.selector;
732
+ const description = params.description || "Click element";
427
733
  if (!selector)
428
734
  throw new Error("Missing selector for click action");
429
- // Always target elements inside the iframe
430
- const frame = page.frameLocator('[data-test="sb-iframe"]');
431
- await frame.locator(selector).click({ timeout: params.timeout });
432
- return { success: true };
735
+ try {
736
+ await page
737
+ .locator(selector)
738
+ .click({ timeout: params.timeout ?? 10000 });
739
+ // Auto-capture screenshot after action
740
+ const screenshot = await captureScreenshot(page);
741
+ return {
742
+ success: true,
743
+ action: "click",
744
+ description,
745
+ screenshot: screenshot.data,
746
+ format: screenshot.format,
747
+ context: {
748
+ selector,
749
+ },
750
+ };
751
+ }
752
+ catch (error) {
753
+ // Capture screenshot even on error
754
+ let errorScreenshot = null;
755
+ try {
756
+ errorScreenshot = await captureScreenshot(page);
757
+ }
758
+ catch {
759
+ // Ignore screenshot errors
760
+ }
761
+ return {
762
+ success: false,
763
+ action: "click",
764
+ description,
765
+ screenshot: errorScreenshot?.data,
766
+ format: errorScreenshot?.format ?? "png",
767
+ context: {
768
+ selector,
769
+ },
770
+ error: `Click failed on "${selector}": ${String(error)}`,
771
+ };
772
+ }
433
773
  }
434
774
  case "fill": {
435
775
  const selector = params.selector;
436
776
  const value = params.value ?? "";
777
+ const description = params.description || "Fill input";
437
778
  if (!selector)
438
779
  throw new Error("Missing selector for fill action");
439
- // Always target elements inside the iframe
440
- const frame = page.frameLocator('[data-test="sb-iframe"]');
441
- await frame.locator(selector).fill(value, { timeout: params.timeout });
442
- return { success: true };
780
+ try {
781
+ await page
782
+ .locator(selector)
783
+ .fill(value, { timeout: params.timeout ?? 10000 });
784
+ // Auto-capture screenshot after fill
785
+ const screenshot = await captureScreenshot(page);
786
+ return {
787
+ success: true,
788
+ action: "fill",
789
+ description,
790
+ screenshot: screenshot.data,
791
+ format: screenshot.format,
792
+ context: {
793
+ selector,
794
+ value,
795
+ },
796
+ };
797
+ }
798
+ catch (error) {
799
+ // Capture screenshot even on error
800
+ let errorScreenshot = null;
801
+ try {
802
+ errorScreenshot = await captureScreenshot(page);
803
+ }
804
+ catch {
805
+ // Ignore screenshot errors
806
+ }
807
+ return {
808
+ success: false,
809
+ action: "fill",
810
+ description,
811
+ screenshot: errorScreenshot?.data,
812
+ format: errorScreenshot?.format ?? "png",
813
+ context: {
814
+ selector,
815
+ value,
816
+ },
817
+ error: `Fill failed on "${selector}": ${String(error)}`,
818
+ };
819
+ }
443
820
  }
444
821
  case "screenshot": {
445
- const buffer = await page.screenshot({
446
- fullPage: Boolean(params.fullPage),
447
- });
822
+ const description = params.description || "Capture current state";
823
+ // Capture screenshot in WebP format for optimal compression
824
+ const screenshot = await captureScreenshot(page);
448
825
  return {
449
826
  success: true,
450
- screenshot: buffer.toString("base64"),
827
+ action: "screenshot",
828
+ description,
829
+ screenshot: screenshot.data,
830
+ format: screenshot.format,
831
+ context: {
832
+ fullPage: Boolean(params.fullPage),
833
+ },
451
834
  };
452
835
  }
453
836
  case "getText": {
454
837
  const selector = params.selector;
455
838
  if (!selector)
456
839
  throw new Error("Missing selector for getText action");
457
- // Always target elements inside the iframe
458
- const frame = page.frameLocator('[data-test="sb-iframe"]');
459
- const text = await frame.locator(selector).textContent({
460
- timeout: params.timeout,
461
- });
462
- return { success: true, text };
840
+ try {
841
+ const text = await page.locator(selector).textContent({
842
+ timeout: params.timeout ?? 10000,
843
+ });
844
+ return { success: true, text };
845
+ }
846
+ catch (error) {
847
+ return {
848
+ success: false,
849
+ error: `getText failed on "${selector}": ${String(error)}`,
850
+ };
851
+ }
463
852
  }
464
853
  case "waitForSelector": {
465
854
  const selector = params.selector;
466
855
  if (!selector) {
467
856
  throw new Error("Missing selector for waitForSelector action");
468
857
  }
469
- // Always target elements inside the iframe
470
- const frame = page.frameLocator('[data-test="sb-iframe"]');
471
- await frame.locator(selector).waitFor({
472
- timeout: params.timeout ?? 30_000,
473
- });
474
- return { success: true };
858
+ try {
859
+ await page.locator(selector).waitFor({
860
+ timeout: params.timeout ?? 15000,
861
+ });
862
+ return { success: true };
863
+ }
864
+ catch (error) {
865
+ return {
866
+ success: false,
867
+ error: `waitForSelector failed on "${selector}": ${String(error)}`,
868
+ };
869
+ }
475
870
  }
476
871
  case "evaluate": {
477
872
  const script = params.script;
873
+ const description = params.description || "Execute JavaScript";
478
874
  if (typeof script !== "string") {
479
875
  throw new Error("Missing script for evaluate action");
480
876
  }
481
877
  // Check for import/export/require statements that can't be used in browser context
482
- if (script.includes("import ") ||
483
- script.includes("export ") ||
484
- script.includes("require(")) {
878
+ // Use regex with word boundaries to avoid false positives on strings like "important"
879
+ const hasImport = /\bimport\s+/.test(script);
880
+ const hasExport = /\bexport\s+/.test(script);
881
+ const hasRequire = /\brequire\s*\(/.test(script);
882
+ if (hasImport || hasExport || hasRequire) {
485
883
  throw new Error("Cannot use import/export/require statements in browser evaluate context. " +
486
884
  "Please use plain JavaScript that can run in a browser console. " +
487
885
  "For example, use 'document.querySelector' instead of importing libraries.");
@@ -496,32 +894,64 @@ async function executePlaywrightAction(action, page, params) {
496
894
  return { error: e.toString(), stack: e.stack };
497
895
  }
498
896
  })()`;
499
- // Always execute JavaScript inside the iframe
500
- const frameElement = await page
501
- .locator('[data-test="sb-iframe"]')
502
- .elementHandle();
503
- const frame = await frameElement?.contentFrame();
504
- if (!frame) {
505
- throw new Error("Could not find iframe [data-test='sb-iframe'] or iframe not loaded");
506
- }
507
- const result = await frame.evaluate(wrappedScript);
897
+ const result = await page.evaluate(wrappedScript);
508
898
  // Check if the evaluation returned an error
509
899
  if (result && typeof result === "object" && "error" in result) {
510
900
  throw new Error(`Browser evaluation failed: ${result.error}`);
511
901
  }
512
- return { success: true, result };
902
+ // Auto-capture screenshot after evaluation
903
+ const screenshot = await captureScreenshot(page);
904
+ return {
905
+ success: true,
906
+ action: "evaluate",
907
+ description,
908
+ result,
909
+ screenshot: screenshot.data,
910
+ format: screenshot.format,
911
+ context: {
912
+ script: script.substring(0, 100) + (script.length > 100 ? "..." : ""),
913
+ },
914
+ };
513
915
  }
514
916
  catch (error) {
917
+ // Capture screenshot even on error
918
+ let errorScreenshot = null;
919
+ try {
920
+ errorScreenshot = await captureScreenshot(page);
921
+ }
922
+ catch {
923
+ // Ignore screenshot errors
924
+ }
515
925
  // If the error looks like a syntax error, provide helpful guidance
516
926
  const errorMsg = String(error);
517
927
  if (errorMsg.includes("SyntaxError") ||
518
928
  errorMsg.includes("import") ||
519
929
  errorMsg.includes("module")) {
520
- throw new Error("JavaScript evaluation failed. Make sure your code is browser-compatible " +
521
- "(no import/export statements). Error: " +
522
- errorMsg);
930
+ return {
931
+ success: false,
932
+ action: "evaluate",
933
+ description,
934
+ screenshot: errorScreenshot?.data,
935
+ format: errorScreenshot?.format ?? "png",
936
+ context: {
937
+ script: script.substring(0, 100) + (script.length > 100 ? "..." : ""),
938
+ },
939
+ error: "JavaScript evaluation failed. Make sure your code is browser-compatible " +
940
+ "(no import/export statements). Error: " +
941
+ errorMsg,
942
+ };
523
943
  }
524
- throw error;
944
+ return {
945
+ success: false,
946
+ action: "evaluate",
947
+ description,
948
+ screenshot: errorScreenshot?.data,
949
+ format: errorScreenshot?.format ?? "png",
950
+ context: {
951
+ script: script.substring(0, 100) + (script.length > 100 ? "..." : ""),
952
+ },
953
+ error: String(error),
954
+ };
525
955
  }
526
956
  }
527
957
  case "getUrl": {
@@ -534,6 +964,112 @@ async function executePlaywrightAction(action, page, params) {
534
964
  });
535
965
  return { success: true };
536
966
  }
967
+ case "getConsoleLogs": {
968
+ // Return captured console logs and optionally clear them
969
+ const logs = [...capturedConsoleLogs];
970
+ if (params.clear) {
971
+ capturedConsoleLogs = [];
972
+ }
973
+ return {
974
+ success: true,
975
+ logs,
976
+ count: logs.length,
977
+ };
978
+ }
979
+ case "scroll": {
980
+ const { x, y, deltaX, deltaY } = params;
981
+ const description = params.description || "Scroll page";
982
+ try {
983
+ await page.evaluate(({ x, y, deltaX, deltaY }) => {
984
+ if (deltaX !== undefined || deltaY !== undefined) {
985
+ window.scrollBy(deltaX ?? 0, deltaY ?? 0);
986
+ }
987
+ else if (x !== undefined || y !== undefined) {
988
+ window.scrollTo(x ?? window.scrollX, y ?? window.scrollY);
989
+ }
990
+ }, { x, y, deltaX, deltaY });
991
+ const screenshot = await captureScreenshot(page);
992
+ return {
993
+ success: true,
994
+ action: "scroll",
995
+ description,
996
+ screenshot: screenshot.data,
997
+ format: screenshot.format,
998
+ context: {
999
+ x,
1000
+ y,
1001
+ deltaX,
1002
+ deltaY,
1003
+ },
1004
+ };
1005
+ }
1006
+ catch (error) {
1007
+ let errorScreenshot = null;
1008
+ try {
1009
+ errorScreenshot = await captureScreenshot(page);
1010
+ }
1011
+ catch {
1012
+ // Ignore screenshot errors
1013
+ }
1014
+ return {
1015
+ success: false,
1016
+ action: "scroll",
1017
+ description,
1018
+ screenshot: errorScreenshot?.data,
1019
+ format: errorScreenshot?.format ?? "png",
1020
+ context: {
1021
+ x,
1022
+ y,
1023
+ deltaX,
1024
+ deltaY,
1025
+ },
1026
+ error: `Scroll failed: ${String(error)}`,
1027
+ };
1028
+ }
1029
+ }
1030
+ case "scrollIntoView": {
1031
+ const selector = params.selector;
1032
+ const description = params.description || "Scroll element into view";
1033
+ if (!selector) {
1034
+ throw new Error("Missing selector for scrollIntoView action");
1035
+ }
1036
+ try {
1037
+ await page.locator(selector).scrollIntoViewIfNeeded({
1038
+ timeout: params.timeout ?? 10000,
1039
+ });
1040
+ const screenshot = await captureScreenshot(page);
1041
+ return {
1042
+ success: true,
1043
+ action: "scrollIntoView",
1044
+ description,
1045
+ screenshot: screenshot.data,
1046
+ format: screenshot.format,
1047
+ context: {
1048
+ selector,
1049
+ },
1050
+ };
1051
+ }
1052
+ catch (error) {
1053
+ let errorScreenshot = null;
1054
+ try {
1055
+ errorScreenshot = await captureScreenshot(page);
1056
+ }
1057
+ catch {
1058
+ // Ignore screenshot errors
1059
+ }
1060
+ return {
1061
+ success: false,
1062
+ action: "scrollIntoView",
1063
+ description,
1064
+ screenshot: errorScreenshot?.data,
1065
+ format: errorScreenshot?.format ?? "png",
1066
+ context: {
1067
+ selector,
1068
+ },
1069
+ error: `scrollIntoView failed on "${selector}": ${String(error)}`,
1070
+ };
1071
+ }
1072
+ }
537
1073
  default:
538
1074
  throw new Error(`Unhandled Playwright action: ${action}`);
539
1075
  }