@superblocksteam/vite-plugin-file-sync 2.0.72-next.1 → 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.
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts +2 -1
- package/dist/ai-service/agent/prompts/build-base-system-prompt.d.ts.map +1 -1
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js +18 -2
- package/dist/ai-service/agent/prompts/build-base-system-prompt.js.map +1 -1
- package/dist/ai-service/agent/subagents/testing/index.d.ts +3 -0
- package/dist/ai-service/agent/subagents/testing/index.d.ts.map +1 -0
- package/dist/ai-service/agent/subagents/testing/index.js +2 -0
- package/dist/ai-service/agent/subagents/testing/index.js.map +1 -0
- package/dist/ai-service/agent/subagents/testing/prompt-builder.d.ts +10 -0
- package/dist/ai-service/agent/subagents/testing/prompt-builder.d.ts.map +1 -0
- package/dist/ai-service/agent/subagents/testing/prompt-builder.js +67 -0
- package/dist/ai-service/agent/subagents/testing/prompt-builder.js.map +1 -0
- package/dist/ai-service/agent/subagents/testing/types.d.ts +47 -0
- package/dist/ai-service/agent/subagents/testing/types.d.ts.map +1 -0
- package/dist/ai-service/agent/subagents/testing/types.js +2 -0
- package/dist/ai-service/agent/subagents/testing/types.js.map +1 -0
- package/dist/ai-service/agent/subagents/types.d.ts +9 -8
- package/dist/ai-service/agent/subagents/types.d.ts.map +1 -1
- package/dist/ai-service/agent/subagents/types.js +9 -9
- package/dist/ai-service/agent/subagents/types.js.map +1 -1
- package/dist/ai-service/agent/tool-message-utils.d.ts +7 -2
- package/dist/ai-service/agent/tool-message-utils.d.ts.map +1 -1
- package/dist/ai-service/agent/tool-message-utils.js +21 -2
- package/dist/ai-service/agent/tool-message-utils.js.map +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.js +1 -1
- package/dist/ai-service/agent/tools/apis/test-api.js.map +1 -1
- package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts +1 -0
- package/dist/ai-service/agent/tools/build-capture-screenshot.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-capture-screenshot.js +4 -2
- package/dist/ai-service/agent/tools/build-capture-screenshot.js.map +1 -1
- package/dist/ai-service/agent/tools/build-delete-file.d.ts +1 -1
- package/dist/ai-service/agent/tools/build-manage-checklist.d.ts +1 -1
- package/dist/ai-service/agent/tools/{build-read-files.d.ts → build-read-file.d.ts} +10 -6
- package/dist/ai-service/agent/tools/build-read-file.d.ts.map +1 -0
- package/dist/ai-service/agent/tools/build-read-file.js +139 -0
- package/dist/ai-service/agent/tools/build-read-file.js.map +1 -0
- package/dist/ai-service/agent/tools/build-reload-file.d.ts +4 -1
- package/dist/ai-service/agent/tools/build-reload-file.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/build-reload-file.js +18 -8
- package/dist/ai-service/agent/tools/build-reload-file.js.map +1 -1
- package/dist/ai-service/agent/tools/get-console-logs.js +1 -1
- package/dist/ai-service/agent/tools/get-console-logs.js.map +1 -1
- package/dist/ai-service/agent/tools/get-runtime-errors.js +1 -1
- package/dist/ai-service/agent/tools/get-runtime-errors.js.map +1 -1
- package/dist/ai-service/agent/tools/index.d.ts +4 -1
- package/dist/ai-service/agent/tools/index.d.ts.map +1 -1
- package/dist/ai-service/agent/tools/index.js +4 -1
- package/dist/ai-service/agent/tools/index.js.map +1 -1
- package/dist/ai-service/agent/tools.d.ts.map +1 -1
- package/dist/ai-service/agent/tools.js +89 -29
- package/dist/ai-service/agent/tools.js.map +1 -1
- package/dist/ai-service/agent/tools2/access-control.d.ts +23 -1
- package/dist/ai-service/agent/tools2/access-control.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/access-control.js +67 -1
- package/dist/ai-service/agent/tools2/access-control.js.map +1 -1
- package/dist/ai-service/agent/tools2/entity-permissions.d.ts +26 -0
- package/dist/ai-service/agent/tools2/entity-permissions.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/entity-permissions.js +15 -0
- package/dist/ai-service/agent/tools2/entity-permissions.js.map +1 -1
- package/dist/ai-service/agent/tools2/example.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/example.js +2 -4
- package/dist/ai-service/agent/tools2/example.js.map +1 -1
- package/dist/ai-service/agent/tools2/index.d.ts +1 -1
- package/dist/ai-service/agent/tools2/index.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/index.js +1 -1
- package/dist/ai-service/agent/tools2/index.js.map +1 -1
- package/dist/ai-service/agent/tools2/registry.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/registry.js +37 -23
- package/dist/ai-service/agent/tools2/registry.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/end-test-run.d.ts +31 -0
- package/dist/ai-service/agent/tools2/tools/end-test-run.d.ts.map +1 -0
- package/dist/ai-service/agent/tools2/tools/end-test-run.js +105 -0
- package/dist/ai-service/agent/tools2/tools/end-test-run.js.map +1 -0
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts +1 -0
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +135 -11
- package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
- package/dist/ai-service/agent/tools2/tools/start-test-run.d.ts +24 -0
- package/dist/ai-service/agent/tools2/tools/start-test-run.d.ts.map +1 -0
- package/dist/ai-service/agent/tools2/tools/start-test-run.js +258 -0
- package/dist/ai-service/agent/tools2/tools/start-test-run.js.map +1 -0
- package/dist/ai-service/agent/tools2/tools/update-test-case-status.d.ts +29 -0
- package/dist/ai-service/agent/tools2/tools/update-test-case-status.d.ts.map +1 -0
- package/dist/ai-service/agent/tools2/tools/update-test-case-status.js +87 -0
- package/dist/ai-service/agent/tools2/tools/update-test-case-status.js.map +1 -0
- package/dist/ai-service/agent/tools2/types.d.ts +6 -24
- package/dist/ai-service/agent/tools2/types.d.ts.map +1 -1
- package/dist/ai-service/agent/tools2/types.js +4 -15
- package/dist/ai-service/agent/tools2/types.js.map +1 -1
- package/dist/ai-service/agent/utils.d.ts +10 -0
- package/dist/ai-service/agent/utils.d.ts.map +1 -1
- package/dist/ai-service/agent/utils.js +115 -1
- package/dist/ai-service/agent/utils.js.map +1 -1
- package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
- package/dist/ai-service/chat/chat-session-store.js +122 -1
- package/dist/ai-service/chat/chat-session-store.js.map +1 -1
- package/dist/ai-service/features.d.ts +4 -0
- package/dist/ai-service/features.d.ts.map +1 -1
- package/dist/ai-service/features.js +4 -0
- package/dist/ai-service/features.js.map +1 -1
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +14 -8
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/judge/integration/mcp-client.d.ts +3 -6
- package/dist/ai-service/judge/integration/mcp-client.d.ts.map +1 -1
- package/dist/ai-service/judge/integration/mcp-client.js.map +1 -1
- package/dist/ai-service/judge/tools/playwright-action.d.ts +1 -1
- package/dist/ai-service/judge/tools/submit-feedback.d.ts +1 -1
- package/dist/ai-service/llm/client.d.ts +6 -0
- package/dist/ai-service/llm/client.d.ts.map +1 -1
- package/dist/ai-service/llm/client.js +9 -0
- package/dist/ai-service/llm/client.js.map +1 -1
- package/dist/ai-service/llm/context/constants.d.ts +8 -0
- package/dist/ai-service/llm/context/constants.d.ts.map +1 -1
- package/dist/ai-service/llm/context/constants.js +8 -0
- package/dist/ai-service/llm/context/constants.js.map +1 -1
- package/dist/ai-service/llm/context/context.d.ts.map +1 -1
- package/dist/ai-service/llm/context/context.js +12 -9
- package/dist/ai-service/llm/context/context.js.map +1 -1
- package/dist/ai-service/llm/context/manager.d.ts +1 -1
- package/dist/ai-service/llm/context/manager.js +1 -1
- package/dist/ai-service/llm/context/utils/message-utils.d.ts +10 -0
- package/dist/ai-service/llm/context/utils/message-utils.d.ts.map +1 -1
- package/dist/ai-service/llm/context/utils/message-utils.js +92 -0
- package/dist/ai-service/llm/context/utils/message-utils.js.map +1 -1
- package/dist/ai-service/llm/interaction/provider.d.ts +1 -0
- package/dist/ai-service/llm/interaction/provider.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/event-bus.d.ts +5 -0
- package/dist/ai-service/llm/stream/event-bus.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/event-bus.js.map +1 -1
- package/dist/ai-service/llm/stream/observers/llmobs.d.ts +4 -1
- package/dist/ai-service/llm/stream/observers/llmobs.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/observers/llmobs.js +194 -10
- package/dist/ai-service/llm/stream/observers/llmobs.js.map +1 -1
- package/dist/ai-service/llm/stream/observers/logging.d.ts +1 -0
- package/dist/ai-service/llm/stream/observers/logging.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/observers/logging.js +92 -20
- package/dist/ai-service/llm/stream/observers/logging.js.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.d.ts +7 -1
- package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/orchestrator.js +24 -4
- package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
- package/dist/ai-service/llm/stream/session.d.ts +12 -2
- package/dist/ai-service/llm/stream/session.d.ts.map +1 -1
- package/dist/ai-service/llm/stream/session.js +9 -26
- package/dist/ai-service/llm/stream/session.js.map +1 -1
- package/dist/ai-service/llmobs/tracer.d.ts +0 -5
- package/dist/ai-service/llmobs/tracer.d.ts.map +1 -1
- package/dist/ai-service/llmobs/tracer.js +1 -40
- package/dist/ai-service/llmobs/tracer.js.map +1 -1
- package/dist/ai-service/llmobs/types.d.ts +1 -0
- package/dist/ai-service/llmobs/types.d.ts.map +1 -1
- package/dist/ai-service/mcp/adapter/mcp-tool-adapter.d.ts +1 -1
- package/dist/ai-service/mcp/adapter/mcp-tool-adapter.d.ts.map +1 -1
- package/dist/ai-service/mcp/adapter/mcp-tool-adapter.js +5 -2
- package/dist/ai-service/mcp/adapter/mcp-tool-adapter.js.map +1 -1
- package/dist/ai-service/mcp/embedded-playwright-mcp-server.d.ts +16 -1
- package/dist/ai-service/mcp/embedded-playwright-mcp-server.d.ts.map +1 -1
- package/dist/ai-service/mcp/embedded-playwright-mcp-server.js +625 -89
- package/dist/ai-service/mcp/embedded-playwright-mcp-server.js.map +1 -1
- package/dist/ai-service/mcp/playwright-server.d.ts +10 -0
- package/dist/ai-service/mcp/playwright-server.d.ts.map +1 -1
- package/dist/ai-service/mcp/playwright-server.js +3 -0
- package/dist/ai-service/mcp/playwright-server.js.map +1 -1
- package/dist/ai-service/mcp/types.d.ts +4 -0
- package/dist/ai-service/mcp/types.d.ts.map +1 -1
- package/dist/ai-service/prompts/explain-code.d.ts +2 -2
- package/dist/ai-service/prompts/explain-code.d.ts.map +1 -1
- package/dist/ai-service/prompts/explain-code.js +2 -2
- package/dist/ai-service/prompts/explain-code.js.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +18 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.js +15 -0
- package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.js +36 -6
- package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.js +87 -34
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
- package/dist/ai-service/state-machine/mocks.js +2 -0
- package/dist/ai-service/state-machine/mocks.js.map +1 -1
- package/dist/ai-service/state-machine/traced-fsm.d.ts +2 -0
- package/dist/ai-service/state-machine/traced-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/traced-fsm.js +18 -0
- package/dist/ai-service/state-machine/traced-fsm.js.map +1 -1
- package/dist/ai-service/util/safe-parse.d.ts +2 -0
- package/dist/ai-service/util/safe-parse.d.ts.map +1 -0
- package/dist/ai-service/util/safe-parse.js +9 -0
- package/dist/ai-service/util/safe-parse.js.map +1 -0
- package/dist/ai-service/util/safe-stringify.d.ts.map +1 -1
- package/dist/ai-service/util/safe-stringify.js +7 -0
- package/dist/ai-service/util/safe-stringify.js.map +1 -1
- package/dist/ai-service/util/stop-condition.d.ts +1 -0
- package/dist/ai-service/util/stop-condition.d.ts.map +1 -1
- package/dist/ai-service/util/stop-condition.js +5 -0
- package/dist/ai-service/util/stop-condition.js.map +1 -1
- package/dist/ai-service/util/strip-content.d.ts +2 -0
- package/dist/ai-service/util/strip-content.d.ts.map +1 -0
- package/dist/ai-service/util/strip-content.js +31 -0
- package/dist/ai-service/util/strip-content.js.map +1 -0
- package/dist/ai-service/util/tool-signature.d.ts +13 -0
- package/dist/ai-service/util/tool-signature.d.ts.map +1 -0
- package/dist/ai-service/util/tool-signature.js +38 -0
- package/dist/ai-service/util/tool-signature.js.map +1 -0
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +3 -0
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/injected-index.js +2 -2
- package/dist/injected-index.js.map +1 -1
- package/dist/parsing/jsx.d.ts.map +1 -1
- package/dist/parsing/jsx.js +0 -2
- package/dist/parsing/jsx.js.map +1 -1
- package/package.json +9 -9
- package/dist/ai-service/agent/tools/build-read-files.d.ts.map +0 -1
- package/dist/ai-service/agent/tools/build-read-files.js +0 -67
- 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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
169
|
-
|
|
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
|
|
206
|
-
|
|
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:
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
413
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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
|
|
446
|
-
|
|
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
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
|
|
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
|
}
|