UNPKG

playwright-mcp

Version:
2,722 lines 94.1 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }

var _chunkV6YYWYKPcjs = require('./chunk-V6YYWYKP.cjs');

// src/mcp/index.ts
var _mcpjs = require('@modelcontextprotocol/sdk/server/mcp.js');
var _zod = require('zod');
var _playwright = require('playwright');

// src/mcp/eval.ts
var _vm = require('vm'); var _vm2 = _interopRequireDefault(_vm);
var secureEvalAsync = async (page2, code, context2 = {}) => {
  const timeout = 2e4;
  const filename = "eval.js";
  let logs = [];
  let errors = [];
  const wrappedCode = `
    ${code}
    run(page);
  `;
  const sandbox = {
    // Core async essentials
    Promise,
    setTimeout,
    clearTimeout,
    setImmediate,
    clearImmediate,
    // Pass page object to sandbox
    page: page2,
    // Capture all console methods
    console: {
      log: (...args) => {
        const msg = args.map((arg) => String(arg)).join(" ");
        logs.push(`[log] ${msg}`);
      },
      error: (...args) => {
        const msg = args.map((arg) => String(arg)).join(" ");
        errors.push(`[error] ${msg}`);
      },
      warn: (...args) => {
        const msg = args.map((arg) => String(arg)).join(" ");
        logs.push(`[warn] ${msg}`);
      },
      info: (...args) => {
        const msg = args.map((arg) => String(arg)).join(" ");
        logs.push(`[info] ${msg}`);
      },
      debug: (...args) => {
        const msg = args.map((arg) => String(arg)).join(" ");
        logs.push(`[debug] ${msg}`);
      },
      trace: (...args) => {
        const msg = args.map((arg) => String(arg)).join(" ");
        logs.push(`[trace] ${msg}`);
      }
    },
    // User-provided context
    ...context2,
    // Explicitly block access to sensitive globals
    process: void 0,
    global: void 0,
    require: void 0,
    __dirname: void 0,
    __filename: void 0,
    Buffer: void 0
  };
  try {
    const vmContext = _vm2.default.createContext(sandbox);
    const script = new _vm2.default.Script(wrappedCode, { filename });
    const result = script.runInContext(vmContext);
    const awaitedResult = await result;
    return {
      result: awaitedResult,
      logs,
      errors
    };
  } catch (error) {
    return {
      error: true,
      message: error.message,
      stack: error.stack,
      logs,
      errors
    };
  }
};

// src/lib/posthog-server.ts
var _posthognode = require('posthog-node');

// src/lib/user-id.ts
var _crypto = require('crypto');
var _os = require('os');
var _path = require('path');
var _fs = require('fs');

var cachedUserId = null;
var CONFIG_DIR = _path.join.call(void 0, _os.homedir.call(void 0, ), ".playwright-mcp");
var USER_ID_FILE = _path.join.call(void 0, CONFIG_DIR, "user-id");
function getUserId() {
  if (cachedUserId) {
    return cachedUserId;
  }
  try {
    if (_fs.existsSync.call(void 0, USER_ID_FILE)) {
      const storedId = _fs.readFileSync.call(void 0, USER_ID_FILE, "utf-8").trim();
      if (storedId && storedId.length > 0) {
        cachedUserId = storedId;
        return cachedUserId;
      }
    }
  } catch (err) {
  }
  let newUserId;
  try {
    const host = _os.hostname.call(void 0, );
    const username = _os.userInfo.call(void 0, ).username;
    const hash = _crypto.createHash.call(void 0, "sha256").update(`${host}-${username}`).digest("hex").substring(0, 16);
    newUserId = `user_${hash}`;
  } catch (err) {
    try {
      const host = _os.hostname.call(void 0, );
      const hash = _crypto.createHash.call(void 0, "sha256").update(host).digest("hex").substring(0, 16);
      newUserId = `user_${hash}`;
    } catch (e2) {
      const randomId = _crypto.createHash.call(void 0, "sha256").update(`${Date.now()}-${Math.random()}`).digest("hex").substring(0, 16);
      newUserId = `user_${randomId}`;
    }
  }
  try {
    if (!_fs.existsSync.call(void 0, CONFIG_DIR)) {
      _fs.mkdirSync.call(void 0, CONFIG_DIR, { recursive: true });
    }
    _fs.writeFileSync.call(void 0, USER_ID_FILE, newUserId, "utf-8");
  } catch (err) {
    console.warn("Could not persist user ID:", err);
  }
  cachedUserId = newUserId;
  return cachedUserId;
}

// src/version.ts
var VERSION = "0.0.19";

// src/lib/posthog-server.ts
var mcpClientInfo = null;
function setMcpClientInfo(clientInfo) {
  mcpClientInfo = clientInfo;
}
var posthogServer = new (0, _posthognode.PostHog)(
  "phc_3lgNkP9E1LttmoFnGyAEU7qqXhL6o6xuAC8wbA7jvFS",
  {
    host: "https://us.i.posthog.com"
  }
);
function capture(params) {
  return posthogServer.capture({
    distinctId: getUserId(),
    event: params.event,
    properties: {
      ...params.properties,
      version: VERSION,
      mcp_client: _optionalChain([mcpClientInfo, 'optionalAccess', _ => _.name]) || "unknown",
      mcp_client_version: _optionalChain([mcpClientInfo, 'optionalAccess', _2 => _2.version]) || "unknown"
    }
  });
}
process.on("exit", async () => {
  await posthogServer.shutdown();
});
process.on("SIGINT", async () => {
  await posthogServer.shutdown();
  process.exit(0);
});
process.on("SIGTERM", async () => {
  await posthogServer.shutdown();
  process.exit(0);
});

// src/snapshot/inject.ts
function injectSnapshotHelpers() {
  if (window.__snapshot) return;
  window.__snapshot = {
    visibility: {},
    interactive: {},
    generateUUID: () => {
      const chars = "234789cdefhkmnprstuvwxyz";
      return "xxxxxxxx".replace(/x/g, () => {
        const r = Math.floor(Math.random() * chars.length);
        return chars[r] || "";
      });
    },
    uuidMap: /* @__PURE__ */ new Map()
  };
  const isWhitelistedForZeroDimensions = (element) => {
    if (element.tagName.toLowerCase() !== "input") return false;
    return element.type === "checkbox";
  };
  window.__snapshot.visibility.isElementVisible = (element) => {
    if (element.tagName.toLowerCase() === "input" && element.type === "file") {
      return true;
    }
    const computedStyle = window.getComputedStyle(element);
    const isHidden = computedStyle.display === "none" || computedStyle.visibility === "hidden" || computedStyle.opacity === "0";
    let current = element.parentElement;
    let parentInvisible = false;
    while (current && !parentInvisible) {
      const parentStyle = window.getComputedStyle(current);
      if (parentStyle.display === "none" || parentStyle.opacity === "0") {
        parentInvisible = true;
        break;
      }
      current = current.parentElement;
    }
    let hasZeroDimensions = false;
    if (element instanceof HTMLElement) {
      hasZeroDimensions = element.offsetWidth === 0 || element.offsetHeight === 0;
      if (hasZeroDimensions && isWhitelistedForZeroDimensions(element)) {
        hasZeroDimensions = false;
      }
    } else if (typeof element.getBBox === "function") {
      try {
        const { width, height } = element.getBBox();
        hasZeroDimensions = width === 0 || height === 0;
      } catch (e3) {
        hasZeroDimensions = true;
      }
    }
    const rect = element.getBoundingClientRect();
    let isClipped = false;
    if (!isWhitelistedForZeroDimensions(element)) {
      let cursor = element;
      while (cursor && !isClipped) {
        const cursorStyle = window.getComputedStyle(cursor);
        if (cursorStyle.position === "fixed") {
          break;
        } else if (cursorStyle.position === "absolute") {
          let ancestor = cursor;
          while (ancestor) {
            const ancestorStyle = window.getComputedStyle(ancestor);
            if (ancestorStyle.position !== "static") {
              cursor = ancestor;
              break;
            }
            ancestor = _optionalChain([ancestor, 'optionalAccess', _3 => _3.parentElement]);
          }
          if (!ancestor) break;
        }
        if (cursorStyle.overflow === "hidden" || cursorStyle.overflowX === "hidden" || cursorStyle.overflowY === "hidden") {
          const parentRect = cursor.getBoundingClientRect();
          if (rect.right < parentRect.left || rect.left > parentRect.right || rect.bottom < parentRect.top || rect.top > parentRect.bottom) {
            isClipped = true;
            break;
          }
        }
        cursor = cursor.parentElement;
      }
    }
    return !isHidden && !parentInvisible && !hasZeroDimensions && !isClipped;
  };
  window.__snapshot.visibility.isElementInViewport = (element) => {
    const rect = element.getBoundingClientRect();
    if (isWhitelistedForZeroDimensions(element)) {
      return rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
    }
    return rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth && rect.width > 0 && rect.height > 0;
  };
  window.__snapshot.visibility.isElementInExpandedViewport = (element) => {
    const rects = element.getClientRects();
    if (!rects || rects.length === 0) return false;
    for (const rect of rects) {
      if (rect.width > 0 && rect.height > 0 && !(rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth)) {
        return true;
      }
    }
    return false;
  };
  window.__snapshot.visibility.isScrollableIntoView = (element) => {
    if (element.tagName.toLowerCase() === "input" && element.type === "file") {
      return true;
    }
    const computedStyle = window.getComputedStyle(element);
    const isHidden = computedStyle.display === "none" || computedStyle.opacity === "0";
    if (isHidden) return false;
    let current = element.parentElement;
    while (current) {
      const parentStyle = window.getComputedStyle(current);
      if (parentStyle.display === "none" || parentStyle.opacity === "0") {
        return false;
      }
      current = current.parentElement;
    }
    return true;
  };
  window.__snapshot.visibility.isTopElement = (element) => {
    if (element.tagName.toLowerCase() === "input" && element.type === "file") {
      return true;
    }
    if (isWhitelistedForZeroDimensions(element)) {
      return true;
    }
    const rects = element.getClientRects();
    if (!rects || rects.length === 0) return false;
    if (!window.__snapshot.visibility.isElementInExpandedViewport(element)) {
      return false;
    }
    const doc = element.ownerDocument;
    if (doc !== window.document) return true;
    const shadowRoot = element.getRootNode();
    if (shadowRoot instanceof ShadowRoot) {
      const largestRect2 = Array.from(rects).reduce(
        (largest, rect) => rect.width * rect.height > largest.width * largest.height ? rect : largest
      );
      const centerX2 = largestRect2.left + largestRect2.width / 2;
      const centerY2 = largestRect2.top + largestRect2.height / 2;
      try {
        const topEl = shadowRoot.elementFromPoint(centerX2, centerY2);
        if (!topEl) return false;
        let current = topEl;
        while (current) {
          if (current === element) return true;
          current = current.parentElement;
        }
        return false;
      } catch (e4) {
        return true;
      }
    }
    const largestRect = Array.from(rects).reduce(
      (largest, rect) => rect.width * rect.height > largest.width * largest.height ? rect : largest
    );
    const centerX = largestRect.left + largestRect.width / 2;
    const centerY = largestRect.top + largestRect.height / 2;
    try {
      const topEl = document.elementFromPoint(centerX, centerY);
      if (!topEl) return false;
      let current = topEl;
      while (current) {
        if (current === element) return true;
        current = current.parentElement;
      }
      return false;
    } catch (e5) {
      return true;
    }
  };
  const EXCLUDED_ELEMENTS = /* @__PURE__ */ new Set([
    "path",
    "rect",
    "circle",
    "line",
    "polyline",
    "polygon",
    "g",
    "text",
    "ellipse",
    "tspan",
    "use",
    "defs",
    "symbol",
    "linearGradient",
    "radialGradient",
    "pattern",
    "filter",
    "animate",
    "animateTransform",
    "animateMotion",
    "set",
    "switch",
    "foreignObject",
    "view",
    "desc",
    "title",
    "metadata",
    "clipPath",
    "mask",
    "style",
    "stop"
  ]);
  const INTERACTIVE_CURSORS = /* @__PURE__ */ new Set([
    "pointer",
    "move",
    "text",
    "grab",
    "grabbing",
    "cell",
    "copy",
    "alias",
    "all-scroll",
    "col-resize",
    "context-menu",
    "crosshair",
    "e-resize",
    "ew-resize",
    "help",
    "n-resize",
    "ne-resize",
    "nesw-resize",
    "ns-resize",
    "nw-resize",
    "nwse-resize",
    "row-resize",
    "s-resize",
    "se-resize",
    "sw-resize",
    "vertical-text",
    "w-resize",
    "zoom-in",
    "zoom-out"
  ]);
  const NON_INTERACTIVE_CURSORS = /* @__PURE__ */ new Set([
    "not-allowed",
    "no-drop",
    "wait",
    "progress",
    "initial",
    "inherit"
  ]);
  const INTERACTIVE_ELEMENTS = /* @__PURE__ */ new Set([
    "a",
    "button",
    "input",
    "select",
    "textarea",
    "details",
    "summary",
    "label",
    "option",
    "optgroup"
  ]);
  const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
    "button",
    "menuitem",
    "menuitemradio",
    "menuitemcheckbox",
    "radio",
    "checkbox",
    "tab",
    "switch",
    "slider",
    "spinbutton",
    "combobox",
    "searchbox",
    "textbox",
    "option",
    "scrollbar"
  ]);
  const hasInteractiveCursor = (element) => {
    if (element.tagName.toLowerCase() === "html") return false;
    const style = window.getComputedStyle(element);
    return INTERACTIVE_CURSORS.has(style.cursor);
  };
  const isDisabled = (element) => {
    if (element.hasAttribute("disabled") || element.getAttribute("disabled") === "true" || element.getAttribute("disabled") === "") {
      return true;
    }
    if (element.hasAttribute("readonly") || element.getAttribute("readonly") === "true" || element.getAttribute("readonly") === "") {
      return true;
    }
    if (element.hasAttribute("inert") || element.getAttribute("inert") === "true" || element.getAttribute("inert") === "") {
      return true;
    }
    return false;
  };
  window.__snapshot.interactive.isInteractiveElement = (element, config) => {
    const ELEMENT_NODE = 1;
    if (!element || element.nodeType !== ELEMENT_NODE) {
      return false;
    }
    const elementTypes = _optionalChain([config, 'optionalAccess', _4 => _4.elementTypes]);
    if (elementTypes && elementTypes.length > 0) {
      const tagName2 = element.tagName.toLowerCase();
      let matchesType = false;
      const textInputTypes = /* @__PURE__ */ new Set([
        "text",
        "password",
        "email",
        "url",
        "tel",
        "search",
        "number",
        "date",
        "datetime-local",
        "month",
        "week",
        "time",
        "color"
      ]);
      for (const type of elementTypes) {
        switch (type) {
          case "FileInput":
            if (tagName2 === "input" && element.type === "file") {
              matchesType = true;
            }
            break;
          case "TextInput":
            if (tagName2 === "input" && textInputTypes.has(element.type) || tagName2 === "textarea" || element.isContentEditable === true) {
              matchesType = true;
            }
            break;
        }
        if (matchesType) break;
      }
      if (!matchesType) return false;
    }
    if (element.getAttribute("interactive")) {
      return true;
    }
    const tagName = element.tagName.toLowerCase();
    if (EXCLUDED_ELEMENTS.has(tagName)) {
      return false;
    }
    if (!window.__snapshot.visibility.isElementVisible(element)) {
      return false;
    }
    if (hasInteractiveCursor(element)) {
      return true;
    }
    if (INTERACTIVE_ELEMENTS.has(tagName)) {
      const style = window.getComputedStyle(element);
      if (NON_INTERACTIVE_CURSORS.has(style.cursor)) {
        return false;
      }
      const includeDisabledElements = _nullishCoalesce(_optionalChain([config, 'optionalAccess', _5 => _5.includeDisabledElements]), () => ( false));
      if (!includeDisabledElements && isDisabled(element)) {
        return false;
      }
      return true;
    }
    const role = element.getAttribute("role");
    const ariaRole = element.getAttribute("aria-role");
    if (INTERACTIVE_ROLES.has(role || "") || INTERACTIVE_ROLES.has(ariaRole || "")) {
      return true;
    }
    if (element.isContentEditable === true) {
      return true;
    }
    if (element.classList && (element.classList.contains("button") || element.classList.contains("dropdown-item") || element.classList.contains("dropdown-toggle") || element.getAttribute("data-index") || element.getAttribute("data-toggle") === "dropdown" || element.getAttribute("aria-haspopup") === "true")) {
      return true;
    }
    return false;
  };
  Object.freeze(window.__snapshot.visibility);
  Object.freeze(window.__snapshot.interactive);
}

// src/snapshot/semantic-tree.ts
async function addUUIDsToPage(page2) {
  await page2.evaluate(() => {
    if (!window.__snapshot) {
      throw new Error("Snapshot helpers not injected");
    }
    const ELEMENT_NODE = 1;
    const addAttributesToNode = (node) => {
      if (node.nodeType === ELEMENT_NODE) {
        const element = node;
        let uuid = element.getAttribute("uuid");
        if (!uuid) {
          uuid = window.__snapshot.generateUUID();
          element.setAttribute("uuid", uuid);
        }
        window.__snapshot.uuidMap.set(uuid, element);
        for (const child of node.childNodes) {
          addAttributesToNode(child);
        }
        if (element.shadowRoot) {
          const shadowRoot = element.shadowRoot;
          for (const shadowChild of shadowRoot.childNodes) {
            addAttributesToNode(shadowChild);
          }
        }
      }
    };
    addAttributesToNode(document.documentElement);
  });
}
async function extractInteractiveElements(page2) {
  return await page2.evaluate(() => {
    if (!window.__snapshot) {
      throw new Error("Snapshot helpers not injected");
    }
    const uuids = [];
    window.__snapshot.uuidMap.forEach((element, uuid) => {
      if (window.__snapshot.interactive.isInteractiveElement(element)) {
        uuids.push(uuid);
      }
    });
    return Array.from(new Set(uuids));
  });
}
async function buildSemanticTree(page2, options = {}) {
  return await page2.evaluate((options2) => {
    if (!window.__snapshot) {
      throw new Error("Snapshot helpers not injected");
    }
    const EMPTY_NODE = { tagName: "body", children: [] };
    const EXCLUDED_TAGS = /* @__PURE__ */ new Set([
      "script",
      "style",
      "link",
      "meta",
      "noscript"
    ]);
    const STRUCTURAL_ELEMENTS = /* @__PURE__ */ new Set([
      "html",
      "body",
      "nav",
      "header",
      "footer",
      "main",
      "article",
      "section",
      "aside",
      "dialog",
      "form",
      "table"
    ]);
    const EXCLUDED_SVG_ELEMENTS = /* @__PURE__ */ new Set([
      "path",
      "rect",
      "circle",
      "line",
      "polyline",
      "polygon",
      "g",
      "text",
      "ellipse",
      "tspan",
      "use",
      "defs",
      "symbol",
      "linearGradient",
      "radialGradient",
      "pattern",
      "filter"
    ]);
    const extractElementName = (element) => {
      return element.getAttribute("aria-label") || element.getAttribute("label") || element.getAttribute("title") || element.getAttribute("name") || null;
    };
    const sanitizeUrl = (url, maxLength = 200) => {
      if (!url) return url;
      if (url.startsWith("data:")) return "[data-url]";
      if (url.length > maxLength) return url.substring(0, maxLength) + "...";
      return url;
    };
    const isStructuralElement = (element) => {
      const tagName = element.tagName.toLowerCase();
      const role = element.getAttribute("role");
      return STRUCTURAL_ELEMENTS.has(tagName) || STRUCTURAL_ELEMENTS.has(role || "");
    };
    const extractDirectText = (element) => {
      return Array.from(element.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => _optionalChain([node, 'access', _6 => _6.textContent, 'optionalAccess', _7 => _7.trim, 'call', _8 => _8()])).filter((text) => text && text.length > 0).join(" ");
    };
    const extractNestedText = (internalNode) => {
      const element = internalNode.element;
      const directText = extractDirectText(element);
      const nestedText = Array.from(internalNode.children).filter((node) => node.shouldPrune).map((node) => _optionalChain([node, 'access', _9 => _9.element, 'access', _10 => _10.textContent, 'optionalAccess', _11 => _11.split, 'call', _12 => _12("\n"), 'access', _13 => _13.join, 'call', _14 => _14(" "), 'access', _15 => _15.trim, 'call', _16 => _16()])).filter((text) => text && text.length > 0).join(" ");
      return [directText, nestedText].filter(Boolean).join(" ");
    };
    const extractElementContent = (internalNode) => {
      const element = internalNode.element;
      const tagName = element.tagName.toLowerCase();
      const result = {};
      const nestedText = extractNestedText(internalNode);
      switch (tagName) {
        case "input": {
          const input = element;
          const type = input.type.toLowerCase();
          const attrs = [`type=${type}`];
          if (input.name) attrs.push(`name=${input.name}`);
          if (input.placeholder) attrs.push(`placeholder=${input.placeholder}`);
          if (type === "checkbox" || type === "radio") {
            result.value = input.checked ? "checked" : "unchecked";
          } else if (type !== "hidden" && input.value) {
            result.value = input.value;
          }
          result.attributes = attrs.join("; ");
          break;
        }
        case "select": {
          const select = element;
          const selectedOptions = Array.from(select.selectedOptions);
          if (selectedOptions.length > 0) {
            result.additionalInfo = "options=" + Array.from(select.querySelectorAll("option")).map((option) => option.textContent || "").join(", ");
            result.value = selectedOptions.map((opt) => opt.text).join(", ");
          }
          break;
        }
        case "textarea": {
          const textarea = element;
          if (textarea.value) result.value = textarea.value;
          break;
        }
        case "img": {
          const img = element;
          if (img.alt) result.value = img.alt;
          if (img.src)
            result.attributes = `src=${sanitizeUrl(img.src)}; alt=${img.alt || ""}`;
          break;
        }
        case "a": {
          const link = element;
          result.value = nestedText || void 0;
          if (link.href) result.attributes = `href=${sanitizeUrl(link.href)}`;
          break;
        }
        case "button": {
          const button = element;
          result.value = _optionalChain([button, 'access', _17 => _17.textContent, 'optionalAccess', _18 => _18.trim, 'call', _19 => _19()]) || void 0;
          break;
        }
        case "h1":
        case "h2":
        case "h3":
        case "h4":
        case "h5":
        case "h6": {
          result.value = _optionalChain([element, 'access', _20 => _20.textContent, 'optionalAccess', _21 => _21.trim, 'call', _22 => _22()]) || void 0;
          break;
        }
        case "svg": {
          const svg = element;
          const titleElement = svg.querySelector("title");
          const ariaLabel = svg.getAttribute("aria-label");
          result.value = ariaLabel || _optionalChain([titleElement, 'optionalAccess', _23 => _23.textContent, 'optionalAccess', _24 => _24.trim, 'call', _25 => _25()]) || void 0;
          break;
        }
        default: {
          const ariaLabel = element.getAttribute("aria-label");
          result.value = [nestedText, ariaLabel].filter(Boolean).join(" ");
        }
      }
      result.nestedText = nestedText;
      return result;
    };
    function buildSemanticNode(internalNode) {
      const element = internalNode.element;
      const tagName = element.tagName.toLowerCase();
      const id = element.getAttribute("uuid") || void 0;
      const visible = window.__snapshot.visibility.isElementVisible(element);
      const interactive = window.__snapshot.interactive.isInteractiveElement(element);
      const scrollableIntoView = window.__snapshot.visibility.isScrollableIntoView(element);
      const contentInfo = extractElementContent(internalNode);
      const selector = id && _optionalChain([window, 'access', _26 => _26.__snapshot, 'optionalAccess', _27 => _27.selectorsMap, 'optionalAccess', _28 => _28.get, 'call', _29 => _29(id)]) || void 0;
      const node = {
        id,
        tagName,
        children: [],
        isHierarchyNode: !internalNode.isKeeper,
        role: element.getAttribute("role") || void 0,
        deductedName: extractElementName(element) || void 0,
        selector,
        visible,
        interactive,
        scrollableIntoView,
        ...contentInfo
      };
      if (id && interactive) {
        const rect = element.getBoundingClientRect();
        if (rect.width > 0 && rect.height > 0) {
          node.boundingBox = {
            x: Math.round(rect.left),
            y: Math.round(rect.top),
            width: Math.round(rect.width),
            height: Math.round(rect.height)
          };
        }
      }
      return node;
    }
    function getAllElementsWithUuid(root) {
      const elements = [];
      const localElements = Array.from(root.querySelectorAll("[uuid]"));
      elements.push(...localElements);
      if (root instanceof Element && root.hasAttribute("uuid")) {
        elements.push(root);
      }
      const allElements = Array.from(root.querySelectorAll("*"));
      for (const el of allElements) {
        if (el.shadowRoot) {
          elements.push(...getAllElementsWithUuid(el.shadowRoot));
        }
      }
      return elements;
    }
    function getFilteredElements(options3) {
      const elementsToInclude2 = /* @__PURE__ */ new Set();
      let allElementsWithUuid = getAllElementsWithUuid(document);
      if (options3.filterByUuids && options3.filterByUuids.length > 0) {
        const uuidSet = new Set(options3.filterByUuids);
        allElementsWithUuid = allElementsWithUuid.filter((el) => {
          const uuid = el.getAttribute("uuid");
          return uuid && uuidSet.has(uuid);
        });
      }
      if (options3.excludeNonVisible) {
        allElementsWithUuid = allElementsWithUuid.filter(
          (el) => window.__snapshot.visibility.isElementVisible(el)
        );
      }
      if (options3.excludeNonScrollableIntoView) {
        allElementsWithUuid = allElementsWithUuid.filter(
          (el) => window.__snapshot.visibility.isScrollableIntoView(el)
        );
      }
      if (options3.excludeNonInteractive) {
        allElementsWithUuid = allElementsWithUuid.filter(
          (el) => window.__snapshot.interactive.isInteractiveElement(el, {
            elementTypes: options3.elementTypes,
            includeDisabledElements: options3.includeDisabledElements
          })
        );
      }
      if (options3.excludeNonTopElements) {
        allElementsWithUuid = allElementsWithUuid.filter(
          (el) => window.__snapshot.visibility.isTopElement(el)
        );
      }
      if (options3.excludeEmptyTextElements) {
        allElementsWithUuid = allElementsWithUuid.filter((el) => {
          const directText = Array.from(el.childNodes).filter((node) => node.nodeType === Node.TEXT_NODE).map((node) => _optionalChain([node, 'access', _30 => _30.textContent, 'optionalAccess', _31 => _31.trim, 'call', _32 => _32()])).filter((text) => text && text.length > 0).join(" ");
          return directText.trim().length > 0;
        });
      }
      allElementsWithUuid.forEach((el) => elementsToInclude2.add(el));
      return elementsToInclude2;
    }
    function buildInternalTree(elementsToInclude2) {
      const nodeMap = /* @__PURE__ */ new Map();
      function buildNode(element) {
        if (nodeMap.has(element)) {
          return nodeMap.get(element);
        }
        const node = {
          element,
          parent: null,
          children: [],
          isKeeper: elementsToInclude2.has(element),
          isStructural: isStructuralElement(element),
          shouldPrune: false
        };
        nodeMap.set(element, node);
        const processChildren = (children) => {
          for (const child of children) {
            const childTagName = child.tagName.toLowerCase();
            if (EXCLUDED_TAGS.has(childTagName) || EXCLUDED_SVG_ELEMENTS.has(childTagName)) {
              continue;
            }
            const childNode = buildNode(child);
            childNode.parent = node;
            node.children.push(childNode);
          }
        };
        processChildren(element.children);
        if (element.shadowRoot) {
          processChildren(element.shadowRoot.children);
        }
        return node;
      }
      return buildNode(document.documentElement);
    }
    function markNodesForPruning(root, hierarchyMode) {
      if (!root) return;
      function markNonRelevantNodes(node) {
        let hasKeeperDescendant = false;
        for (const child of node.children) {
          if (markNonRelevantNodes(child)) {
            hasKeeperDescendant = true;
          }
        }
        if (node.isKeeper) {
          hasKeeperDescendant = true;
        }
        if (node.shouldPrune) return false;
        if (!hasKeeperDescendant) {
          node.shouldPrune = true;
          return false;
        }
        return hasKeeperDescendant;
      }
      markNonRelevantNodes(root);
      if (hierarchyMode === "full") return;
      function getLeafNodes(node) {
        if (node.children.length === 0) return [node];
        const leaves2 = [];
        for (const child of node.children) {
          leaves2.push(...getLeafNodes(child));
        }
        return leaves2;
      }
      const leaves = getLeafNodes(root);
      for (const leaf of leaves) {
        let current = leaf;
        while (current) {
          const parent = current.parent;
          if (!parent) break;
          if (parent.shouldPrune) {
            current = parent;
            continue;
          }
          const isKeeper = parent.isKeeper;
          const isStructural = parent.isStructural;
          let shouldMarkForPruning = false;
          if (hierarchyMode === "minimal") {
            shouldMarkForPruning = !isStructural && !isKeeper;
          } else if (hierarchyMode === "important") {
            const remainingChildrenCount = parent.children.filter(
              (child) => !child.shouldPrune
            ).length;
            const parentRemainingChildrenCount = parent.parent ? parent.parent.children.filter((child) => !child.shouldPrune).length : 0;
            shouldMarkForPruning = !isStructural && !isKeeper && (remainingChildrenCount === 1 || parentRemainingChildrenCount === 1);
          }
          if (shouldMarkForPruning) {
            parent.shouldPrune = true;
          }
          current = parent;
        }
      }
    }
    function convertToSemanticTree(node) {
      if (!node) return [];
      if (node.shouldPrune) {
        const result = [];
        for (const child of node.children) {
          result.push(...convertToSemanticTree(child));
        }
        return result;
      }
      const semanticNode = buildSemanticNode(node);
      if (!semanticNode) return [];
      semanticNode.children = [];
      for (const child of node.children) {
        semanticNode.children.push(...convertToSemanticTree(child));
      }
      return [semanticNode];
    }
    const elementsToInclude = getFilteredElements(options2);
    const internalRoot = buildInternalTree(elementsToInclude);
    markNodesForPruning(internalRoot, options2.hierarchy || "important");
    const semanticNodes = convertToSemanticTree(internalRoot);
    return semanticNodes.length > 0 ? semanticNodes[0] || EMPTY_NODE : EMPTY_NODE;
  }, options);
}
function serializeSemanticNode(node, options = {}) {
  const {
    maxLength = 3e4,
    maxNodeTextLength = 1e3,
    skipHierarchyNodeContent = true,
    skipNonVisibleElements = true,
    skipNonScrollableIntoView = false,
    includeUuid = "interactive",
    includeRole = false,
    includeAttributes = true,
    includeDeductedName = true,
    includeAdditionalInfo = true,
    includePath = false,
    includeInteractive = false,
    includeVisibility = false
  } = options;
  if (!node) return "EMPTY!";
  const processChildren = (children, depth) => {
    let result2 = "";
    for (const child of children) {
      const childText = processNode(child, depth);
      if (childText) result2 += childText;
    }
    return result2;
  };
  const processNode = (currentNode, depth) => {
    let result2 = "";
    const indent = "	".repeat(depth);
    if (skipNonVisibleElements && currentNode.visible === false) {
      if (currentNode.children && currentNode.children.length > 0) {
        return processChildren(currentNode.children, depth);
      }
      return "";
    }
    if (skipNonScrollableIntoView && currentNode.scrollableIntoView === false) {
      if (currentNode.children && currentNode.children.length > 0) {
        return processChildren(currentNode.children, depth);
      }
      return "";
    }
    result2 += indent;
    if ((includeUuid === "interactive" && currentNode.interactive && !currentNode.isHierarchyNode || includeUuid === "all" && includeUuid !== "none") && currentNode.id) {
      result2 += `[${currentNode.id}]`;
    }
    result2 += `<${currentNode.tagName}`;
    if (includeRole && currentNode.role) {
      result2 += ` aria-role="${currentNode.role}"`;
    }
    if (includeDeductedName && currentNode.deductedName) {
      result2 += ` aria-name="${currentNode.deductedName}"`;
    }
    if (includeAttributes && currentNode.attributes) {
      const attrs = currentNode.attributes.split("; ");
      attrs.forEach((attr) => {
        const [key, value] = attr.split("=");
        if (key && value) {
          result2 += ` ${key}="${value}"`;
        }
      });
    }
    if (includePath && currentNode.path) {
      result2 += ` path="${currentNode.path}"`;
    }
    if (includeVisibility && currentNode.visible !== void 0) {
      result2 += ` visible="${currentNode.visible}"`;
    }
    if (includeInteractive && currentNode.interactive) {
      result2 += ` interactive="${currentNode.interactive}"`;
    }
    if (currentNode.boundingBox) {
      result2 += ` pos="${currentNode.boundingBox.x},${currentNode.boundingBox.y},${currentNode.boundingBox.width},${currentNode.boundingBox.height}"`;
    }
    result2 += ">";
    let hasContent = false;
    if (currentNode.value && currentNode.value.trim() && (!skipHierarchyNodeContent || !currentNode.isHierarchyNode)) {
      let nodeText = currentNode.value.trim();
      if (nodeText.length > maxNodeTextLength) {
        nodeText = nodeText.slice(0, maxNodeTextLength) + "...";
      }
      result2 += nodeText;
      hasContent = true;
    }
    if (includeAdditionalInfo && currentNode.additionalInfo) {
      if (hasContent) result2 += " ";
      result2 += `(${currentNode.additionalInfo})`;
      hasContent = true;
    }
    if (currentNode.children.length > 0) {
      result2 += "\n";
      result2 += processChildren(currentNode.children, depth + 1);
      result2 += indent + `</${currentNode.tagName}>
`;
    } else if (hasContent) {
      result2 += `</${currentNode.tagName}>
`;
    } else {
      result2 = result2.slice(0, -1) + "/>\n";
    }
    return result2;
  };
  let result = processNode(node, 0).trim();
  if (result.length > maxLength) {
    result = result.slice(0, maxLength) + "\n... (truncated)";
  }
  return result;
}

// src/snapshot/element-grouping.ts
async function groupInteractiveElements(page2, interactableElements) {
  return await page2.evaluate(
    ({ uuids }) => {
      if (!window.__snapshot) {
        throw new Error("Snapshot helpers not injected");
      }
      const DENSITY_THRESHOLD = 5;
      const PROXIMITY_THRESHOLD = 150;
      const getElementBounds = (element) => {
        const rect = element.getBoundingClientRect();
        return {
          top: Math.round(rect.top),
          left: Math.round(rect.left),
          width: Math.round(rect.width),
          height: Math.round(rect.height),
          right: Math.round(rect.right),
          bottom: Math.round(rect.bottom)
        };
      };
      const findSemanticContainer = (elements) => {
        if (elements.length === 0) return null;
        let commonAncestor = elements[0];
        for (const elem of elements.slice(1)) {
          while (commonAncestor && !commonAncestor.contains(elem)) {
            commonAncestor = commonAncestor.parentElement;
          }
        }
        const semanticTags = [
          "nav",
          "form",
          "header",
          "footer",
          "aside",
          "menu",
          "toolbar"
        ];
        const semanticRoles = [
          "navigation",
          "form",
          "menu",
          "toolbar",
          "tablist",
          "group"
        ];
        const semanticClasses = [
          "menu",
          "nav",
          "toolbar",
          "sidebar",
          "header",
          "footer",
          "form",
          "calendar",
          "datepicker"
        ];
        while (commonAncestor && commonAncestor !== document.body) {
          const tag = commonAncestor.tagName.toLowerCase();
          const role = commonAncestor.getAttribute("role");
          const classList = Array.from(commonAncestor.classList);
          if (semanticTags.includes(tag) || role && semanticRoles.includes(role) || classList.some(
            (cls) => semanticClasses.some((sc) => cls.toLowerCase().includes(sc))
          )) {
            return commonAncestor;
          }
          const ancestorBounds = getElementBounds(commonAncestor);
          const area = ancestorBounds.width * ancestorBounds.height;
          const viewportArea = window.innerWidth * window.innerHeight;
          if (area > viewportArea * 0.5) {
            break;
          }
          commonAncestor = commonAncestor.parentElement;
        }
        return null;
      };
      const areElementsDense = (elements) => {
        if (elements.length < DENSITY_THRESHOLD) return false;
        const bounds = elements.map(getElementBounds);
        const minLeft = Math.min(...bounds.map((b) => b.left));
        const minTop = Math.min(...bounds.map((b) => b.top));
        const maxRight = Math.max(...bounds.map((b) => b.right));
        const maxBottom = Math.max(...bounds.map((b) => b.bottom));
        const containerArea = (maxRight - minLeft) * (maxBottom - minTop);
        const elementsTotalArea = bounds.reduce(
          (sum, b) => sum + b.width * b.height,
          0
        );
        const density = elementsTotalArea / containerArea;
        let closeElements = 0;
        for (let i = 0; i < elements.length; i++) {
          for (let j = i + 1; j < elements.length; j++) {
            const b1 = bounds[i];
            const b2 = bounds[j];
            const horizontalDist = Math.max(
              0,
              Math.max(
                (_optionalChain([b1, 'optionalAccess', _33 => _33.left]) || 0) - (_optionalChain([b2, 'optionalAccess', _34 => _34.right]) || 0),
                (_optionalChain([b2, 'optionalAccess', _35 => _35.left]) || 0) - (_optionalChain([b1, 'optionalAccess', _36 => _36.right]) || 0)
              )
            );
            const verticalDist = Math.max(
              0,
              Math.max(
                (_optionalChain([b1, 'optionalAccess', _37 => _37.top]) || 0) - (_optionalChain([b2, 'optionalAccess', _38 => _38.bottom]) || 0),
                (_optionalChain([b2, 'optionalAccess', _39 => _39.top]) || 0) - (_optionalChain([b1, 'optionalAccess', _40 => _40.bottom]) || 0)
              )
            );
            const distance = Math.sqrt(horizontalDist ** 2 + verticalDist ** 2);
            if (distance < PROXIMITY_THRESHOLD) {
              closeElements++;
            }
          }
        }
        const proximityRatio = closeElements / (elements.length * (elements.length - 1) / 2);
        return density > 0.3 || proximityRatio > 0.5;
      };
      const generateGroupLabel = (container, elements) => {
        if (container) {
          const role = container.getAttribute("role");
          const ariaLabel = container.getAttribute("aria-label");
          const title = container.getAttribute("title");
          const tag = container.tagName.toLowerCase();
          if (ariaLabel) return `${ariaLabel} (${elements.length} items)`;
          if (title) return `${title} (${elements.length} items)`;
          if (role) return `${role} section (${elements.length} items)`;
          if (tag === "nav") return `Navigation (${elements.length} items)`;
          if (tag === "form") return `Form (${elements.length} fields)`;
          if (tag === "menu") return `Menu (${elements.length} items)`;
          const classList = Array.from(container.classList).join(" ").toLowerCase();
          if (classList.includes("calendar"))
            return `Calendar (${elements.length} items)`;
          if (classList.includes("toolbar"))
            return `Toolbar (${elements.length} items)`;
          if (classList.includes("sidebar"))
            return `Sidebar (${elements.length} items)`;
        }
        const buttons = elements.filter(
          (e) => e.tagName === "BUTTON" || e.getAttribute("role") === "button"
        ).length;
        const links = elements.filter((e) => e.tagName === "A").length;
        const inputs = elements.filter(
          (e) => e.tagName === "INPUT" || e.tagName === "TEXTAREA"
        ).length;
        if (buttons > links && buttons > inputs)
          return `Button group (${elements.length} items)`;
        if (links > buttons && links > inputs)
          return `Link group (${elements.length} items)`;
        if (inputs > buttons && inputs > links)
          return `Input group (${elements.length} items)`;
        return `Interactive group (${elements.length} items)`;
      };
      const groups = [];
      const processedUuids = /* @__PURE__ */ new Set();
      const ungroupedElements = /* @__PURE__ */ new Set();
      const containerMap = /* @__PURE__ */ new Map();
      for (const uuid of uuids) {
        const element = window.__snapshot.uuidMap.get(uuid);
        if (!element) continue;
        let container = element.parentElement;
        while (container && container !== document.body) {
          const childrenWithUuids = Array.from(container.children).filter(
            (child) => uuids.includes(child.getAttribute("uuid") || "")
          );
          if (childrenWithUuids.length >= DENSITY_THRESHOLD) {
            if (!containerMap.has(container)) {
              containerMap.set(container, []);
            }
            containerMap.get(container).push(uuid);
            break;
          }
          container = container.parentElement;
        }
        if (!container || container === document.body) {
          ungroupedElements.add(uuid);
        }
      }
      let groupId = 1;
      for (const [container, elementUuids] of containerMap.entries()) {
        if (processedUuids.size > 0 && elementUuids.some((uuid) => processedUuids.has(uuid))) {
          continue;
        }
        const elements = elementUuids.map((uuid) => window.__snapshot.uuidMap.get(uuid)).filter((e) => e !== void 0);
        if (areElementsDense(elements)) {
          const allBounds = elements.map(getElementBounds);
          const spreadMinTop = Math.min(...allBounds.map((b) => b.top));
          const spreadMaxTop = Math.max(...allBounds.map((b) => b.top));
          const spreadMinLeft = Math.min(...allBounds.map((b) => b.left));
          const spreadMaxLeft = Math.max(...allBounds.map((b) => b.left));
          const verticalSpread = spreadMaxTop - spreadMinTop;
          const horizontalSpread = spreadMaxLeft - spreadMinLeft;
          const maxSpread = Math.max(verticalSpread, horizontalSpread);
          if (maxSpread > 500) {
            elementUuids.forEach((uuid) => ungroupedElements.add(uuid));
            continue;
          }
          const semanticContainer = findSemanticContainer(elements);
          const minLeft = Math.min(...allBounds.map((b) => b.left));
          const minTop = Math.min(...allBounds.map((b) => b.top));
          const maxRight = Math.max(...allBounds.map((b) => b.left + b.width));
          const maxBottom = Math.max(...allBounds.map((b) => b.top + b.height));
          const bounds = {
            top: minTop,
            left: minLeft,
            width: maxRight - minLeft,
            height: maxBottom - minTop
          };
          const group = {
            id: `group-${groupId++}`,
            type: "group",
            label: generateGroupLabel(semanticContainer || container, elements),
            elements: elementUuids,
            bounds: {
              top: bounds.top,
              left: bounds.left,
              width: bounds.width,
              height: bounds.height
            }
          };
          groups.push(group);
          elementUuids.forEach((uuid) => processedUuids.add(uuid));
        } else {
          elementUuids.forEach((uuid) => ungroupedElements.add(uuid));
        }
      }
      return {
        groups,
        ungroupedElements: Array.from(ungroupedElements)
      };
    },
    { uuids: interactableElements }
  );
}

// src/snapshot/dom-annotations.ts
async function addDOMAnnotations(page2, bounds) {
  await page2.evaluate((bounds2) => {
    const existingAnnotations = document.querySelectorAll(
      "[data-pw-annotation]"
    );
    existingAnnotations.forEach((el) => el.remove());
    const annotationLayer = document.createElement("div");
    annotationLayer.setAttribute("data-pw-annotation", "layer");
    annotationLayer.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
      z-index: 2147483647;
    `;
    document.body.appendChild(annotationLayer);
    const calculateLabelPosition = (bound) => {
      const elementArea = bound.width * bound.height;
      const isSmallElement = elementArea < 2e3;
      const bubbleWidth = isSmallElement ? 16 : 28;
      const bubbleHeight = isSmallElement ? 16 : 24;
      const positions = [
        { x: bound.left - bubbleWidth - 5, y: bound.top },
        // Left
        { x: bound.left + bound.width + 5, y: bound.top },
        // Right
        { x: bound.left, y: bound.top - bubbleHeight - 5 },
        // Top
        { x: bound.left, y: bound.top + bound.height + 5 },
        // Bottom
        { x: bound.left - bubbleWidth - 5, y: bound.top - bubbleHeight - 5 },
        // Top-left
        { x: bound.left + bound.width + 5, y: bound.top - bubbleHeight - 5 }
        // Top-right
      ];
      for (const pos of positions) {
        if (pos.x >= 0 && pos.y >= 0 && pos.x + bubbleWidth <= window.innerWidth && pos.y + bubbleHeight <= window.innerHeight) {
          return { ...pos, width: bubbleWidth, height: bubbleHeight };
        }
      }
      return {
        x: Math.max(
          5,
          Math.min(bound.left, window.innerWidth - bubbleWidth - 5)
        ),
        y: Math.max(
          5,
          Math.min(
            bound.top - bubbleHeight - 5,
            window.innerHeight - bubbleHeight - 5
          )
        ),
        width: bubbleWidth,
        height: bubbleHeight
      };
    };
    bounds2.forEach((bound) => {
      const box = document.createElement("div");
      box.setAttribute("data-pw-annotation", "box");
      box.style.cssText = `
        position: absolute;
        left: ${bound.left}px;
        top: ${bound.top}px;
        width: ${bound.width}px;
        height: ${bound.height}px;
        border: ${bound.isGroup ? "3px dashed" : "2px solid"} ${bound.color};
        background-color: ${bound.color}${bound.isGroup ? "10" : "15"};
        pointer-events: none;
        box-sizing: border-box;
      `;
      annotationLayer.appendChild(box);
      if (bound.label) {
        const labelPos = calculateLabelPosition(bound);
        const label = document.createElement("div");
        label.setAttribute("data-pw-annotation", "label");
        const isSmallLabel = labelPos.width < 20;
        const fontSize = labelPos.height <= 16 ? 9 : 12;
        label.style.cssText = `
          position: absolute;
          left: ${labelPos.x}px;
          top: ${labelPos.y}px;
          width: ${labelPos.width}px;
          height: ${labelPos.height}px;
          background-color: ${bound.color};
          color: white;
          display: flex;
          align-items: center;
          justify-content: center;
          border-radius: ${isSmallLabel ? "3px" : "4px"};
          font-family: system-ui, -apple-system, sans-serif;
          font-size: ${fontSize}px;
          font-weight: bold;
          box-shadow: 0 ${isSmallLabel ? "2px 4px" : "2px 8px"} rgba(0, 0, 0, 0.3);
          pointer-events: none;
          opacity: ${isSmallLabel ? 0.85 : 1};
        `;
        label.textContent = bound.label;
        annotationLayer.appendChild(label);
      }
    });
  }, bounds);
}
async function removeDOMAnnotations(page2) {
  await page2.evaluate(() => {
    const existingAnnotations = document.querySelectorAll(
      "[data-pw-annotation]"
    );
    existingAnnotations.forEach((el) => el.remove());
  });
}
async function screenshotWithDOMAnnotations(page2, bounds, fullPage = false) {
  try {
    await addDOMAnnotations(page2, bounds);
    await page2.waitForTimeout(100);
    const screenshot = await page2.screenshot({
      fullPage
    });
    await removeDOMAnnotations(page2);
    return screenshot;
  } catch (error) {
    await removeDOMAnnotations(page2);
    throw error;
  }
}

// src/snapshot/bounding-box.ts
function filterNestedElements(bounds, viewportHeight = 800) {
  if (bounds.length <= 1) return bounds;
  const scoredBounds = bounds.map((bound) => ({
    ...bound,
    importance: 100
    // Equal importance for all elements
  }));
  const minImportanceThreshold = 30;
  const importantBounds = scoredBounds.filter(
    (bound) => bound.importance >= minImportanceThreshold
  );
  const finalBounds = importantBounds.length > 0 ? importantBounds : scoredBounds.sort((a, b) => b.importance - a.importance).slice(0, Math.min(8, bounds.length));
  const sortedBounds = finalBounds.sort((a, b) => {
    const importanceDiff = b.importance - a.importance;
    if (Math.abs(importanceDiff) > 10) return importanceDiff;
    return b.width * b.height - a.width * a.height;
  });
  const totalArea = sortedBounds.reduce(
    (sum, bound) => sum + bound.width * bound.height,
    0
  );
  const avgArea = totalArea / sortedBounds.length;
  const minAreaThreshold = avgArea * 0.1;
  const contains = (a, b) => {
    return a.left <= b.left && a.top <= b.top && a.left + a.width >= b.left + b.width && a.top + a.height >= b.top + b.height;
  };
  const hasSignificantOverlap = (a, b, threshold) => {
    const overlapLeft = Math.max(a.left, b.left);
    const overlapTop = Math.max(a.top, b.top);
    const overlapRight = Math.min(a.left + a.width, b.left + b.width);
    const overlapBottom = Math.min(a.top + a.height, b.top + b.height);
    if (overlapRight <= overlapLeft || overlapBottom <= overlapTop)
      return false;
    const overlapArea = (overlapRight - overlapLeft) * (overlapBottom - overlapTop);
    const smallerArea = Math.min(a.width * a.height, b.width * b.height);
    return overlapArea / smallerArea >= threshold;
  };
  const filtered = [];
  for (const bound of sortedBounds) {
    const area = bound.width * bound.height;
    if (area < minAreaThreshold && bound.importance < 80) continue;
    const isNested = filtered.some((selected) => {
      if (selected.importance > bound.importance + 20) {
        return contains(selected, bound) || hasSignificantOverlap(selected, bound, 0.6);
      }
      return contains(selected, bound) || hasSignificantOverlap(selected, bound, 0.8);
    });
    if (!isNested) {
      filtered.push(bound);
    }
  }
  return filtered.map(({ importance: _importance, ...bound }) => bound);
}
async function captureScreenshotWithBoundingBoxes(page2, interactableElements, options) {
  const validUuids = interactableElements.filter(
    (uuid) => uuid && uuid.length > 5 && uuid !== "no-uuid-found"
  );
  let groupingResult = null;
  const USE_GROUPING_THRESHOLD = 15;
  if (validUuids.length > USE_GROUPING_THRESHOLD) {
    const elementsForGrouping = _optionalChain([options, 'optionalAccess', _41 => _41.includeAllInteractiveForGroups]) && _optionalChain([options, 'optionalAccess', _42 => _42.allInteractiveElements]) ? options.allInteractiveElements.filter(
      (uuid) => uuid && uuid.length > 5 && uuid !== "no-uuid-found"
    ) : validUuids;
    groupingResult = await groupInteractiveElements(page2, elementsForGrouping);
  }
  const evaluateParams = {
    uuids: groupingResult ? groupingResult.ungroupedElements : validUuids,
    groups: groupingResult ? groupingResult.groups : null
  };
  const elementBounds = await page2.evaluate(
    ({ uuids, groups }) => {
      if (!window.__snapshot) {
        throw new Error("Snapshot helpers not injected");
      }
      const bounds = [];
      const colors = [
        "#FF0000",
        "#00FF00",
        "#0000FF",
        "#FFA500",
        "#800080",
        "#008080",
        "#FF69B4",
        "#4B0082",
        "#FF4500",
        "#2E8B57"
      ];
      const isIndependentInteractive = (element) => {
        if (!element || element.nodeType !== Node.ELEMENT_NODE) return false;
        if (!window.__snapshot.visibility.isElementVisible(element))
          return false;
        if (!window.__snapshot.visibility.isElementInExpandedViewport(element))
          return false;
        if (!window.__snapshot.visibility.isTopElement(element)) return false;
        const hasInteractiveAttributes = element.hasAttribute("role") || element.hasAttribute("tabindex") || element.hasAttribute("onclick") || typeof element.onclick === "function";
        const hasInteractiveClass = /\b(btn|clickable|menu|item|entry|link)\b/i.test(
          element.className || ""
        );
        const isParentBody = element.parentElement && element.parentElement.isSameNode(document.body);
        const hasPointerCursor = (el) => {
          const styles = window.getComputedStyle(el);
          return styles.cursor === "pointer" || el.classList.contains("cursor-pointer");
        };
        const isButtonOrLinkTag = (el) => {
          const tag = el.tagName.toLowerCase();
          return tag === "button" || tag === "a";
        };
        const isInsideInteractiveElement = (() => {
          let parent = element.parentElement;
          while (parent && parent !== document.body) {
            if (isButtonOrLinkTag(parent) || hasPointerCursor(parent)) {
              return true;
            }
            parent = parent.parentElement;
          }
          return false;
        })();
        const isInteractiveElement = isButtonOrLinkTag(element) || hasPointerCursor(element) && !isInsideInteractiveElement;
        const isBasicallyInteractive = window.__snapshot.interactive.isInteractiveElement(element) || hasInteractiveAttributes || hasInteractiveClass;
        if (!isBasicallyInteractive || isParentBody) {
          return false;
        }
        if (isInteractiveElement) return true;
        if (isInsideInteractiveElement) return false;
        return true;
      };
      let colorIndex = 0;
      let elementIndex = 0;
      const processedUuids = /* @__PURE__ */ new Set();
      if (groups && groups.length > 0) {
        groups.forEach((group) => {
          if (group.bounds) {
            const labelNumber = elementIndex + 1;
            if (group.bounds.left < 0 || group.bounds.top < 0 || group.bounds.left > window.innerWidth || group.bounds.top > window.innerHeight || group.bounds.left + group.bounds.width < 0 || group.bounds.top + group.bounds.height < 0) {
              return;
            }
            bounds.push({
              top: group.bounds.top,
              left: group.bounds.left,
              width: group.bounds.width,
              height: group.bounds.height,
              uuid: group.label,
              isGroup: true,
              color: colors[colorIndex % colors.length] || "#007acc",
              label: labelNumber.toString()
            });
            colorIndex++;
            elementIndex++;
            group.elements.forEach((uuid) => {
              const element = window.__snapshot.uuidMap.get(uuid);
              if (element) {
                const rect = element.getBoundingClientRect();
                if (rect.width > 0 && rect.height > 0) {
                  bounds.push({
                    top: rect.top,
                    left: rect.left,
                    width: rect.width,
                    height: rect.height,
                    uuid,
                    isGroup: false,
                    color: colors[colorIndex % colors.length] || "#007acc",
                    label: ""
                  });
                }
              }
              processedUuids.add(uuid);
            });
          }
        });
        const ungroupedUuids = uuids.filter((uuid) => !processedUuids.has(uuid));
        const filteredUngroupedUuids = ungroupedUuids.filter((uuid) => {
          const element = window.__snapshot.uuidMap.get(uuid);
          if (!element) return false;
          return isIndependentInteractive(element);
        });
        filteredUngroupedUuids.forEach((uuid) => {
          const element = window.__snapshot.uuidMap.get(uuid);
          if (!element) return;
          const rect = element.getBoundingClientRect();
          if (rect.width === 0 || rect.height === 0) return;
          const labelNumber = elementIndex + 1;
          bounds.push({
            top: rect.top,
            left: rect.left,
            width: rect.width,
            height: rect.height,
            uuid,
            isGroup: false,
            color: colors[colorIndex % colors.length] || "#007acc",
            label: labelNumber.toString()
          });
          colorIndex++;
          elementIndex++;
        });
      } else {
        const filteredUuids = uuids.filter((uuid) => {
          const element = window.__snapshot.uuidMap.get(uuid);
          if (!element) return false;
          return isIndependentInteractive(element);
        });
        filteredUuids.forEach((uuid) => {
          const element = window.__snapshot.uuidMap.get(uuid);
          if (!element) return;
          const rect = element.getBoundingClientRect();
          if (rect.width === 0 || rect.height === 0) return;
          const labelNumber = elementIndex + 1;
          bounds.push({
            top: rect.top,
            left: rect.left,
            width: rect.width,
            height: rect.height,
            uuid,
            isGroup: false,
            color: colors[colorIndex % colors.length] || "#007acc",
            label: labelNumber.toString()
          });
          colorIndex++;
          elementIndex++;
        });
      }
      return bounds;
    },
    evaluateParams
  );
  const dimensions = await page2.evaluate(() => ({
    viewportWidth: window.innerWidth,
    viewportHeight: window.innerHeight,
    documentWidth: Math.max(
      document.documentElement.scrollWidth,
      window.innerWidth
    ),
    documentHeight: Math.max(
      document.documentElement.scrollHeight,
      window.innerHeight
    )
  }));
  const screenshotData = {
    width: dimensions.documentWidth,
    height: dimensions.documentHeight,
    viewportDimensions: {
      width: dimensions.viewportWidth,
      height: dimensions.viewportHeight
    }
  };
  const viewportHeight = _optionalChain([screenshotData, 'optionalAccess', _43 => _43.viewportDimensions, 'optionalAccess', _44 => _44.height]) || 800;
  const filteredElementBounds = filterNestedElements(
    elementBounds,
    viewportHeight
  );
  const annotatedScreenshot = await screenshotWithDOMAnnotations(
    page2,
    filteredElementBounds,
    true
    // full page
  );
  const labelLines = [];
  labelLines.push("Interactive elements in screenshot:");
  elementBounds.forEach((bound) => {
    if (bound.label) {
      const selector = _optionalChain([options, 'optionalAccess', _45 => _45.selectorsMap, 'optionalAccess', _46 => _46.get, 'call', _47 => _47(bound.uuid)]);
      if (selector) {
        labelLines.push(
          `Label ${bound.label} = ${bound.uuid} | selector: ${selector}`
        );
      } else {
        labelLines.push(`Label ${bound.label} = ${bound.uuid}`);
      }
    }
  });
  const labelMapping = labelLines.join("\n");
  return {
    screenshot: annotatedScreenshot,
    groups: _optionalChain([groupingResult, 'optionalAccess', _48 => _48.groups]),
    labelMapping,
    dimensions: {
      screenshot: {
        width: screenshotData.width,
        height: screenshotData.height
      },
      viewport: screenshotData.viewportDimensions
    }
  };
}

// src/mcp/recording/selector-engine.ts
var getSelectorsLightDOM = async (page2, elementUUID) => {
  const result = await page2.evaluate((elementUUID2) => {
    const ATTR_PRIORITIES = {
      id: 1,
      "data-testid": 2,
      "data-test-id": 2,
      "data-pw": 2,
      "data-cy": 2,
      "data-id": 2,
      "data-style-id": 2,
      "data-name": 3,
      name: 3,
      "aria-label": 3,
      title: 3,
      placeholder: 4,
      href: 4,
      alt: 4,
      "data-index": 5,
      "data-role": 5,
      role: 5
    };
    const IMPORTANT_ATTRS = Object.keys(ATTR_PRIORITIES);
    const _escapeSpecialCharacters = (str) => {
      return CSS.escape(str);
    };
    const getNodeSimpleSelectors = (element) => {
      const selectors = [];
      const tag = element.tagName.toLowerCase();
      const attrSelectors = IMPORTANT_ATTRS.map((attr) => {
        const value = element.getAttribute(attr);
        if (!value) return null;
        return {
          priority: ATTR_PRIORITIES[attr] || 999,
          selector: attr === "id" ? `#${_escapeSpecialCharacters(value)}` : `${tag}[${attr}="${_escapeSpecialCharacters(value)}"]`
        };
      }).filter((item) => item !== null);
      const otherSelectors = [];
      const classList = element.classList;
      if (classList.length > 0) {
        otherSelectors.push({
          priority: 100,
          selector: `${tag}.${Array.from(classList).join(".")}`
        });
      }
      const availableSelectors = [...attrSelectors, ...otherSelectors];
      availableSelectors.sort((a, b) => a.priority - b.priority);
      const topSelectors = availableSelectors.slice(0, 5);
      topSelectors.push({
        priority: 999,
        selector: tag
      });
      for (const item of topSelectors) {
        selectors.push(item.selector);
      }
      return selectors;
    };
    const _getSiblingRelationshipSelectors = (_dom, element) => {
      const selectors = [];
      const parent = element.parentElement;
      if (!parent || parent.tagName === "BODY") {
        return selectors;
      }
      const siblings = Array.from(parent.children);
      const elementIndex = siblings.indexOf(element);
      const tagName = element.tagName.toLowerCase();
      const selectorPrefixes = [];
      for (let i = 0; i < siblings.length; i++) {
        if (i === elementIndex) continue;
        const sibling = siblings[i];
        if (!sibling) continue;
        const siblingSimpleSelectors = getNodeSimpleSelectors(sibling);
        siblingSimpleSelectors.forEach((siblingSelector) => {
          selectorPrefixes.push(`${siblingSelector} ~ `);
        });
      }
      const selectorSuffixes = [tagName, ...getNodeSimpleSelectors(element)];
      selectorSuffixes.forEach((selectorSuffix) => {
        selectorPrefixes.forEach((selectorPrefix) => {
          selectors.push(`${selectorPrefix}${selectorSuffix}`);
        });
      });
      return selectors;
    };
    const _getChildRelationshipSelectors = (_dom, element) => {
      const children = [];
      const currentQueue = Array.from(element.children).map((child) => ({
        child,
        depth: 0
      }));
      while (currentQueue.length > 0) {
        const item = currentQueue.shift();
        if (!item) continue;
        const { child, depth } = item;
        if (depth > 3) {
          continue;
        }
        children.push({ child, depth });
        currentQueue.push(
          ...Array.from(child.children).map((child2) => ({
            child: child2,
            depth: depth + 1
          }))
        );
      }
      const selectorSuffixes = [];
      children.forEach(({ child, depth }) => {
        const childSelectors = getNodeSimpleSelectors(child);
        const childIndex = Array.from(element.children).indexOf(child) + 1;
        childSelectors.forEach((childSelector) => {
          if (depth === 0) {
            selectorSuffixes.push(`:has(${childSelector})`);
            selectorSuffixes.push(
              `:has(${childSelector}:nth-child(${childIndex}))`
            );
          } else {
            selectorSuffixes.push(`:has(${childSelector})`);
          }
        });
      });
      const selectorPrefixes = [
        element.tagName.toLowerCase(),
        ...getNodeSimpleSelectors(element)
      ];
      const selectors = [];
      selectorPrefixes.forEach((selectorPrefix) => {
        selectorSuffixes.forEach((selectorSuffix) => {
          selectors.push(`${selectorPrefix}${selectorSuffix}`);
        });
      });
      return selectors;
    };
    const getMatchCount = (dom, selector) => {
      try {
        return dom.querySelectorAll(selector).length;
      } catch (e6) {
        return Number.POSITIVE_INFINITY;
      }
    };
    const _getParentPathSelectors = (dom, element) => {
      const path = [];
      let current = element;
      while (current && current.tagName !== "HTML") {
        path.push(current);
        current = current.parentElement;
      }
      const logger = _optionalChain([window, 'access', _49 => _49.__qaby, 'optionalAccess', _50 => _50.logger]);
      if (logger) {
        logger.debug(
          "Path: ",
          JSON.stringify(
            {
              path: path.map((node) => node.tagName)
            },
            null,
            2
          )
        );
      }
      const nodeSelectors = path.map((node) => ({
        node,
        selectors: getNodeSimpleSelectors(node)
      }));
      if (!nodeSelectors.length) {
        return [];
      }
      const result2 = [];
      const targetNode = _optionalChain([nodeSelectors, 'access', _51 => _51[0], 'optionalAccess', _52 => _52.node]);
      const targetSelectors = _optionalChain([nodeSelectors, 'access', _53 => _53[0], 'optionalAccess', _54 => _54.selectors]);
      if (!targetNode || !targetSelectors) return [];
      const targetSelectorsWithNthChild = targetSelectors.map((selector) => {
        const index = targetNode.parentElement ? Array.from(targetNode.parentElement.children).indexOf(targetNode) + 1 : 1;
        return `${selector}:nth-child(${index})`;
      });
      const allTargetSelectors = [
        ...targetSelectors,
        ...targetSelectorsWithNthChild
      ];
      if (logger) {
        logger.debug(
          "Target Selectors: ",
          JSON.stringify(
            {
              selectors: allTargetSelectors
            },
            null,
            2
          )
        );
      }
      for (const targetSelector of allTargetSelectors) {
        const matches = getMatchCount(dom, targetSelector);
        if (matches === 0) continue;
        if (matches === 1) {
          const isStandaloneNthChild = targetSelector.includes(":nth-child(") && !targetSelector.includes(" ") && // No parent selector (descendant combinator)
          !targetSelector.includes(">") && // No parent selector (child combinator)
          targetSelector.match(/^[a-z]+(:nth-child\(\d+\))?$/i);
          if (!isStandaloneNthChild) {
            result2.push(targetSelector);
          }
        }
        let currentSelector = targetSelector;
        let currentMatches = matches;
        let lastAddedNode = targetNode;
        for (let i = 1; i < nodeSelectors.length; i++) {
          const ancestor = _optionalChain([nodeSelectors, 'access', _55 => _55[i], 'optionalAccess', _56 => _56.node]);
          const ancestorSelectors = _optionalChain([nodeSelectors, 'access', _57 => _57[i], 'optionalAccess', _58 => _58.selectors]);
          if (!ancestor || !ancestorSelectors) continue;
          let bestSelector = null;
          let bestMatches = currentMatches;
          for (const ancestorSelector of ancestorSelectors) {
            const descendantOperator = Array.from(ancestor.children).indexOf(lastAddedNode) !== -1 ? " > " : " ";
            const possibleCombinedSelectors = [
              `${ancestorSelector} ${descendantOperator} ${currentSelector}`
            ];
            if (ancestor.tagName != "BODY" && ancestor.parentElement) {
              const elementIndex = Array.from(ancestor.parentElement.children).indexOf(ancestor) + 1;
              possibleCombinedSelectors.push(
                `${ancestorSelector}:nth-child(${elementIndex}) ${descendantOperator} ${currentSelector}`
              );
            }
            if (logger) {
              logger.debug(
                "Possible Combined Selectors: ",
                JSON.stringify(
                  {
                    selectors: possibleCombinedSelectors
                  },
                  null,
                  2
                )
              );
            }
            for (const combinedSelector of possibleCombinedSelectors) {
              const newMatches = getMatchCount(dom, combinedSelector);
              if (newMatches === 0) continue;
              else if (newMatches === 1) {
                result2.push(combinedSelector);
                bestSelector = null;
              } else if (newMatches < bestMatches) {
                bestSelector = combinedSelector;
                bestMatches = newMatches;
              }
            }
          }
          if (bestSelector && bestMatches < currentMatches) {
            currentSelector = bestSelector;
            currentMatches = bestMatches;
            lastAddedNode = ancestor;
          }
        }
      }
      return result2;
    };
    const validateSelector = (document2, element, selector) => {
      try {
        const selectedElements = document2.querySelectorAll(selector);
        return selectedElements.length === 1 && selectedElements[0] === element;
      } catch (e7) {
        return false;
      }
    };
    const _getSelectors = (uuid) => {
      const element = _optionalChain([window, 'access', _59 => _59.__qaby, 'optionalAccess', _60 => _60.uuidMap, 'optionalAccess', _61 => _61.get, 'call', _62 => _62(uuid)]) || document.querySelector(`[uuid="${uuid}"]`);
      if (!element) {
        throw new Error(`Element with UUID ${uuid} not found`);
      }
      const validSelectors = [];
      const selectorGenerators = [
        () => _getParentPathSelectors(document, element),
        () => _getChildRelationshipSelectors(document, element),
        () => _getSiblingRelationshipSelectors(document, element)
      ];
      for (const generator of selectorGenerators) {
        const selectors = generator();
        for (const selector of selectors) {
          if (validateSelector(document, element, selector)) {
            validSelectors.push(selector);
            if (validSelectors.length >= 10) {
              return validSelectors;
            }
          }
        }
      }
      if (validSelectors.length === 0) {
        const absoluteSelector = (() => {
          const child_combinator = " > ";
          const node = "/";
          function indexElement(el) {
            let index = 1;
            let previousSibling = el.previousElementSibling;
            while (previousSibling) {
              if (previousSibling.nodeName.toLowerCase() === el.nodeName.toLowerCase()) {
                index++;
              }
              previousSibling = previousSibling.previousElementSibling;
            }
            return node + el.tagName.toLowerCase() + "[" + index + "]";
          }
          function getAbsoluteXpath(el) {
            const xpath = [];
            let currentElement = el;
            while (currentElement) {
              const tagName = currentElement.tagName.toLowerCase();
              if (tagName === "html" || tagName === "body") {
                break;
              }
              xpath.unshift(indexElement(currentElement));
              currentElement = currentElement.parentElement;
            }
            return xpath.join("");
          }
          function getAbsoluteCss(xpath) {
            const regex = new RegExp(node, "g");
            let cssSelector = xpath.replace(regex, child_combinator);
            cssSelector = cssSelector.replace(/\[(\d+)\]/g, ":nth-of-type($1)");
            if (cssSelector.startsWith(child_combinator)) {
              cssSelector = cssSelector.substring(child_combinator.length);
            }
            return cssSelector;
          }
          return getAbsoluteCss(getAbsoluteXpath(element));
        })();
        if (validateSelector(document, element, absoluteSelector)) {
          validSelectors.push(absoluteSelector);
        }
      }
      return validSelectors;
    };
    return _getSelectors(elementUUID2);
  }, elementUUID);
  return result;
};
var getSelectors = async (page2, elementUUID) => {
  const isInShadow = await page2.evaluate((uuid) => {
    const element = _optionalChain([window, 'access', _63 => _63.__qaby, 'optionalAccess', _64 => _64.uuidMap, 'optionalAccess', _65 => _65.get, 'call', _66 => _66(uuid)]) || document.querySelector(`[uuid="${uuid}"]`);
    if (!element) return false;
    const root = element.getRootNode();
    return root && root.nodeType === 11;
  }, elementUUID);
  if (isInShadow) {
    const lightDOMSelectors = await getSelectorsLightDOM(page2, elementUUID);
    return {
      selectors: lightDOMSelectors,
      shadowHostSelectors: null
    };
  } else {
    const lightDOMSelectors = await getSelectorsLightDOM(page2, elementUUID);
    return {
      selectors: lightDOMSelectors,
      shadowHostSelectors: null
    };
  }
};

// src/snapshot/index.ts
async function ensureSnapshotHelpers(page2) {
  const isInjected = await page2.evaluate(() => {
    return typeof window.__snapshot !== "undefined";
  });
  if (!isInjected) {
    await page2.evaluate(injectSnapshotHelpers);
  }
}
async function createSnapshot(page2) {
  await ensureSnapshotHelpers(page2);
  await addUUIDsToPage(page2);
  const interactiveElements = await extractInteractiveElements(page2);
  const allInteractiveElements = await page2.evaluate(() => {
    if (!window.__snapshot) {
      throw new Error("Snapshot helpers not injected");
    }
    const uuids = [];
    window.__snapshot.uuidMap.forEach((element, uuid) => {
      if (window.__snapshot.interactive.isInteractiveElement(element) && window.__snapshot.visibility.isScrollableIntoView(element)) {
        uuids.push(uuid);
      }
    });
    return Array.from(new Set(uuids));
  });
  const selectorsMap = /* @__PURE__ */ new Map();
  for (const uuid of allInteractiveElements) {
    try {
      const selectorResult = await getSelectors(page2, uuid);
      if (selectorResult.selectors && selectorResult.selectors.length > 0) {
        selectorsMap.set(uuid, selectorResult.selectors[0]);
      }
    } catch (error) {
      console.warn(`Failed to generate selector for UUID ${uuid}:`, error);
    }
  }
  await page2.evaluate((selectorEntries) => {
    if (!window.__snapshot) {
      throw new Error("Snapshot helpers not injected");
    }
    window.__snapshot.selectorsMap = new Map(selectorEntries);
  }, Array.from(selectorsMap.entries()));
  const semanticTree = await buildSemanticTree(page2, {
    filterByUuids: allInteractiveElements,
    excludeNonScrollableIntoView: false
  });
  const serializedTree = serializeSemanticNode(semanticTree);
  const result = await captureScreenshotWithBoundingBoxes(
    page2,
    allInteractiveElements,
    {
      includeAllInteractiveForGroups: true,
      allInteractiveElements,
      selectorsMap
    }
  );
  return {
    url: page2.url(),
    title: await page2.title(),
    semanticTree: serializedTree,
    screenshot: result.screenshot.toString("base64"),
    labelMapping: result.labelMapping || []
  };
}

// src/snapshot/full-snapshot.ts
async function ensureSnapshotHelpers2(page2) {
  const isInjected = await page2.evaluate(() => {
    return typeof window.__snapshot !== "undefined";
  });
  if (!isInjected) {
    await page2.evaluate(injectSnapshotHelpers);
  }
}
async function createFullSnapshot(page2) {
  await ensureSnapshotHelpers2(page2);
  await addUUIDsToPage(page2);
  const semanticTree = await buildSemanticTree(page2, {
    excludeNonVisible: true,
    excludeNonScrollableIntoView: false,
    excludeNonInteractive: false,
    excludeNonTopElements: false,
    hierarchy: "full"
  });
  const serializedTree = serializeSemanticNode(semanticTree, {
    skipHierarchyNodeContent: false,
    skipNonVisibleElements: true,
    includeUuid: "all",
    includeRole: true,
    includeAttributes: true,
    includeDeductedName: true,
    includeAdditionalInfo: true,
    includeInteractive: true,
    includeVisibility: true
  });
  const screenshot = await page2.screenshot({ type: "png" });
  return {
    url: page2.url(),
    title: await page2.title(),
    semanticTree: serializedTree,
    screenshot: screenshot.toString("base64"),
    labelMapping: []
  };
}

// src/snapshot/text-snapshot.ts
async function ensureSnapshotHelpers3(page2) {
  const isInjected = await page2.evaluate(() => {
    return typeof window.__snapshot !== "undefined";
  });
  if (!isInjected) {
    await page2.evaluate(injectSnapshotHelpers);
  }
}
async function createTextSnapshot(page2) {
  await ensureSnapshotHelpers3(page2);
  await addUUIDsToPage(page2);
  const textElements = await page2.evaluate(() => {
    if (!window.__snapshot) {
      throw new Error("Snapshot helpers not injected");
    }
    const TEXT_TAGS = /* @__PURE__ */ new Set([
      "p",
      "span",
      "div",
      "h1",
      "h2",
      "h3",
      "h4",
      "h5",
      "h6",
      "li",
      "td",
      "th",
      "dt",
      "dd",
      "blockquote",
      "pre",
      "code",
      "article",
      "section",
      "main",
      "label",
      "caption",
      "figcaption"
    ]);
    const uuids = [];
    window.__snapshot.uuidMap.forEach((element, uuid) => {
      const tagName = element.tagName.toLowerCase();
      const hasText = element.textContent && element.textContent.trim().length > 0;
      const isTextElement = TEXT_TAGS.has(tagName) || element.getAttribute("role") === "heading" || element.getAttribute("role") === "text";
      if (hasText && (isTextElement || element.childElementCount === 0)) {
        uuids.push(uuid);
      }
    });
    return Array.from(new Set(uuids));
  });
  const semanticTree = await buildSemanticTree(page2, {
    filterByUuids: textElements,
    excludeNonVisible: true,
    hierarchy: "minimal"
  });
  const serializedTree = serializeSemanticNode(semanticTree, {
    skipHierarchyNodeContent: false,
    skipNonVisibleElements: true,
    includeUuid: "none",
    includeRole: false,
    includeAttributes: false,
    includeDeductedName: false,
    includeAdditionalInfo: false,
    includeInteractive: false,
    includeVisibility: false,
    maxNodeTextLength: 5e3
  });
  const screenshot = await page2.screenshot({ type: "png" });
  return {
    url: page2.url(),
    title: await page2.title(),
    semanticTree: serializedTree,
    screenshot: screenshot.toString("base64"),
    labelMapping: []
  };
}

// src/mcp/index.ts
var browser;
var context;
var page;
var server = new (0, _mcpjs.McpServer)({
  name: "playwright",
  version: "1.0.0"
});
var clientInfoStored = false;
var originalConnect = server.connect.bind(server);
server.connect = async function(transport) {
  const originalOnMessage = transport.onmessage;
  transport.onmessage = (message) => {
    try {
      const parsed = typeof message === "string" ? JSON.parse(message) : message;
      if (parsed.method === "initialize" && _optionalChain([parsed, 'access', _67 => _67.params, 'optionalAccess', _68 => _68.clientInfo]) && !clientInfoStored) {
        setMcpClientInfo({
          name: parsed.params.clientInfo.name,
          version: parsed.params.clientInfo.version
        });
        clientInfoStored = true;
      }
    } catch (e) {
    }
    if (originalOnMessage) {
      originalOnMessage(message);
    }
  };
  return originalConnect(transport);
};
server.registerPrompt(
  "server-flow",
  {
    title: "Server Flow",
    description: "Get prompt on how to use this MCP server",
    argsSchema: {}
  },
  () => {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `# DON'T ASSUME ANYTHING. Whatever you write in code, it must be found in the context. Otherwise leave comments.

## Goal
Help me write playwright code with following functionalities:
- [[add semi-high level functionality you want here]]
- [[more]]
- [[more]]
- [[more]]

## Reference
- Use @x, @y files if you want to take reference on how I write POM code

## Steps
- First fetch the context from 'get-context' tool, until it returns no elements remaining
- Based on context and user functionality, write code in POM format, encapsulating high level functionality into reusable functions
- Try executing code using 'execute-code' tool. You could be on any page, so make sure to navigate to the correct page
- Write spec file using those reusable functions, covering multiple scenarios
`
          }
        }
      ]
    };
  }
);
server.registerPrompt(
  "create-testcase",
  {
    title: "Create Testcase",
    description: "Create a new testcase with iterative development workflow",
    argsSchema: {}
  },
  () => {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `# Create New Testcase - Iterative Development Workflow

## Goal
Create a comprehensive testcase by building and validating code iteratively. Test each step before moving to the next.

## Prerequisites
1. **URL**: User must provide the platform URL to test
2. **Auth/Login Info**: Search the user's codebase for login credentials, auth patterns, or existing test data based on user's description
3. **Test Objective**: Clear description of what functionality to test

## Workflow
Follow this iterative approach - validate each step before proceeding:

### Step 1: Initialize & Navigate
- Use \`init-browser\` with the provided URL
- Take a screenshot to confirm page loaded correctly

### Step 2: Discover Interactive Elements  
- Use \`get-interactive-snapshot\` to see all clickable/interactive elements
- Identify auth-related elements (login buttons, forms, etc.)

### Step 3: Build Code Incrementally
For each interaction needed:
- Write small code snippet for ONE action (click, fill, etc.)
- Use \`execute-code\` to test the snippet immediately
- If it works, add to your growing test code
- If it fails, debug and fix before moving on
- Take screenshots after major actions to verify state

### Step 4: Handle Authentication
- Look for login forms, auth buttons, or existing session handling
- Use codebase patterns if found, otherwise build step-by-step
- Validate each auth step works before proceeding

### Step 5: Test Core Functionality
- Continue iterative approach: write \u2192 test \u2192 validate \u2192 accumulate
- Use \`get-interactive-snapshot\` whenever you need to see current page state
- If you don't get element here, call \`get-full-snapshot\` but try to avoid it because it's too large generally
- Build up your working test code piece by piece

### Step 6: Create Final Test Structure
- Organize all working code snippets into a complete test
- Add proper assertions and error handling
- Include setup and cleanup steps

## Key Principles
- **Test EVERY code snippet** before adding to final test
- **Never assume** - always verify with \`execute-code\` 
- **Build incrementally** - one working step at a time
- **Use snapshots** to understand current page state
- **Accumulate working code** as you validate each piece

## Expected Output
A complete, tested Playwright testcase that successfully achieves the user's testing objective.`
          }
        }
      ]
    };
  }
);
server.registerPrompt(
  "debug-testcase",
  {
    title: "Debug Testcase",
    description: "Debug and fix an existing testcase",
    argsSchema: {}
  },
  () => {
    return {
      messages: [
        {
          role: "user",
          content: {
            type: "text",
            text: `# Debug Existing Testcase

## Goal  
Fix a failing testcase by identifying issues and applying targeted fixes using iterative testing.

## Prerequisites
1. **Testcase Code**: User provides the failing test code
2. **Error Description**: What's failing or expected vs actual behavior
3. **Platform URL**: Where the test should run

## Workflow

### Step 1: Understand the Failure
- Review the provided testcase code
- Understand what it's supposed to do vs what's happening
- Identify the specific failure point

### Step 2: Set Up Environment
- Use \`init-browser\` to navigate to the test URL
- Take initial screenshot to see current state

### Step 3: Execute and Locate Failure Point
- Run the existing testcase using \`execute-code\`
- Note exactly where it fails (line/step)
- Use \`get-interactive-snapshot\` to see current page state at failure point
- If you don't get element here, call \`get-full-snapshot\` but try to avoid it because it's too large generally

### Step 4: Incremental Debugging
For the failing section:
- Break down the failing part into smaller steps
- Test each small step with \`execute-code\`
- Use snapshots to understand page state changes
- Identify root cause (element not found, wrong selector, timing issue, etc.)

### Step 5: Apply Targeted Fixes
- Fix the specific issue (update selectors, add waits, etc.)
- Test the fix in isolation with \`execute-code\`
- Verify it works before integrating back

### Step 6: Test Complete Flow  
- Run the entire fixed testcase to ensure no regressions
- Verify all steps work end-to-end
- Take screenshots at key points to confirm expected behavior

## Common Debug Patterns
- **Selector Issues**: Use \`get-interactive-snapshot\` to find correct selectors
- **Timing Issues**: Add proper waits and verify element visibility
- **Page State**: Check if page state changed (new UI, different flow)
- **Auth Problems**: Verify login/session handling still works

## Expected Output
A corrected testcase that passes all steps and achieves the original testing objective.`
          }
        }
      ]
    };
  }
);
server.tool(
  "init-browser",
  "Initialize a browser with a URL",
  {
    url: _zod.z.string().url().describe("The URL to navigate to")
  },
  async ({ url }) => {
    capture({
      event: "init_browser",
      properties: {
        url
      }
    });
    if (context) {
      await context.close();
    }
    if (browser) {
      await browser.close();
    }
    browser = await _playwright.chromium.launch({
      headless: false,
      args: [
        "--disable-web-security",
        "--disable-features=VizDisplayCompositor"
      ]
    });
    context = await browser.newContext({
      viewport: null,
      userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
      bypassCSP: true
    });
    page = await context.newPage();
    await page.exposeFunction("takeScreenshot", async (selector) => {
      try {
        const screenshot = await page.locator(selector).screenshot({
          timeout: 5e3
        });
        return screenshot.toString("base64");
      } catch (error) {
        console.error("Error taking screenshot", error);
        return null;
      }
    });
    await page.exposeFunction("executeCode", async (code) => {
      const result = await secureEvalAsync(page, code);
      return result;
    });
    await page.goto(url);
    await page.addInitScript(`(${_chunkV6YYWYKPcjs.injectDiscordButton.toString()})()`);
    return {
      content: [
        {
          type: "text",
          text: `Browser has been initialized and navigated to ${url}`
        }
      ]
    };
  }
);
server.tool(
  "get-full-dom",
  "Get the full DOM of the current page. (Deprecated, use get-context instead)",
  {},
  async () => {
    capture({
      event: "get_full_dom"
    });
    const html = await page.content();
    return {
      content: [
        {
          type: "text",
          text: html
        }
      ]
    };
  }
);
server.tool(
  "get-screenshot",
  "Get a screenshot of the current page",
  {},
  async () => {
    capture({
      event: "get_screenshot"
    });
    const screenshot = await page.screenshot({
      type: "png"
    });
    return {
      content: [
        {
          type: "image",
          data: screenshot.toString("base64"),
          mimeType: "image/png"
        }
      ]
    };
  }
);
server.tool(
  "execute-code",
  "Execute custom Playwright JS code against the current page",
  {
    code: _zod.z.string().describe(`The Playwright code to execute. Must be an async function declaration that takes a page parameter.

Example:
async function run(page) {
  console.log(await page.title());
  return await page.title();
}

Returns an object with:
- result: The return value from your function
- logs: Array of console logs from execution
- errors: Array of any errors encountered

Example response:
{"result": "Google", "logs": ["[log] Google"], "errors": []}`)
  },
  async ({ code }) => {
    capture({
      event: "execute_code",
      properties: {
        code: code.length > 1e3 ? code.substring(0, 1e3) + "..." : code,
        codeLength: code.length,
        pageUrl: page.url()
      }
    });
    const result = await secureEvalAsync(page, code);
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(result, null, 2)
          // Pretty print the JSON
        }
      ]
    };
  }
);
server.tool(
  "get-interactive-snapshot",
  "Get a snapshot focused on interactive elements (buttons, links, inputs) with annotated screenshot for UI automation",
  {},
  async () => {
    capture({
      event: "get_interactive_snapshot"
    });
    try {
      const snapshot = await createSnapshot(page);
      return {
        content: [
          {
            type: "text",
            text: `# Interactive Elements Snapshot

URL: ${snapshot.url}
Title: ${snapshot.title}

## Interactive Elements Tree

${snapshot.semanticTree}`
          },
          {
            type: "image",
            data: snapshot.screenshot,
            mimeType: "image/png"
          },
          {
            type: "text",
            text: `## Label Mapping

${JSON.stringify(snapshot.labelMapping, null, 2)}`
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error creating interactive snapshot: ${error instanceof Error ? error.message : String(error)}`
          }
        ]
      };
    }
  }
);
server.tool(
  "get-full-snapshot",
  "Get a complete snapshot of the page including all visible content (text, images, forms, etc.) for understanding the full context",
  {},
  async () => {
    capture({
      event: "get_full_snapshot"
    });
    try {
      const snapshot = await createFullSnapshot(page);
      return {
        content: [
          {
            type: "text",
            text: `# Full Page Snapshot

URL: ${snapshot.url}
Title: ${snapshot.title}

## Complete Page Structure

${snapshot.semanticTree}`
          },
          {
            type: "image",
            data: snapshot.screenshot,
            mimeType: "image/png"
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error creating full snapshot: ${error instanceof Error ? error.message : String(error)}`
          }
        ]
      };
    }
  }
);
server.tool(
  "get-text-snapshot",
  "Get all text content from the page (headings, paragraphs, lists) for reading and content extraction",
  {},
  async () => {
    capture({
      event: "get_text_snapshot"
    });
    try {
      const snapshot = await createTextSnapshot(page);
      return {
        content: [
          {
            type: "text",
            text: `# Text Content Snapshot

URL: ${snapshot.url}
Title: ${snapshot.title}

## Page Text Content

${snapshot.semanticTree}`
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error creating text snapshot: ${error instanceof Error ? error.message : String(error)}`
          }
        ]
      };
    }
  }
);



exports.server = server;