import { once } from "node:events";
import fs from "node:fs";
import http from "node:http";
import { chromium, firefox, webkit, } from "playwright";
/**
 * Playwright action logger that captures browser actions and saves them as log artifacts.
 */
class PlaywrightActionLogger {
    logContent = "";
    sessionId;
    startTime;
    actionCount = 0;
    saveArtifact;
    runTimestamp;
    logger;
    constructor(options) {
        this.sessionId = options.sessionId;
        this.startTime = Date.now();
        this.saveArtifact = options.saveArtifact;
        this.runTimestamp = options.runTimestamp || new Date().toISOString();
        this.logger = options.logger;
        this.logContent += this.formatSection("PLAYWRIGHT SESSION START") + "\n";
        this.logContent += `Session ID: ${this.sessionId}\n`;
        this.logContent += `Timestamp: ${new Date().toISOString()}\n\n`;
    }
    formatSection(title) {
        const prefix = `------ ${title} `;
        const remaining = Math.max(0, 72 - prefix.length);
        return prefix + "-".repeat(remaining);
    }
    truncateString(s, maxBytes) {
        if (s.length <= maxBytes)
            return s;
        return (s.slice(0, maxBytes) + `\n... [truncated ${s.length - maxBytes} bytes]`);
    }
    /**
     * Log the start of a Playwright action.
     */
    logActionStart(action, params) {
        this.actionCount++;
        const timestamp = new Date().toISOString();
        this.logContent += `ACTION #${this.actionCount}: ${action}\n`;
        this.logContent += `  Timestamp: ${timestamp}\n`;
        // Log relevant parameters (exclude screenshot data)
        const logParams = {};
        for (const [key, value] of Object.entries(params)) {
            if (key === "screenshot" || key === "data")
                continue;
            logParams[key] = value;
        }
        if (Object.keys(logParams).length > 0) {
            this.logContent += `  Params:\n`;
            for (const [key, value] of Object.entries(logParams)) {
                const valueStr = typeof value === "string"
                    ? this.truncateString(value, 200)
                    : JSON.stringify(value);
                this.logContent += `    ${key}: ${valueStr}\n`;
            }
        }
    }
    /**
     * Log the result of a Playwright action.
     */
    logActionResult(result) {
        const success = result.success !== false;
        this.logContent += `  Result: ${success ? "SUCCESS" : "FAILED"}\n`;
        // Log error if present
        if (result.error) {
            this.logContent += `  Error: ${result.error}\n`;
        }
        // Log runtime errors if present
        if (result.runtimeErrors?.length > 0) {
            this.logContent += `  Runtime Errors:\n`;
            for (const err of result.runtimeErrors) {
                this.logContent += `    - ${err.header}\n`;
                if (err.stack) {
                    const truncatedStack = this.truncateString(err.stack, 500);
                    this.logContent += `      Stack: ${truncatedStack}\n`;
                }
            }
        }
        // Log specific result fields (excluding large data like screenshots)
        const fieldsToLog = ["url", "text", "hasErrors", "errorCount", "count"];
        for (const field of fieldsToLog) {
            if (field in result && result[field] !== undefined) {
                this.logContent += `  ${field}: ${JSON.stringify(result[field])}\n`;
            }
        }
        this.logContent += "\n";
    }
    /**
     * Log console messages from the page.
     */
    logConsoleLogs(logs) {
        if (logs.length === 0)
            return;
        this.logContent += `  Console Logs (${logs.length}):\n`;
        const maxLogs = 20;
        const logsToShow = logs.slice(-maxLogs);
        for (const log of logsToShow) {
            const text = this.truncateString(log.text, 200);
            this.logContent += `    [${log.type}] ${text}\n`;
        }
        if (logs.length > maxLogs) {
            this.logContent += `    ... and ${logs.length - maxLogs} more logs\n`;
        }
    }
    /**
     * Save the log to an artifact file.
     */
    async save() {
        if (!this.saveArtifact) {
            this.logger.debug("[PlaywrightLogger] No saveArtifact function provided, skipping log save");
            return;
        }
        const duration = Date.now() - this.startTime;
        this.logContent += this.formatSection("PLAYWRIGHT SESSION END") + "\n";
        this.logContent += `End Timestamp: ${new Date().toISOString()}\n`;
        this.logContent += `Duration: ${duration}ms\n`;
        this.logContent += `Total Actions: ${this.actionCount}\n`;
        try {
            const artifact = {
                type: "file",
                filePath: `playwright-session-${this.sessionId}.log`,
                content: this.logContent,
            };
            const stepId = `playwright-session-${this.sessionId}`;
            await this.saveArtifact(artifact, stepId, this.runTimestamp);
            this.logger.debug(`[PlaywrightLogger] Saved log artifact: ${artifact.filePath}`);
        }
        catch (error) {
            this.logger.error(`[PlaywrightLogger] Failed to save log: ${String(error)}`);
        }
    }
    /**
     * Save the current log and reset for a new session.
     * Used to save logs after each test run without closing the server.
     */
    async saveAndReset() {
        // Only save if there are actions logged
        if (this.actionCount === 0) {
            this.logger.debug("[PlaywrightLogger] No actions logged, skipping save and reset");
            return;
        }
        await this.save();
        // Reset for next session
        const newSessionId = String(Date.now());
        this.sessionId = newSessionId;
        this.startTime = Date.now();
        this.actionCount = 0;
        this.logContent = "";
        this.logContent += this.formatSection("PLAYWRIGHT SESSION START") + "\n";
        this.logContent += `Session ID: ${this.sessionId}\n`;
        this.logContent += `Timestamp: ${new Date().toISOString()}\n\n`;
        this.logger.debug(`[PlaywrightLogger] Reset for new session: ${newSessionId}`);
    }
    getLogContent() {
        return this.logContent;
    }
}
/** Default viewport dimensions for consistent screenshots */
const DEFAULT_VIEWPORT = { width: 1280, height: 800 };
const PLAYWRIGHT_ACTIONS = [
    "navigate",
    "click",
    "fill",
    "screenshot",
    "getText",
    "waitForSelector",
    "evaluate",
    "getUrl",
    "reload",
    "getConsoleLogs",
    "scroll",
    "scrollIntoView",
    "checkRuntimeErrors",
    "getEmbedEvents",
    "triggerEmbedEvent",
    "setEmbedProperties",
];
let capturedConsoleLogs = [];
let lastDrainedEmbedEvents = null;
export async function startEmbeddedPlaywrightMcpServer(options) {
    const logger = options.logger;
    // Create action logger for capturing Playwright actions
    const sessionId = String(Date.now());
    const actionLogger = new PlaywrightActionLogger({
        sessionId,
        saveArtifact: options.saveArtifact,
        runTimestamp: options.runTimestamp,
        logger,
    });
    let browser;
    let context = null;
    let shouldCloseBrowser = true;
    if (options?.connectWsEndpoint) {
        browser = await chromium.connect({
            wsEndpoint: options.connectWsEndpoint,
        });
        shouldCloseBrowser = false;
    }
    else {
        browser = await launchBrowser();
    }
    // Reuse an existing context when connecting to an existing browser
    if (options?.connectWsEndpoint) {
        const existing = browser.contexts();
        if (existing.length > 0) {
            context = existing[0];
        }
    }
    if (!context) {
        const contextOptions = {
            viewport: DEFAULT_VIEWPORT,
        };
        if (options?.storageStateData) {
            contextOptions.storageState = options.storageStateData;
            // Duplicate auth cookies onto app host if needed (e.g., when original domain is auth0)
            if (options.appUrl && options.storageStateData.cookies?.length) {
                const appHost = new URL(options.appUrl).hostname;
                const duplicateCookies = options.storageStateData.cookies
                    .filter((c) => ["auth0", "auth0_compat", "did", "did_compat"].includes(c.name))
                    .map((c) => ({
                    ...c,
                    domain: appHost,
                    secure: false,
                    sameSite: "Lax",
                }));
                contextOptions.storageState.cookies = [
                    ...(contextOptions.storageState.cookies || []),
                    ...duplicateCookies,
                ];
            }
        }
        else if (options?.storageStatePath) {
            if (fs.existsSync(options.storageStatePath)) {
                contextOptions.storageState = options.storageStatePath;
            }
        }
        context = await browser.newContext(contextOptions);
    }
    // Ensure context exists
    if (!context) {
        throw new Error("Failed to create or reuse a Playwright context");
    }
    // Mock parent window for Playwright browser automation.
    //
    // NOTE: We always install this init script, even when init/bootstrap data is not available.
    // This ensures third-party embed hooks (embed-emit-event) can be exercised and asserted in tests.
    // When init/bootstrap data is provided, this script also simulates Superblocks editor messaging
    // (sb-init, sb-bootstrap-response, etc.) needed for first-party embedding flows.
    const initData = options.initData;
    const bootstrapData = options.bootstrapData;
    /**
     * CRITICAL: Mock Parent Window Setup for Playwright Browser Automation
     *
     * WHY THIS IS NECESSARY:
     * The Superblocks library code (in @superblocksteam/library) expects to run inside an iframe
     * embedded by the Superblocks editor. It uses `window.parent.postMessage()` to communicate
     * with the editor and relies on `window.parent !== window` to detect the embedded state.
     *
     * When running in Playwright for E2E testing, there is no parent iframe - the page runs
     * standalone. Without this mock, the library would:
     * 1. Fail the `isEmbeddedBySuperblocksFirstParty()` check
     * 2. Never receive the `sb-init` message needed to establish socket connections
     * 3. Never receive the `sb-bootstrap-response` with auth tokens and app data
     *
     * HOW IT WORKS:
     * 1. We override `window.parent` with a mock object BEFORE library code loads (via addInitScript)
     * 2. The mock intercepts `postMessage` calls from the library
     * 3. When the library sends "sb-ready", we respond with "sb-init" containing peerId/auth data
     * 4. When the library sends "sb-editor-request-bootstrap", we respond with bootstrap data
     * 5. We also send initial messages proactively in case the library sets up listeners late
     *
     * TESTING NOTE:
     * Integration tests for this setup should verify:
     * 1. The app successfully initializes and shows content
     * 2. API calls are authenticated (tokens were passed correctly)
     * 3. Real-time features work (socket connection established via peerId)
     */
    await context.addInitScript((payload) => {
        const { initData: init, bootstrapData: bootstrap } = payload;
        /**
         * Capture embed events emitted by apps via @superblocksteam/library:
         * useEmitEmbedEvent() -> embedStore.emitEvent() -> window.parent.postMessage({ type: "embed-emit-event" })
         *
         * We store them on the window so the MCP server can drain them and attach to test steps.
         */
        const EMBED_EVENTS_KEY = "__sb_embed_events__";
        window[EMBED_EVENTS_KEY] =
            window[EMBED_EVENTS_KEY] ?? [];
        window.__sbGetAndClearEmbedEvents = () => {
            const events = Array.isArray(window[EMBED_EVENTS_KEY])
                ? window[EMBED_EVENTS_KEY]
                : [];
            window[EMBED_EVENTS_KEY] = [];
            return events;
        };
        /**
         * Mock parent window object that intercepts postMessage calls from the library.
         * When the app sends messages expecting a parent response, we handle them here.
         */
        const mockParent = {
            postMessage: (message, _targetOrigin) => {
                // Record third-party embed events emitted by the app.
                if (message?.type === "embed-emit-event") {
                    window[EMBED_EVENTS_KEY].push({
                        timestamp: Date.now(),
                        eventName: message?.payload?.eventName,
                        payload: message?.payload?.payload,
                    });
                }
                // When the app sends sb-ready, we respond with sb-init
                // This triggers the library to establish its socket connection
                if (message?.type === "sb-ready" && init) {
                    setTimeout(() => {
                        window.postMessage({
                            type: "sb-init",
                            payload: {
                                peerId: init.peerId,
                                userId: init.userId,
                                devServerAuthorization: init.devServerAuthorization,
                                appId: init.appId,
                                windowOriginUrl: init.windowOriginUrl,
                            },
                            startTime: Date.now(),
                        }, "*");
                    }, 10);
                }
                // When the app sends sb-editor-request-bootstrap, we respond with bootstrap data
                // This provides auth tokens and configuration needed by the API manager
                if (message?.type === "sb-editor-request-bootstrap" && bootstrap) {
                    setTimeout(() => {
                        window.postMessage({
                            type: "sb-bootstrap-response",
                            payload: bootstrap,
                            startTime: Date.now(),
                        }, "*");
                    }, 10);
                }
                // When the app sends authenticate-api-request, we respond with resolve-promise
                // This allows API calls to proceed in test mode without actual auth
                if (message?.type === "authenticate-api-request") {
                    const { callbackId } = message.payload || {};
                    if (callbackId) {
                        setTimeout(() => {
                            window.postMessage({
                                type: "resolve-promise",
                                callbackId,
                                payload: {}, // Empty success result - no auth errors
                            }, "*");
                        }, 10);
                    }
                }
            },
            // These properties make the mock look like a real window object
            // which helps pass any instanceof or property existence checks
            window: window,
            document: document,
            location: window.location,
        };
        // Override window.parent to point to our mock
        // This must happen before the library code runs (hence addInitScript)
        Object.defineProperty(window, "parent", {
            value: mockParent,
            writable: false,
            configurable: true,
        });
        // Send initial messages proactively after a delay
        // This handles the case where the library sets up listeners after checking window.parent
        if (init) {
            setTimeout(() => {
                window.postMessage({
                    type: "sb-init",
                    payload: {
                        peerId: init.peerId,
                        userId: init.userId,
                        devServerAuthorization: init.devServerAuthorization,
                        appId: init.appId,
                        windowOriginUrl: init.windowOriginUrl,
                    },
                    startTime: Date.now(),
                }, "*");
            }, 100);
        }
        if (bootstrap) {
            setTimeout(() => {
                window.postMessage({
                    type: "sb-bootstrap-response",
                    payload: bootstrap,
                    startTime: Date.now(),
                }, "*");
            }, 200);
            // Send sb-global-sync with profiles data for API execution
            // This sets superblocksContext.profiles which is needed for profileId
            if (bootstrap.profiles) {
                setTimeout(() => {
                    window.postMessage({
                        type: "sb-global-sync",
                        payload: {
                            global: {
                                profiles: bootstrap.profiles,
                            },
                        },
                        startTime: Date.now(),
                    }, "*");
                }, 300);
            }
        }
    }, {
        initData,
        bootstrapData,
    });
    // Seed a cookie/localStorage with JWT for app domain if provided
    if (options?.jwt && options?.appUrl) {
        try {
            const appUrl = new URL(options.appUrl);
            const authDomains = new Set([
                appUrl.hostname,
                "login-dev.superblocks.com",
                "login.superblocks.com",
            ]);
            await context.addInitScript(({ token, domains }) => {
                try {
                    localStorage.setItem("sb_jwt", token);
                    sessionStorage.setItem("sb_jwt", token);
                    localStorage.setItem("access_token", token);
                    sessionStorage.setItem("access_token", token);
                }
                catch {
                    // ignore storage errors
                }
                try {
                    domains.forEach((domain) => {
                        document.cookie = `x-superblocks-authorization=Bearer ${token}; Path=/; SameSite=None; ${domain === "localhost" ? "" : "Secure;"} Domain=${domain}`;
                        document.cookie = `sb_jwt=${token}; Path=/; SameSite=None; ${domain === "localhost" ? "" : "Secure;"} Domain=${domain}`;
                    });
                }
                catch {
                    // ignore cookie errors
                }
            }, { token: options.jwt, domains: Array.from(authDomains) });
            const cookieBase = {
                value: `Bearer ${options.jwt}`,
                path: "/",
                httpOnly: false,
                sameSite: "None",
            };
            const cookies = [
                {
                    ...cookieBase,
                    name: "x-superblocks-authorization",
                    domain: appUrl.hostname,
                    secure: appUrl.protocol === "https:",
                },
                {
                    ...cookieBase,
                    name: "sb_jwt",
                    value: options.jwt,
                    domain: appUrl.hostname,
                    secure: appUrl.protocol === "https:",
                },
            ];
            authDomains.forEach((domain) => {
                if (domain !== appUrl.hostname) {
                    cookies.push({
                        ...cookieBase,
                        name: "x-superblocks-authorization",
                        domain,
                        secure: true,
                    });
                    cookies.push({
                        ...cookieBase,
                        name: "sb_jwt",
                        value: options.jwt,
                        domain,
                        secure: true,
                    });
                }
            });
            await context.addCookies(cookies);
        }
        catch (error) {
            logger.error(`[MCP-Server] ❌ Failed to seed JWT cookie/localStorage: ${String(error)}`, {
                error: {
                    kind: "McpServerSeedJwtError",
                    message: String(error),
                    stack: error instanceof Error ? error.stack : undefined,
                },
            });
        }
    }
    // Seed session storage for app origin if provided
    if (options?.sessionStorageData?.items?.length) {
        const { origin, items } = options.sessionStorageData;
        try {
            await context.addInitScript(({ targetOrigin, entries, }) => {
                if (location.origin === targetOrigin) {
                    try {
                        entries.forEach(({ name, value }) => {
                            sessionStorage.setItem(name, value);
                        });
                    }
                    catch {
                        // ignore
                    }
                }
            }, { targetOrigin: origin, entries: items });
        }
        catch {
            // ignore session storage seeding errors
        }
    }
    // Seed additional origins storage (local/session) if provided
    if (options?.extraOrigins?.length) {
        for (const originData of options.extraOrigins) {
            const { origin, localStorage = [], sessionStorage = [] } = originData;
            const seedLocal = localStorage;
            const seedSession = sessionStorage;
            if (seedLocal.length > 0 || seedSession.length > 0) {
                try {
                    await context.addInitScript(({ targetOrigin, lsEntries, ssEntries, }) => {
                        if (location.origin === targetOrigin) {
                            lsEntries.forEach(({ name, value }) => {
                                try {
                                    globalThis.localStorage?.setItem(name, value);
                                }
                                catch {
                                    /* ignore */
                                }
                            });
                            ssEntries.forEach(({ name, value }) => {
                                try {
                                    globalThis.sessionStorage?.setItem(name, value);
                                }
                                catch {
                                    /* ignore */
                                }
                            });
                        }
                    }, {
                        targetOrigin: origin,
                        lsEntries: seedLocal,
                        ssEntries: seedSession,
                    });
                }
                catch {
                    // ignore extra origin storage seeding errors
                }
            }
        }
    }
    const page = await context.newPage();
    // Prefer an existing page in the reused context (e.g., the CLI-authenticated one)
    const existingPages = context.pages();
    const activePage = existingPages.length > 0 ? existingPages[0] : page;
    // Set up console log capture for the page
    capturedConsoleLogs = []; // Reset logs for new session
    activePage.on("console", (msg) => {
        capturedConsoleLogs.push({
            type: msg.type(),
            text: msg.text(),
            timestamp: Date.now(),
        });
    });
    // Auto-navigate to the app URL if provided, so the agent doesn't need to navigate manually
    if (options?.appUrl) {
        try {
            await activePage.goto(options.appUrl, {
                waitUntil: "load",
                timeout: 60000,
            });
        }
        catch (error) {
            // Don't throw - let the agent handle navigation if auto-nav fails
            // Log the error for debugging purposes
            logger.warn(`[MCP-Server] Auto-navigation to ${options.appUrl} failed: ${String(error)}`, {
                error: {
                    kind: "McpServerAutoNavError",
                    message: String(error),
                    stack: error instanceof Error ? error.stack : undefined,
                },
            });
        }
    }
    const server = http.createServer((req, res) => {
        void (async () => {
            try {
                if (req.method !== "POST") {
                    res.writeHead(405, { "Content-Type": "application/json" });
                    res.end(JSON.stringify({
                        jsonrpc: "2.0",
                        id: null,
                        error: {
                            code: -32600,
                            message: "Only POST requests are supported",
                        },
                    }));
                    return;
                }
                const body = await readRequestBody(req);
                let request;
                try {
                    request = JSON.parse(body);
                }
                catch (error) {
                    res.writeHead(400, { "Content-Type": "application/json" });
                    res.end(JSON.stringify({
                        jsonrpc: "2.0",
                        id: null,
                        error: {
                            code: -32700,
                            message: `Invalid JSON: ${String(error)}`,
                        },
                    }));
                    return;
                }
                const response = await handleJsonRpcRequest(request, activePage, actionLogger);
                res.writeHead(200, { "Content-Type": "application/json" });
                res.end(JSON.stringify(response));
            }
            catch (error) {
                res.writeHead(500, { "Content-Type": "application/json" });
                res.end(JSON.stringify({
                    jsonrpc: "2.0",
                    id: null,
                    error: {
                        code: -32603,
                        message: `Server error: ${String(error)}`,
                    },
                }));
            }
        })();
    });
    server.listen(0, "127.0.0.1");
    await once(server, "listening");
    const address = server.address();
    const url = `http://${address.address}:${address.port}/mcp`;
    return {
        url,
        close: async () => {
            // Save the action log before closing
            await actionLogger.save();
            await Promise.allSettled([
                new Promise((resolve, reject) => {
                    server.close((error) => {
                        if (error) {
                            reject(error);
                        }
                        else {
                            resolve();
                        }
                    });
                }),
                page.close().catch(() => { }),
                context.close().catch(() => { }),
                shouldCloseBrowser
                    ? browser.close().catch(() => { })
                    : Promise.resolve(),
            ]);
        },
        flushLog: async () => {
            await actionLogger.saveAndReset();
        },
        captureInteractiveElements: async () => {
            try {
                // Wait for page to be ready
                await page.waitForLoadState("domcontentloaded");
                const elements = await page.evaluate(() => {
                    const selector = 'button, a, input, select, textarea, [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="switch"], [role="tab"], [role="menuitem"], [onclick], [data-testid]';
                    const nodeList = document.querySelectorAll(selector);
                    return Array.from(nodeList)
                        .map((el) => {
                        const text = el.textContent?.trim().slice(0, 100);
                        const id = el.id || undefined;
                        const className = el.className || undefined;
                        const testId = el.getAttribute("data-testid") || undefined;
                        const placeholder = el.getAttribute("placeholder") || undefined;
                        const href = el.getAttribute("href") || undefined;
                        const type = el.getAttribute("type") || undefined;
                        const role = el.getAttribute("role") || undefined;
                        const ariaLabel = el.getAttribute("aria-label") || undefined;
                        const name = el.getAttribute("name") || undefined;
                        const disabled = el.hasAttribute("disabled") ||
                            el.getAttribute("aria-disabled") === "true";
                        return {
                            tag: el.tagName.toLowerCase(),
                            text: text || undefined,
                            id,
                            className: typeof className === "string" ? className : undefined,
                            testId,
                            placeholder,
                            href,
                            type,
                            role,
                            ariaLabel,
                            name,
                            disabled: disabled || undefined,
                        };
                    })
                        .filter((el) => {
                        // Filter out elements with no useful selector info
                        return (el.text ||
                            el.id ||
                            el.testId ||
                            el.placeholder ||
                            el.ariaLabel ||
                            el.name);
                    });
                });
                return elements;
            }
            catch (error) {
                logger.warn(`Failed to capture interactive elements: ${String(error)}`);
                return [];
            }
        },
    };
}
async function launchBrowser() {
    const browserName = (process.env.PLAYWRIGHT_MCP_BROWSER ??
        "chromium");
    const headless = process.env.HEADLESS !== "false";
    switch (browserName) {
        case "chromium":
            return chromium.launch({ headless });
        case "firefox":
            return firefox.launch({ headless });
        case "webkit":
            return webkit.launch({ headless });
        default:
            throw new Error(`Unsupported PLAYWRIGHT_MCP_BROWSER: ${browserName}`);
    }
}
async function readRequestBody(req) {
    const chunks = [];
    for await (const chunk of req) {
        chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
    }
    return Buffer.concat(chunks).toString("utf-8");
}
async function handleJsonRpcRequest(request, page, actionLogger) {
    const { method, id } = request;
    try {
        if (method === "tools/list") {
            return {
                jsonrpc: "2.0",
                id,
                result: {
                    tools: [
                        {
                            name: "playwright_action",
                            description: `Execute Playwright browser automation actions. The browser is already navigated to the app.

ACTIONS:
- screenshot: Take a screenshot. Params: { fullPage?: boolean }
- click: Click an element. Params: { selector: string }
- fill: Type into an input. Params: { selector: string, value: string }
- getText: Get text content. Params: { selector: string }
- waitForSelector: Wait for element. Params: { selector: string }
- evaluate: Run JavaScript. Params: { script: string }
- getUrl: Get current URL. No params.
- reload: Reload the page. No params.
- navigate: Go to URL. Params: { url: string }
- getConsoleLogs: Get browser console logs. Params: { clear?: boolean }
- getEmbedEvents: Get the most recent embed events emitted by the app. Params: { }
- triggerEmbedEvent: Trigger an incoming embed event as if sent by the embedder. Params: { eventName: string, payload?: object }
- setEmbedProperties: Set embed properties as if sent by the embedder. Params: { properties: object }
- scroll: Scroll the page. Params: { x?: number, y?: number, deltaX?: number, deltaY?: number }
- scrollIntoView: Scroll element into view. Params: { selector: string }
- checkRuntimeErrors: Explicitly check for component crashes. No params.

RUNTIME ERROR DETECTION:
Actions (navigate, click, fill, evaluate) automatically detect component crashes.
When a component crashes, the action returns:
- success: false
- runtimeErrors: Array of { header: string, stack?: string }
- error: Summary message

If you see runtimeErrors in a response, the test should FAIL. Include the error details in your summary.

SELECTOR EXAMPLES:
- By testid: [data-testid="login-form"]
- By name: input[name="username"], textarea[name="bio"]
- By placeholder: input[placeholder="Enter name"]
- By type: input[type="email"], button[type="submit"]  
- By text content: button:has-text("Submit"), a:has-text("Click here")
- By ID: #submitBtn, #loginForm
- By class: .btn-primary, .form-input
- Combining: form#login input[name="password"]

Any selector that playwright supports can be used.

CSS SELECTOR SYNTAX (IMPORTANT):
✓ CORRECT: input[name="email"], button[type="submit"], #myId, .myClass
✗ WRONG: input [name="email"] (no space before bracket!)
✗ WRONG: input[name=email] (quotes required around value)

TIPS:
1. Use specific selectors: prefer [name="x"] or [placeholder="x"] over generic .class
2. If click/fill fails, the element may not be visible - try waitForSelector first
3. For dynamic content, use waitForSelector before interacting
4. Navigate, click, and fill will automatically take a screenshot of the page after the action is performed
5. If success is false due to runtimeErrors, FAIL the test and report the error details`,
                            inputSchema: {
                                type: "object",
                                properties: {
                                    action: {
                                        type: "string",
                                        enum: PLAYWRIGHT_ACTIONS,
                                        description: "The action to perform. Most actions require a 'selector' parameter.",
                                    },
                                    description: {
                                        type: "string",
                                        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.",
                                    },
                                    selector: {
                                        type: "string",
                                        description: 'CSS selector for the target element. NO SPACE before brackets! Example: input[name="email"] NOT input [name="email"]',
                                    },
                                    value: {
                                        type: "string",
                                        description: "Value to fill (for 'fill' action)",
                                    },
                                    url: {
                                        type: "string",
                                        description: "URL to navigate to (for 'navigate' action)",
                                    },
                                    script: {
                                        type: "string",
                                        description: "JavaScript to execute in browser (for 'evaluate' action). No imports allowed.",
                                    },
                                    fullPage: {
                                        type: "boolean",
                                        description: "Capture full scrollable page (for 'screenshot' action)",
                                    },
                                    clear: {
                                        type: "boolean",
                                        description: "Clear logs after retrieving (for 'getConsoleLogs' action)",
                                    },
                                    eventName: {
                                        type: "string",
                                        description: "Embed event name to trigger (for 'triggerEmbedEvent' action)",
                                    },
                                    payload: {
                                        type: "object",
                                        description: "Optional payload to send with the embed event (for 'triggerEmbedEvent' action)",
                                        additionalProperties: true,
                                    },
                                    properties: {
                                        type: "object",
                                        description: "Embed properties to set (for 'setEmbedProperties' action)",
                                        additionalProperties: true,
                                    },
                                    x: {
                                        type: "number",
                                        description: "Absolute horizontal scroll position in pixels (for 'scroll' action)",
                                    },
                                    y: {
                                        type: "number",
                                        description: "Absolute vertical scroll position in pixels (for 'scroll' action)",
                                    },
                                    deltaX: {
                                        type: "number",
                                        description: "Relative horizontal scroll amount in pixels (for 'scroll' action)",
                                    },
                                    deltaY: {
                                        type: "number",
                                        description: "Relative vertical scroll amount in pixels (for 'scroll' action)",
                                    },
                                    testCaseId: {
                                        type: "string",
                                        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.",
                                    },
                                },
                                required: ["action", "description"],
                            },
                        },
                    ],
                },
            };
        }
        if (method === "tools/call") {
            const params = request.params;
            if (!params || params.name !== "playwright_action") {
                throw new Error(`Unknown tool: ${params?.name ?? "undefined"}`);
            }
            const action = params.arguments?.action;
            if (!action || !PLAYWRIGHT_ACTIONS.includes(action)) {
                throw new Error(`Unsupported Playwright action: ${String(action)}`);
            }
            // Log the action start
            actionLogger.logActionStart(action, params.arguments ?? {});
            const result = await executePlaywrightAction(action, page, params.arguments ?? {});
            // Log the action result
            actionLogger.logActionResult(result);
            // Include testCaseId in the result if provided
            const testCaseId = params.arguments?.testCaseId;
            if (testCaseId) {
                result.testCaseId = testCaseId;
            }
            return {
                jsonrpc: "2.0",
                id,
                result: {
                    content: result,
                },
            };
        }
        if (method === "disconnect") {
            return {
                jsonrpc: "2.0",
                id,
                result: {},
            };
        }
        throw new Error(`Unsupported method: ${method}`);
    }
    catch (error) {
        return {
            jsonrpc: "2.0",
            id,
            error: {
                code: -32603,
                message: String(error),
            },
        };
    }
}
/**
 * Checks for runtime errors displayed by ErrorBoundary components.
 * These are detected by looking for elements with data-testid="runtime-error-details".
 *
 * @param page - The Playwright page to check
 * @returns Object with hasErrors flag and array of error details
 */
async function checkForRuntimeErrors(page) {
    try {
        const errorElements = await page
            .locator('[data-testid="runtime-error-details"]')
            .all();
        if (errorElements.length === 0) {
            return { hasErrors: false, errors: [] };
        }
        const errors = [];
        for (const element of errorElements) {
            try {
                // Get the error header (h3 contains "Something went wrong." or custom header)
                const header = (await element.locator("h3").textContent()) || "Component error";
                // Try to get the stack trace from the pre > code element inside details
                let stack;
                try {
                    const codeElement = element.locator("pre code");
                    const codeText = await codeElement.textContent({ timeout: 1000 });
                    stack = codeText || undefined;
                }
                catch {
                    // Stack not available or details not expanded
                }
                errors.push({ header, stack });
            }
            catch {
                errors.push({ header: "Unknown component error" });
            }
        }
        return { hasErrors: true, errors };
    }
    catch {
        return { hasErrors: false, errors: [] };
    }
}
/**
 * Captures a screenshot in PNG format.
 *
 * TODO: Re-enable WebP conversion using sharp once the build issue is resolved.
 * WebP provides ~25-34% smaller file sizes compared to JPEG at equivalent
 * visual quality, with better compression than PNG.
 *
 * @param page - The Playwright page to capture
 * @returns Screenshot data as base64 string with format identifier
 */
async function captureScreenshot(page) {
    try {
        await page.evaluate(() => document.fonts.ready);
        await page.waitForTimeout(100);
    }
    catch {
        // Continue anyway if fonts.ready fails
    }
    const pngBuffer = await page.screenshot({
        fullPage: false,
        scale: "css",
        type: "png",
    });
    // TODO: Re-enable WebP conversion once sharp build issue is resolved
    // const webpBuffer = await sharp(pngBuffer).webp({ quality: 80 }).toBuffer();
    // return { data: webpBuffer.toString("base64"), format: "webp" };
    return {
        data: pngBuffer.toString("base64"),
        format: "png",
    };
}
async function drainEmbedEvents(page) {
    try {
        const events = await page.evaluate(() => {
            return window.__sbGetAndClearEmbedEvents?.() ?? null;
        });
        if (Array.isArray(events) && events.length > 0) {
            lastDrainedEmbedEvents = events;
            return events;
        }
        return null;
    }
    catch {
        return null;
    }
}
async function executePlaywrightAction(action, page, params) {
    switch (action) {
        case "navigate": {
            const url = params.url;
            const description = params.description || "Navigate to page";
            if (!url)
                throw new Error("Missing url parameter for navigate action");
            try {
                await page.goto(url, {
                    waitUntil: "load",
                    timeout: 60000, // 60 second timeout
                });
                // Auto-capture screenshot after navigation
                const screenshot = await captureScreenshot(page);
                const embedEvents = await drainEmbedEvents(page);
                // Check for runtime errors (component crashes)
                const runtimeErrors = await checkForRuntimeErrors(page);
                return {
                    success: !runtimeErrors.hasErrors,
                    action: "navigate",
                    description,
                    screenshot: screenshot.data,
                    format: screenshot.format,
                    context: {
                        url,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    ...(runtimeErrors.hasErrors && {
                        runtimeErrors: runtimeErrors.errors,
                        error: `Component crashed with ${runtimeErrors.errors.length} runtime error(s): ${runtimeErrors.errors.map((e) => e.header).join(", ")}`,
                    }),
                };
            }
            catch (error) {
                console.error(`🎬 MCP Server: Navigation failed: ${String(error)}`);
                // Capture screenshot even on error
                let errorScreenshot = null;
                try {
                    errorScreenshot = await captureScreenshot(page);
                }
                catch {
                    // Ignore screenshot errors
                }
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: false,
                    action: "navigate",
                    description,
                    screenshot: errorScreenshot?.data,
                    format: errorScreenshot?.format ?? "png",
                    context: {
                        url,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    error: `Navigation to ${url} failed: ${String(error)}`,
                };
            }
        }
        case "click": {
            const selector = params.selector;
            const description = params.description || "Click element";
            if (!selector)
                throw new Error("Missing selector for click action");
            try {
                await page
                    .locator(selector)
                    .click({ timeout: params.timeout ?? 10000 });
                // Auto-capture screenshot after action
                const screenshot = await captureScreenshot(page);
                const embedEvents = await drainEmbedEvents(page);
                // Check for runtime errors (component crashes)
                const runtimeErrors = await checkForRuntimeErrors(page);
                return {
                    success: !runtimeErrors.hasErrors,
                    action: "click",
                    description,
                    screenshot: screenshot.data,
                    format: screenshot.format,
                    context: {
                        selector,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    ...(runtimeErrors.hasErrors && {
                        runtimeErrors: runtimeErrors.errors,
                        error: `Component crashed with ${runtimeErrors.errors.length} runtime error(s): ${runtimeErrors.errors.map((e) => e.header).join(", ")}`,
                    }),
                };
            }
            catch (error) {
                // Capture screenshot even on error
                let errorScreenshot = null;
                try {
                    errorScreenshot = await captureScreenshot(page);
                }
                catch {
                    // Ignore screenshot errors
                }
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: false,
                    action: "click",
                    description,
                    screenshot: errorScreenshot?.data,
                    format: errorScreenshot?.format ?? "png",
                    context: {
                        selector,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    error: `Click failed on "${selector}": ${String(error)}`,
                };
            }
        }
        case "fill": {
            const selector = params.selector;
            const value = params.value ?? "";
            const description = params.description || "Fill input";
            if (!selector)
                throw new Error("Missing selector for fill action");
            try {
                await page
                    .locator(selector)
                    .fill(value, { timeout: params.timeout ?? 10000 });
                // Auto-capture screenshot after fill
                const screenshot = await captureScreenshot(page);
                const embedEvents = await drainEmbedEvents(page);
                // Check for runtime errors (component crashes)
                const runtimeErrors = await checkForRuntimeErrors(page);
                return {
                    success: !runtimeErrors.hasErrors,
                    action: "fill",
                    description,
                    screenshot: screenshot.data,
                    format: screenshot.format,
                    context: {
                        selector,
                        value,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    ...(runtimeErrors.hasErrors && {
                        runtimeErrors: runtimeErrors.errors,
                        error: `Component crashed with ${runtimeErrors.errors.length} runtime error(s): ${runtimeErrors.errors.map((e) => e.header).join(", ")}`,
                    }),
                };
            }
            catch (error) {
                // Capture screenshot even on error
                let errorScreenshot = null;
                try {
                    errorScreenshot = await captureScreenshot(page);
                }
                catch {
                    // Ignore screenshot errors
                }
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: false,
                    action: "fill",
                    description,
                    screenshot: errorScreenshot?.data,
                    format: errorScreenshot?.format ?? "png",
                    context: {
                        selector,
                        value,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    error: `Fill failed on "${selector}": ${String(error)}`,
                };
            }
        }
        case "screenshot": {
            const description = params.description || "Capture current state";
            // Capture screenshot in WebP format for optimal compression
            const screenshot = await captureScreenshot(page);
            const embedEvents = await drainEmbedEvents(page);
            return {
                success: true,
                action: "screenshot",
                description,
                screenshot: screenshot.data,
                format: screenshot.format,
                context: {
                    fullPage: Boolean(params.fullPage),
                    ...(embedEvents ? { embedEvents } : {}),
                },
            };
        }
        case "getText": {
            const selector = params.selector;
            if (!selector)
                throw new Error("Missing selector for getText action");
            try {
                const text = await page.locator(selector).textContent({
                    timeout: params.timeout ?? 10000,
                });
                return { success: true, text };
            }
            catch (error) {
                return {
                    success: false,
                    error: `getText failed on "${selector}": ${String(error)}`,
                };
            }
        }
        case "waitForSelector": {
            const selector = params.selector;
            if (!selector) {
                throw new Error("Missing selector for waitForSelector action");
            }
            try {
                await page.locator(selector).waitFor({
                    timeout: params.timeout ?? 15000,
                });
                return { success: true };
            }
            catch (error) {
                return {
                    success: false,
                    error: `waitForSelector failed on "${selector}": ${String(error)}`,
                };
            }
        }
        case "evaluate": {
            const script = params.script;
            const description = params.description || "Execute JavaScript";
            if (typeof script !== "string") {
                throw new Error("Missing script for evaluate action");
            }
            // Check for import/export/require statements that can't be used in browser context
            // Use regex with word boundaries to avoid false positives on strings like "important"
            const hasImport = /\bimport\s+/.test(script);
            const hasExport = /\bexport\s+/.test(script);
            const hasRequire = /\brequire\s*\(/.test(script);
            if (hasImport || hasExport || hasRequire) {
                throw new Error("Cannot use import/export/require statements in browser evaluate context. " +
                    "Please use plain JavaScript that can run in a browser console. " +
                    "For example, use 'document.querySelector' instead of importing libraries.");
            }
            try {
                // Wrap the script in a function to ensure it's evaluated as a function body
                // This prevents Node.js from trying to parse it as a module
                const wrappedScript = `(() => {
          try {
            ${script}
          } catch (e) {
            return { error: e.toString(), stack: e.stack };
          }
        })()`;
                const result = await page.evaluate(wrappedScript);
                // Check if the evaluation returned an error
                if (result && typeof result === "object" && "error" in result) {
                    throw new Error(`Browser evaluation failed: ${result.error}`);
                }
                // Auto-capture screenshot after evaluation
                const screenshot = await captureScreenshot(page);
                const embedEvents = await drainEmbedEvents(page);
                // Check for runtime errors (component crashes)
                const runtimeErrors = await checkForRuntimeErrors(page);
                return {
                    success: !runtimeErrors.hasErrors,
                    action: "evaluate",
                    description,
                    result,
                    screenshot: screenshot.data,
                    format: screenshot.format,
                    context: {
                        script: script.substring(0, 100) + (script.length > 100 ? "..." : ""),
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    ...(runtimeErrors.hasErrors && {
                        runtimeErrors: runtimeErrors.errors,
                        error: `Component crashed with ${runtimeErrors.errors.length} runtime error(s): ${runtimeErrors.errors.map((e) => e.header).join(", ")}`,
                    }),
                };
            }
            catch (error) {
                // Capture screenshot even on error
                let errorScreenshot = null;
                try {
                    errorScreenshot = await captureScreenshot(page);
                }
                catch {
                    // Ignore screenshot errors
                }
                const embedEvents = await drainEmbedEvents(page);
                // If the error looks like a syntax error, provide helpful guidance
                const errorMsg = String(error);
                if (errorMsg.includes("SyntaxError") ||
                    errorMsg.includes("import") ||
                    errorMsg.includes("module")) {
                    return {
                        success: false,
                        action: "evaluate",
                        description,
                        screenshot: errorScreenshot?.data,
                        format: errorScreenshot?.format ?? "png",
                        context: {
                            script: script.substring(0, 100) + (script.length > 100 ? "..." : ""),
                            ...(embedEvents ? { embedEvents } : {}),
                        },
                        error: "JavaScript evaluation failed. Make sure your code is browser-compatible " +
                            "(no import/export statements). Error: " +
                            errorMsg,
                    };
                }
                return {
                    success: false,
                    action: "evaluate",
                    description,
                    screenshot: errorScreenshot?.data,
                    format: errorScreenshot?.format ?? "png",
                    context: {
                        script: script.substring(0, 100) + (script.length > 100 ? "..." : ""),
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    error: String(error),
                };
            }
        }
        case "getUrl": {
            return { success: true, url: page.url() };
        }
        case "reload": {
            await page.reload({
                waitUntil: params.waitUntil ?? "load",
                timeout: params.timeout,
            });
            return { success: true };
        }
        case "getConsoleLogs": {
            // Return captured console logs and optionally clear them
            const logs = [...capturedConsoleLogs];
            if (params.clear) {
                capturedConsoleLogs = [];
            }
            return {
                success: true,
                logs,
                count: logs.length,
            };
        }
        case "getEmbedEvents": {
            const description = params.description || "Get embed events";
            const events = lastDrainedEmbedEvents ?? (await drainEmbedEvents(page)) ?? [];
            lastDrainedEmbedEvents = null;
            const screenshot = await captureScreenshot(page);
            // Check for runtime errors (component crashes)
            const runtimeErrors = await checkForRuntimeErrors(page);
            return {
                success: !runtimeErrors.hasErrors,
                action: "getEmbedEvents",
                description,
                screenshot: screenshot.data,
                format: screenshot.format,
                events,
                count: events.length,
                ...(runtimeErrors.hasErrors && {
                    runtimeErrors: runtimeErrors.errors,
                    error: `Component crashed with ${runtimeErrors.errors.length} runtime error(s): ${runtimeErrors.errors.map((e) => e.header).join(", ")}`,
                }),
            };
        }
        case "triggerEmbedEvent": {
            const description = params.description || "Trigger embed event";
            const eventName = params.eventName;
            const payload = params.payload ?? {};
            if (typeof eventName !== "string" || !eventName.trim()) {
                throw new Error("Missing eventName for triggerEmbedEvent action");
            }
            try {
                // Simulate the embedder sending a message into the iframe.
                // The library converts this to a CustomEvent `superblocks:${eventName}`,
                // which is what `useEmbedEvent()` listens to.
                await page.evaluate(({ eventName, payload }) => {
                    window.postMessage({
                        type: "embed-trigger-event",
                        payload: { eventName, payload },
                        startTime: Date.now(),
                    }, "*");
                }, { eventName, payload });
                await page.waitForTimeout(50);
                const screenshot = await captureScreenshot(page);
                const embedEvents = await drainEmbedEvents(page);
                // Check for runtime errors (component crashes)
                const runtimeErrors = await checkForRuntimeErrors(page);
                return {
                    success: !runtimeErrors.hasErrors,
                    action: "triggerEmbedEvent",
                    description,
                    screenshot: screenshot.data,
                    format: screenshot.format,
                    context: {
                        eventName,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    ...(runtimeErrors.hasErrors && {
                        runtimeErrors: runtimeErrors.errors,
                        error: `Component crashed with ${runtimeErrors.errors.length} runtime error(s): ${runtimeErrors.errors.map((e) => e.header).join(", ")}`,
                    }),
                };
            }
            catch (error) {
                let errorScreenshot = null;
                try {
                    errorScreenshot = await captureScreenshot(page);
                }
                catch {
                    // Ignore screenshot errors
                }
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: false,
                    action: "triggerEmbedEvent",
                    description,
                    screenshot: errorScreenshot?.data,
                    format: errorScreenshot?.format ?? "png",
                    context: {
                        eventName,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    error: `triggerEmbedEvent failed for "${eventName}": ${String(error)}`,
                };
            }
        }
        case "setEmbedProperties": {
            const description = params.description || "Set embed properties";
            const properties = params.properties;
            if (!properties || typeof properties !== "object") {
                throw new Error("Missing properties for setEmbedProperties action");
            }
            try {
                // Simulate the embedder sending embed properties into the iframe.
                // The library listens for "embed-property-change" and updates embedStore.properties,
                // which is what `useEmbedProperties()` reads.
                await page.evaluate(({ properties }) => {
                    window.postMessage({
                        type: "embed-property-change",
                        payload: { properties },
                        startTime: Date.now(),
                    }, "*");
                }, { properties });
                await page.waitForTimeout(50);
                const screenshot = await captureScreenshot(page);
                const embedEvents = await drainEmbedEvents(page);
                // Check for runtime errors (component crashes)
                const runtimeErrors = await checkForRuntimeErrors(page);
                return {
                    success: !runtimeErrors.hasErrors,
                    action: "setEmbedProperties",
                    description,
                    screenshot: screenshot.data,
                    format: screenshot.format,
                    context: {
                        properties,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    ...(runtimeErrors.hasErrors && {
                        runtimeErrors: runtimeErrors.errors,
                        error: `Component crashed with ${runtimeErrors.errors.length} runtime error(s): ${runtimeErrors.errors.map((e) => e.header).join(", ")}`,
                    }),
                };
            }
            catch (error) {
                let errorScreenshot = null;
                try {
                    errorScreenshot = await captureScreenshot(page);
                }
                catch {
                    // Ignore screenshot errors
                }
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: false,
                    action: "setEmbedProperties",
                    description,
                    screenshot: errorScreenshot?.data,
                    format: errorScreenshot?.format ?? "png",
                    context: {
                        properties,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    error: `setEmbedProperties failed: ${String(error)}`,
                };
            }
        }
        case "scroll": {
            const { x, y, deltaX, deltaY } = params;
            const description = params.description || "Scroll page";
            try {
                await page.evaluate(({ x, y, deltaX, deltaY }) => {
                    if (deltaX !== undefined || deltaY !== undefined) {
                        window.scrollBy(deltaX ?? 0, deltaY ?? 0);
                    }
                    else if (x !== undefined || y !== undefined) {
                        window.scrollTo(x ?? window.scrollX, y ?? window.scrollY);
                    }
                }, { x, y, deltaX, deltaY });
                const screenshot = await captureScreenshot(page);
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: true,
                    action: "scroll",
                    description,
                    screenshot: screenshot.data,
                    format: screenshot.format,
                    context: {
                        x,
                        y,
                        deltaX,
                        deltaY,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                };
            }
            catch (error) {
                let errorScreenshot = null;
                try {
                    errorScreenshot = await captureScreenshot(page);
                }
                catch {
                    // Ignore screenshot errors
                }
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: false,
                    action: "scroll",
                    description,
                    screenshot: errorScreenshot?.data,
                    format: errorScreenshot?.format ?? "png",
                    context: {
                        x,
                        y,
                        deltaX,
                        deltaY,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    error: `Scroll failed: ${String(error)}`,
                };
            }
        }
        case "scrollIntoView": {
            const selector = params.selector;
            const description = params.description || "Scroll element into view";
            if (!selector) {
                throw new Error("Missing selector for scrollIntoView action");
            }
            try {
                await page.locator(selector).scrollIntoViewIfNeeded({
                    timeout: params.timeout ?? 10000,
                });
                const screenshot = await captureScreenshot(page);
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: true,
                    action: "scrollIntoView",
                    description,
                    screenshot: screenshot.data,
                    format: screenshot.format,
                    context: {
                        selector,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                };
            }
            catch (error) {
                let errorScreenshot = null;
                try {
                    errorScreenshot = await captureScreenshot(page);
                }
                catch {
                    // Ignore screenshot errors
                }
                const embedEvents = await drainEmbedEvents(page);
                return {
                    success: false,
                    action: "scrollIntoView",
                    description,
                    screenshot: errorScreenshot?.data,
                    format: errorScreenshot?.format ?? "png",
                    context: {
                        selector,
                        ...(embedEvents ? { embedEvents } : {}),
                    },
                    error: `scrollIntoView failed on "${selector}": ${String(error)}`,
                };
            }
        }
        case "checkRuntimeErrors": {
            const description = params.description || "Check for component runtime errors";
            const screenshot = await captureScreenshot(page);
            const runtimeErrors = await checkForRuntimeErrors(page);
            return {
                success: !runtimeErrors.hasErrors,
                action: "checkRuntimeErrors",
                description,
                screenshot: screenshot.data,
                format: screenshot.format,
                hasErrors: runtimeErrors.hasErrors,
                errorCount: runtimeErrors.errors.length,
                ...(runtimeErrors.hasErrors && {
                    runtimeErrors: runtimeErrors.errors,
                    error: `Found ${runtimeErrors.errors.length} component crash(es): ${runtimeErrors.errors.map((e) => e.header).join(", ")}`,
                }),
            };
        }
        default:
            throw new Error(`Unhandled Playwright action: ${action}`);
    }
}
//# sourceMappingURL=embedded-playwright-mcp-server.js.map