366 lines
12 KiB
Python
366 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Browser Action Debugging Utility
|
|
|
|
This utility helps debug browser automation issues by:
|
|
1. Testing MCP server connectivity
|
|
2. Validating browser state
|
|
3. Testing selector discovery and execution
|
|
4. Providing detailed logging for troubleshooting
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
import json
|
|
import sys
|
|
from typing import Dict, Any, List
|
|
from mcp_chrome_client import MCPChromeClient
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.StreamHandler(sys.stdout),
|
|
logging.FileHandler('browser_debug.log')
|
|
]
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BrowserActionDebugger:
|
|
"""Debug utility for browser automation issues"""
|
|
|
|
def __init__(self, config: Dict[str, Any]):
|
|
self.config = config
|
|
self.client = MCPChromeClient(config)
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
async def run_full_diagnostic(self) -> Dict[str, Any]:
|
|
"""Run a comprehensive diagnostic of browser automation"""
|
|
results = {
|
|
"connectivity": None,
|
|
"browser_state": None,
|
|
"page_content": None,
|
|
"interactive_elements": None,
|
|
"selector_tests": [],
|
|
"action_tests": []
|
|
}
|
|
|
|
try:
|
|
# Test 1: MCP Server Connectivity
|
|
self.logger.info("🔍 TEST 1: Testing MCP server connectivity...")
|
|
results["connectivity"] = await self._test_connectivity()
|
|
|
|
# Test 2: Browser State
|
|
self.logger.info("🔍 TEST 2: Checking browser state...")
|
|
results["browser_state"] = await self._test_browser_state()
|
|
|
|
# Test 3: Page Content
|
|
self.logger.info("🔍 TEST 3: Getting page content...")
|
|
results["page_content"] = await self._test_page_content()
|
|
|
|
# Test 4: Interactive Elements
|
|
self.logger.info("🔍 TEST 4: Finding interactive elements...")
|
|
results["interactive_elements"] = await self._test_interactive_elements()
|
|
|
|
# Test 5: Selector Generation
|
|
self.logger.info("🔍 TEST 5: Testing selector generation...")
|
|
results["selector_tests"] = await self._test_selector_generation()
|
|
|
|
# Test 6: Action Execution
|
|
self.logger.info("🔍 TEST 6: Testing action execution...")
|
|
results["action_tests"] = await self._test_action_execution()
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"💥 Diagnostic failed: {e}")
|
|
results["error"] = str(e)
|
|
|
|
return results
|
|
|
|
async def _test_connectivity(self) -> Dict[str, Any]:
|
|
"""Test MCP server connectivity"""
|
|
try:
|
|
await self.client.connect()
|
|
return {
|
|
"status": "success",
|
|
"server_type": self.client.server_type,
|
|
"server_url": self.client.server_url,
|
|
"connected": self.client.session is not None
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"status": "failed",
|
|
"error": str(e)
|
|
}
|
|
|
|
async def _test_browser_state(self) -> Dict[str, Any]:
|
|
"""Test browser state and availability"""
|
|
try:
|
|
# Try to get current URL
|
|
result = await self.client._call_mcp_tool("chrome_get_web_content", {
|
|
"format": "text",
|
|
"selector": "title"
|
|
})
|
|
|
|
return {
|
|
"status": "success",
|
|
"browser_available": True,
|
|
"page_title": result.get("content", [{}])[0].get("text", "Unknown") if result.get("content") else "Unknown"
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"status": "failed",
|
|
"browser_available": False,
|
|
"error": str(e)
|
|
}
|
|
|
|
async def _test_page_content(self) -> Dict[str, Any]:
|
|
"""Test page content retrieval"""
|
|
try:
|
|
result = await self.client._call_mcp_tool("chrome_get_web_content", {
|
|
"format": "text"
|
|
})
|
|
|
|
content = result.get("content", [])
|
|
if content and len(content) > 0:
|
|
text_content = content[0].get("text", "")
|
|
return {
|
|
"status": "success",
|
|
"content_length": len(text_content),
|
|
"has_content": len(text_content) > 0,
|
|
"preview": text_content[:200] + "..." if len(text_content) > 200 else text_content
|
|
}
|
|
else:
|
|
return {
|
|
"status": "success",
|
|
"content_length": 0,
|
|
"has_content": False,
|
|
"preview": ""
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"status": "failed",
|
|
"error": str(e)
|
|
}
|
|
|
|
async def _test_interactive_elements(self) -> Dict[str, Any]:
|
|
"""Test interactive element discovery"""
|
|
try:
|
|
result = await self.client._call_mcp_tool("chrome_get_interactive_elements", {
|
|
"types": ["button", "a", "input", "select", "textarea"]
|
|
})
|
|
|
|
elements = result.get("elements", [])
|
|
|
|
# Analyze elements
|
|
element_summary = {}
|
|
for element in elements:
|
|
tag = element.get("tagName", "unknown").lower()
|
|
element_summary[tag] = element_summary.get(tag, 0) + 1
|
|
|
|
return {
|
|
"status": "success",
|
|
"total_elements": len(elements),
|
|
"element_types": element_summary,
|
|
"sample_elements": elements[:5] if elements else []
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"status": "failed",
|
|
"error": str(e)
|
|
}
|
|
|
|
async def _test_selector_generation(self) -> List[Dict[str, Any]]:
|
|
"""Test selector generation for various elements"""
|
|
tests = []
|
|
|
|
try:
|
|
# Get interactive elements first
|
|
result = await self.client._call_mcp_tool("chrome_get_interactive_elements", {
|
|
"types": ["button", "a", "input"]
|
|
})
|
|
|
|
elements = result.get("elements", [])[:5] # Test first 5 elements
|
|
|
|
for i, element in enumerate(elements):
|
|
test_result = {
|
|
"element_index": i,
|
|
"element_tag": element.get("tagName", "unknown"),
|
|
"element_text": element.get("textContent", "")[:50],
|
|
"element_attributes": element.get("attributes", {}),
|
|
"generated_selector": None,
|
|
"selector_valid": False
|
|
}
|
|
|
|
try:
|
|
# Generate selector
|
|
selector = self.client._extract_best_selector(element)
|
|
test_result["generated_selector"] = selector
|
|
|
|
# Test if selector is valid by trying to use it
|
|
validation_result = await self.client._call_mcp_tool("chrome_get_web_content", {
|
|
"selector": selector,
|
|
"textOnly": False
|
|
})
|
|
|
|
test_result["selector_valid"] = validation_result.get("content") is not None
|
|
|
|
except Exception as e:
|
|
test_result["error"] = str(e)
|
|
|
|
tests.append(test_result)
|
|
|
|
except Exception as e:
|
|
tests.append({
|
|
"error": f"Failed to get elements for selector testing: {e}"
|
|
})
|
|
|
|
return tests
|
|
|
|
async def _test_action_execution(self) -> List[Dict[str, Any]]:
|
|
"""Test action execution with safe, non-destructive actions"""
|
|
tests = []
|
|
|
|
# Test 1: Try to get page title (safe action)
|
|
test_result = {
|
|
"action": "get_page_title",
|
|
"description": "Safe action to get page title",
|
|
"status": None,
|
|
"error": None
|
|
}
|
|
|
|
try:
|
|
result = await self.client._call_mcp_tool("chrome_get_web_content", {
|
|
"selector": "title",
|
|
"textOnly": True
|
|
})
|
|
test_result["status"] = "success"
|
|
test_result["result"] = result
|
|
except Exception as e:
|
|
test_result["status"] = "failed"
|
|
test_result["error"] = str(e)
|
|
|
|
tests.append(test_result)
|
|
|
|
# Test 2: Try keyboard action (safe - just Escape key)
|
|
test_result = {
|
|
"action": "keyboard_escape",
|
|
"description": "Safe keyboard action (Escape key)",
|
|
"status": None,
|
|
"error": None
|
|
}
|
|
|
|
try:
|
|
result = await self.client._call_mcp_tool("chrome_keyboard", {
|
|
"keys": "Escape"
|
|
})
|
|
test_result["status"] = "success"
|
|
test_result["result"] = result
|
|
except Exception as e:
|
|
test_result["status"] = "failed"
|
|
test_result["error"] = str(e)
|
|
|
|
tests.append(test_result)
|
|
|
|
return tests
|
|
|
|
async def test_specific_selector(self, selector: str) -> Dict[str, Any]:
|
|
"""Test a specific selector"""
|
|
self.logger.info(f"🔍 Testing specific selector: {selector}")
|
|
|
|
result = {
|
|
"selector": selector,
|
|
"validation": None,
|
|
"click_test": None
|
|
}
|
|
|
|
try:
|
|
# Test 1: Validate selector exists
|
|
validation = await self.client._call_mcp_tool("chrome_get_web_content", {
|
|
"selector": selector,
|
|
"textOnly": False
|
|
})
|
|
|
|
result["validation"] = {
|
|
"status": "success" if validation.get("content") else "not_found",
|
|
"content": validation.get("content")
|
|
}
|
|
|
|
# Test 2: Try clicking (only if element was found)
|
|
if validation.get("content"):
|
|
try:
|
|
click_result = await self.client._call_mcp_tool("chrome_click_element", {
|
|
"selector": selector
|
|
})
|
|
result["click_test"] = {
|
|
"status": "success",
|
|
"result": click_result
|
|
}
|
|
except Exception as click_error:
|
|
result["click_test"] = {
|
|
"status": "failed",
|
|
"error": str(click_error)
|
|
}
|
|
else:
|
|
result["click_test"] = {
|
|
"status": "skipped",
|
|
"reason": "Element not found"
|
|
}
|
|
|
|
except Exception as e:
|
|
result["validation"] = {
|
|
"status": "failed",
|
|
"error": str(e)
|
|
}
|
|
|
|
return result
|
|
|
|
async def cleanup(self):
|
|
"""Cleanup resources"""
|
|
try:
|
|
await self.client.disconnect()
|
|
except Exception as e:
|
|
self.logger.warning(f"Cleanup warning: {e}")
|
|
|
|
|
|
async def main():
|
|
"""Main function for running diagnostics"""
|
|
# Default configuration - adjust as needed
|
|
config = {
|
|
'mcp_server_type': 'http',
|
|
'mcp_server_url': 'http://localhost:3000/mcp',
|
|
'mcp_server_command': '',
|
|
'mcp_server_args': []
|
|
}
|
|
|
|
debugger = BrowserActionDebugger(config)
|
|
|
|
try:
|
|
print("🚀 Starting Browser Action Diagnostics...")
|
|
results = await debugger.run_full_diagnostic()
|
|
|
|
print("\n" + "="*60)
|
|
print("📊 DIAGNOSTIC RESULTS")
|
|
print("="*60)
|
|
|
|
for test_name, test_result in results.items():
|
|
print(f"\n{test_name.upper()}:")
|
|
print(json.dumps(test_result, indent=2, default=str))
|
|
|
|
# Save results to file
|
|
with open('browser_diagnostic_results.json', 'w') as f:
|
|
json.dump(results, f, indent=2, default=str)
|
|
|
|
print(f"\n✅ Diagnostics complete! Results saved to browser_diagnostic_results.json")
|
|
|
|
except Exception as e:
|
|
print(f"💥 Diagnostic failed: {e}")
|
|
finally:
|
|
await debugger.cleanup()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|