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
JavaScript
/**
* 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