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

227 lines 9.69 kB
/** * Flutter Navigation Helper * Handles navigation challenges in Flutter web apps */ import { BrowserPermissionHandler } from './browser-permission-handler.js'; export class FlutterNavigationHelper { /** * Prepare the page for Flutter navigation */ static async prepareForNavigation(page) { // 1. Grant permissions proactively await BrowserPermissionHandler.configurePermissions(page, { permissions: ['geolocation', 'camera', 'microphone', 'notifications'], geolocation: { latitude: 47.6062, // Seattle longitude: -122.3321, accuracy: 10 }, autoAcceptDialogs: true }); // 2. Remove any visual overlays that might interfere await this.removeVisualOverlays(page); // 3. Wait for Flutter to be fully initialized await this.waitForFlutterReady(page); } /** * Remove debugging overlays that might block clicks */ static async removeVisualOverlays(page) { await page.evaluate(() => { // Remove our debugging overlay const overlays = document.querySelectorAll('.flutter-debug-overlay'); overlays.forEach(el => el.remove()); // Remove the canvas overlay created by flutter-comprehensive-detector const canvasOverlay = document.getElementById('flutter-detector-overlay'); if (canvasOverlay) { canvasOverlay.remove(); } // Remove any other potential blocking elements const debugElements = document.querySelectorAll('[id*="debug"], [class*="debug-overlay"]'); debugElements.forEach(el => { if (el instanceof HTMLElement) { el.style.pointerEvents = 'none'; } }); // Ensure Flutter canvas can receive clicks const flutterView = document.querySelector('flutter-view'); const canvas = document.querySelector('canvas'); if (flutterView instanceof HTMLElement) { flutterView.style.pointerEvents = 'auto'; } if (canvas instanceof HTMLElement) { canvas.style.pointerEvents = 'auto'; } }); } /** * Wait for Flutter to be fully ready */ static async waitForFlutterReady(page) { return await page.evaluate(() => { return new Promise((resolve) => { let attempts = 0; const maxAttempts = 50; // 5 seconds const checkReady = () => { attempts++; // Check if Flutter is initialized const hasFlutter = !!window.__FLUTTER_DEBUG__ || document.querySelector('flt-scene-host') !== null || document.querySelector('flutter-view') !== null; // Check if semantic nodes are present const hasSemantics = document.querySelectorAll('[role]').length > 10; if (hasFlutter && hasSemantics) { resolve(true); } else if (attempts >= maxAttempts) { resolve(false); } else { setTimeout(checkReady, 100); } }; checkReady(); }); }); } /** * Perform a navigation click with enhanced reliability */ static async performNavigationClick(page, x, y, options = {}) { // Remove overlays before clicking await this.removeVisualOverlays(page); // Capture initial state for comparison const initialUrl = page.url(); const initialContent = await page.evaluate(() => document.body.textContent?.trim() || ''); // Perform the click with a longer hold duration const clickSuccess = await page.evaluate(async ({ x, y }) => { const flutterView = document.querySelector('flutter-view'); const canvas = document.querySelector('canvas'); const target = flutterView || canvas || document.elementFromPoint(x, y); if (!target) return false; // Create a more authentic click sequence const pointerDown = new PointerEvent('pointerdown', { clientX: x, clientY: y, bubbles: true, cancelable: true, pointerId: 1, pointerType: 'mouse', pressure: 1 }); const pointerUp = new PointerEvent('pointerup', { clientX: x, clientY: y, bubbles: true, cancelable: true, pointerId: 1, pointerType: 'mouse', pressure: 0 }); const click = new MouseEvent('click', { clientX: x, clientY: y, bubbles: true, cancelable: true }); // Dispatch events with delays to mimic human interaction target.dispatchEvent(pointerDown); await new Promise(resolve => setTimeout(resolve, 50)); // Hold for 50ms target.dispatchEvent(pointerUp); await new Promise(resolve => setTimeout(resolve, 10)); target.dispatchEvent(click); return true; }, { x, y }); if (options.waitForNavigation && clickSuccess) { // Wait for potential navigation await page.waitForTimeout(2000); // Check for both URL and content changes const navigationChanges = await this.checkForNavigationChanges(page, initialUrl, initialContent); // console.log('Navigation check results:', { // clicked: { x, y }, // urlChanged: navigationChanges.urlChanged, // contentChanged: navigationChanges.contentChanged, // currentUrl: page.url() // }); if (!navigationChanges.urlChanged && !navigationChanges.contentChanged) { // console.log('⚠️ No navigation detected after click. Overlays may still be interfering.'); } } return clickSuccess; } /** * Try alternative navigation strategies */ static async tryAlternativeNavigation(page) { // Strategy 1: Try clicking the Report sidebar button first // console.log('Trying alternative navigation: Click Report in sidebar'); const reportButton = await page.evaluate(() => { const buttons = Array.from(document.querySelectorAll('button')); const reportBtn = buttons.find(btn => btn.textContent?.trim() === 'Report'); if (reportBtn) { const rect = reportBtn.getBoundingClientRect(); return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }; } return null; }); if (reportButton) { await this.performNavigationClick(page, reportButton.x, reportButton.y, { waitForNavigation: true }); return true; } return false; } /** * Check for navigation changes (URL or content) */ static async checkForNavigationChanges(page, initialUrl, initialContent) { const currentUrl = page.url(); const urlChanged = currentUrl !== initialUrl; // Get current visible text content const currentContent = await page.evaluate(() => { // Get text from the main content area, excluding navigation const mainContent = document.querySelector('main') || document.querySelector('[role="main"]') || document.body; return mainContent?.textContent?.trim() || ''; }); const contentChanged = initialContent ? currentContent !== initialContent : false; // Check if any modal or dialog appeared const hasModal = await page.evaluate(() => { return !!(document.querySelector('[role="dialog"]') || document.querySelector('.modal') || document.querySelector('[aria-modal="true"]')); }); return { urlChanged, contentChanged: contentChanged || hasModal, newUrl: urlChanged ? currentUrl : undefined, visibleTextChanged: contentChanged }; } /** * Diagnose navigation issues */ static async diagnoseNavigationIssues(page) { return await page.evaluate(() => { // Check permissions const hasPermissions = !!(navigator.permissions && navigator.geolocation); // Check for overlays const overlays = document.querySelectorAll('.flutter-debug-overlay, [class*="debug"], #flutter-detector-overlay'); const hasOverlays = overlays.length > 0; // Check console errors const hasErrors = !!window.__CONSOLE_ERRORS__ && window.__CONSOLE_ERRORS__.length > 0; // Check Flutter state const flutterReady = !!window.__FLUTTER_DEBUG__ || document.querySelector('flutter-view') !== null; // Get current route from URL hash const currentRoute = window.location.hash; return { hasPermissions, hasOverlays, hasErrors, flutterReady, currentRoute }; }); } } //# sourceMappingURL=flutter-navigation-helper.js.map