// Helper function to check if element is clickable function isClickable(element) { // Check tag name if (element.tagName === 'BUTTON' || element.tagName === 'A' || (element.tagName === 'INPUT' && element.type === 'submit')) { return true; } // Check ARIA role if (element.getAttribute('role') === 'button') { return true; } // Check for click handlers if (element.onclick || element.getAttribute('onclick') || element.getAttribute('ng-click') || element.getAttribute('v-on:click')) { return true; } // Check computed style const style = window.getComputedStyle(element); if (style.cursor === 'pointer') { return true; } return false; } // Helper function to check if element is an input function isInputElement(element) { const inputTags = ['INPUT', 'TEXTAREA', 'SELECT']; if (inputTags.includes(element.tagName)) { return true; } if (element.getAttribute('contenteditable') === 'true') { return true; } if (element.getAttribute('role') === 'textbox' || element.getAttribute('role') === 'combobox') { return true; } return false; } // Helper function to check if element is scrollable function isScrollable(element) { const style = window.getComputedStyle(element); // Check for scroll overflow if (style.overflow === 'auto' || style.overflow === 'scroll' || style.overflowY === 'auto' || style.overflowY === 'scroll' || style.overflowX === 'auto' || style.overflowX === 'scroll') { // Verify element has scrollable content return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; } return false; } // Helper function to check if element is actually visible function isElementVisible(element) { const style = window.getComputedStyle(element); // Special handling for fixed position elements if (style.position === 'fixed') { // Fixed elements are always considered visible if they have dimensions if (element.offsetWidth > 0 && element.offsetHeight > 0) { return true; } } // For non-fixed elements, check offsetParent if (!element.offsetParent && element !== document.body) { return false; } if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { return false; } const rect = element.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) { return false; } // For non-fixed elements, check viewport boundaries if (style.position !== 'fixed') { const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; if (rect.right < 0 || rect.bottom < 0 || rect.left > viewportWidth || rect.top > viewportHeight) { return false; } } return true; } // Helper function to get element text function getElementText(element) { // Only process direct text nodes const textNodes = Array.from(element.childNodes) .filter(node => node.nodeType === Node.TEXT_NODE); if (textNodes.length === 0) return ''; return textNodes .map(node => node.textContent.trim()) .filter(text => text.length > 0) .join(' '); } function processElements(element) { if (!element || !isElementVisible(element)) return []; const rect = element.getBoundingClientRect(); const styles = window.getComputedStyle(element); const isFixed = styles.position === 'fixed'; // Get element base info const elementInfo = { tag: element.tagName.toLowerCase(), x: Math.round(isFixed ? rect.left : rect.left + window.pageXOffset), y: Math.round(isFixed ? rect.top : rect.top + window.pageYOffset), width: Math.round(rect.width), height: Math.round(rect.height), interactions: [], fixed: isFixed }; // Add ID or suggested click coordinates for any interactive element if (isClickable(element) || isInputElement(element)) { const id = element.id; if (id) { elementInfo.id = id; } else { // Only add suggested click if there's no ID elementInfo.suggestedClick = { x: Math.round(elementInfo.x + elementInfo.width / 2), y: Math.round(elementInfo.y + elementInfo.height / 2) }; } } // Add text only if this isn't going to be an interactive element const isInteractive = isClickable(element) || isInputElement(element); if (!isInteractive) { elementInfo.text = getElementText(element); } // Add interactions if (isClickable(element)) { elementInfo.interactions.push('click'); elementInfo.buttonText = getElementText(element); } if (isInputElement(element)) { const inputType = element.getAttribute('type') || 'text'; elementInfo.interactions.push(`input:${inputType}`); const placeholder = element.getAttribute('placeholder'); if (placeholder) { elementInfo.placeholder = placeholder; } } if (isScrollable(element)) { elementInfo.interactions.push('scroll'); } // Special handling for images if (element.tagName === 'IMG') { elementInfo.src = element.src; elementInfo.alt = element.alt; } // Process children elementInfo.children = Array.from(element.children) .map(child => processElements(child)) .flat() .filter(child => child !== null); return elementInfo; } function analyzePageContent() { const body = document.body; return { viewport: getViewportInfo(), content: body ? processElements(body) : [] }; }