UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

217 lines 10.3 kB
/** * Phoenix LiveView Screenshot Fix * * Addresses the 1x1 pixel screenshot issue by properly waiting for * LiveView to fully render before taking screenshots. */ export class PhoenixLiveViewScreenshotFix { /** * Take a proper screenshot of a Phoenix LiveView page * Addresses the 1x1 pixel issue by ensuring the page is fully loaded */ static async takeScreenshot(page, options = {}) { const { fullPage = true, selector, timeout = 30000 } = options; try { // Step 1: Ensure viewport has reasonable size const viewport = await page.viewportSize(); if (!viewport || viewport.width < 100 || viewport.height < 100) { console.error('🔧 Setting default viewport size to avoid 1x1 issue'); await page.setViewportSize({ width: 1280, height: 720 }); } // Step 2: Wait for Phoenix LiveView to be ready const hasLiveView = await page.evaluate(() => { return !!window.liveSocket || !!document.querySelector('[data-phx-main]'); }); if (hasLiveView) { console.error('⚡ Phoenix LiveView detected, waiting for full initialization...'); // Wait for LiveSocket connection await page.waitForFunction(() => { if (window.liveSocket && window.liveSocket.isConnected) { return window.liveSocket.isConnected(); } // Fallback: check for phx-connected attribute return document.body?.getAttribute('phx-connected') === 'true'; }, { timeout: timeout / 2 }).catch(() => { console.error('⚠️ LiveSocket connection check timed out, proceeding anyway'); }); // Wait for any pending LiveView updates await page.waitForFunction(() => { // Check if there are no pending phx events const pendingElements = document.querySelectorAll('[phx-pending]'); return pendingElements.length === 0; }, { timeout: 5000 }).catch(() => { console.error('⚠️ Some LiveView updates might still be pending'); }); // Additional wait for animations/transitions await page.waitForTimeout(500); } // Step 3: Ensure page has actual content const dimensions = await page.evaluate(() => { return { documentWidth: document.documentElement.scrollWidth, documentHeight: document.documentElement.scrollHeight, bodyWidth: document.body.scrollWidth, bodyHeight: document.body.scrollHeight, hasContent: (document.body.textContent || '').trim().length > 0 }; }); if (dimensions.documentHeight < 10 || dimensions.documentWidth < 10) { console.error('⚠️ Page dimensions are too small:', dimensions); console.error('🔄 Waiting for content to render...'); // Force a re-render by scrolling await page.evaluate(() => window.scrollTo(0, 0)); await page.waitForTimeout(1000); // Try to wait for any specific content await page.waitForSelector('body > *', { timeout: 5000 }).catch(() => { }); } // Step 4: Take the screenshot with retry logic let screenshotBuffer = Buffer.alloc(0); let attempts = 0; const maxAttempts = 3; while (attempts < maxAttempts) { attempts++; try { if (selector) { // Ensure the selector exists and is visible await page.waitForSelector(selector, { state: 'visible', timeout: 5000 }); const element = await page.$(selector); if (!element) { throw new Error(`Selector "${selector}" not found`); } screenshotBuffer = await element.screenshot({ type: 'png' }); } else { screenshotBuffer = await page.screenshot({ fullPage, type: 'png' }); } // Verify the screenshot is not 1x1 // PNG header + minimal content should be > 100 bytes if (screenshotBuffer.length < 100) { throw new Error(`Screenshot too small (${screenshotBuffer.length} bytes), likely 1x1 pixel`); } break; // Success } catch (error) { console.error(`⚠️ Screenshot attempt ${attempts} failed:`, error.message); if (attempts < maxAttempts) { console.error('🔄 Retrying after additional wait...'); await page.waitForTimeout(2000); // Try to trigger a re-render await page.evaluate(() => { window.dispatchEvent(new Event('resize')); document.body.style.display = 'none'; document.body.offsetHeight; // Force reflow document.body.style.display = ''; }); } else { throw error; } } } // Step 5: Get actual dimensions from the screenshot // For now, we'll get page dimensions as a proxy const finalDimensions = await page.evaluate(() => { const rect = document.documentElement.getBoundingClientRect(); return { width: Math.max(rect.width, window.innerWidth), height: Math.max(rect.height, window.innerHeight) }; }); // Step 6: Collect debug info const debugInfo = await page.evaluate(() => { return { url: window.location.href, title: document.title, readyState: document.readyState, liveViewConnected: window.liveSocket?.isConnected?.() || false, elementCount: document.querySelectorAll('*').length, hasPhxMain: !!document.querySelector('[data-phx-main]'), viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight }; }); console.error('✅ Screenshot captured successfully:', { size: `${screenshotBuffer.length} bytes`, dimensions: `${finalDimensions.width}x${finalDimensions.height}`, liveView: debugInfo.liveViewConnected ? 'connected' : 'not connected' }); return { data: screenshotBuffer.toString('base64'), width: finalDimensions.width, height: finalDimensions.height, debugInfo }; } catch (error) { console.error('❌ Phoenix LiveView screenshot failed:', error); // Last resort: return a diagnostic image const diagnosticHtml = ` <html> <body style="padding: 20px; font-family: monospace;"> <h2>Screenshot Failed</h2> <p>Error: ${error.message}</p> <p>URL: ${page.url()}</p> <p>This is a diagnostic image. The actual page screenshot failed.</p> <details> <summary>Debug Info</summary> <pre>${JSON.stringify({ error: error.message, url: page.url(), timestamp: new Date().toISOString() }, null, 2)}</pre> </details> </body> </html> `; await page.setContent(diagnosticHtml); const diagnosticScreenshot = await page.screenshot({ type: 'png' }); return { data: diagnosticScreenshot.toString('base64'), width: 800, height: 600, debugInfo: { error: error instanceof Error ? error.message : String(error), diagnostic: true } }; } } /** * Wait for Phoenix LiveView to be fully loaded */ static async waitForLiveView(page, timeout = 10000) { try { // Method 1: Wait for LiveSocket connection const hasLiveSocket = await page.waitForFunction(() => window.liveSocket?.isConnected?.() === true, { timeout: timeout / 2 }).then(() => true).catch(() => false); if (hasLiveSocket) { console.error('✅ LiveSocket connected'); return true; } // Method 2: Wait for phx-connected attribute const hasPhxConnected = await page.waitForFunction(() => document.body.getAttribute('phx-connected') === 'true', { timeout: timeout / 2 }).then(() => true).catch(() => false); if (hasPhxConnected) { console.error('✅ Phoenix connected (phx-connected)'); return true; } // Method 3: Check for any LiveView elements const hasLiveViewElements = await page.evaluate(() => { return document.querySelector('[data-phx-main], [data-phx-session]') !== null; }); return hasLiveViewElements; } catch { return false; } } } // Type augmentation for window.liveSocket // Window.liveSocket type already defined elsewhere //# sourceMappingURL=phoenix-liveview-screenshot-fix.js.map