Spaces:
Sleeping
Sleeping
File size: 19,635 Bytes
2b89d68 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 |
"""
🤖 Fagun Browser Automation Testing Agent - Advanced Error Handler
=================================================================
Advanced error handling, debugging, and intelligent error recovery system.
Author: Mejbaur Bahar Fagun
Role: Software Engineer in Test
LinkedIn: https://www.linkedin.com/in/mejbaur/
"""
import asyncio
import logging
import traceback
import json
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass
from datetime import datetime
from playwright.async_api import Page, Locator, Error as PlaywrightError
logger = logging.getLogger(__name__)
@dataclass
class DetailedError:
"""Detailed error information with context."""
error_type: str
error_message: str
element_info: Dict[str, Any]
page_context: Dict[str, Any]
action_context: Dict[str, Any]
suggested_fix: str
severity: str # low, medium, high, critical
timestamp: datetime
stack_trace: str
class AdvancedErrorHandler:
"""Advanced error handling and debugging system."""
def __init__(self, page: Page):
self.page = page
self.error_history: List[DetailedError] = []
self.recovery_attempts: Dict[str, int] = {}
async def handle_action_error(self, error: Exception, action: str,
element_index: Optional[int] = None,
element_selector: Optional[str] = None,
input_value: Optional[str] = None) -> DetailedError:
"""Handle action errors with detailed context and suggestions."""
# Extract element information
element_info = await self._extract_element_info(element_index, element_selector)
# Extract page context
page_context = await self._extract_page_context()
# Extract action context
action_context = {
"action": action,
"element_index": element_index,
"element_selector": element_selector,
"input_value": input_value,
"timestamp": datetime.now().isoformat()
}
# Analyze error and generate suggestions
error_analysis = self._analyze_error(error, element_info, page_context, action_context)
# Create detailed error
detailed_error = DetailedError(
error_type=type(error).__name__,
error_message=str(error),
element_info=element_info,
page_context=page_context,
action_context=action_context,
suggested_fix=error_analysis["suggested_fix"],
severity=error_analysis["severity"],
timestamp=datetime.now(),
stack_trace=traceback.format_exc()
)
self.error_history.append(detailed_error)
# Log detailed error
self._log_detailed_error(detailed_error)
return detailed_error
async def _extract_element_info(self, element_index: Optional[int],
element_selector: Optional[str]) -> Dict[str, Any]:
"""Extract detailed information about the element that caused the error."""
element_info = {
"index": element_index,
"selector": element_selector,
"exists": False,
"visible": False,
"enabled": False,
"tag_name": None,
"attributes": {},
"text_content": None,
"bounding_box": None,
"similar_elements": []
}
try:
if element_index is not None:
# Get element by index
elements = await self.page.locator("input, button, select, textarea").all()
if 0 <= element_index < len(elements):
element = elements[element_index]
element_info.update(await self._get_element_details(element))
# Find similar elements
element_info["similar_elements"] = await self._find_similar_elements(element)
elif element_selector:
# Get element by selector
element = self.page.locator(element_selector).first
if await element.count() > 0:
element_info.update(await self._get_element_details(element))
element_info["similar_elements"] = await self._find_similar_elements(element)
except Exception as e:
element_info["extraction_error"] = str(e)
return element_info
async def _get_element_details(self, element: Locator) -> Dict[str, Any]:
"""Get detailed information about a specific element."""
details = {}
try:
details["exists"] = await element.count() > 0
if details["exists"]:
details["visible"] = await element.is_visible()
details["enabled"] = await element.is_enabled()
details["tag_name"] = await element.evaluate("el => el.tagName")
details["text_content"] = await element.text_content()
# Get attributes
attributes = await element.evaluate("el => { const attrs = {}; for (let attr of el.attributes) { attrs[attr.name] = attr.value; } return attrs; }")
details["attributes"] = attributes
# Get bounding box
try:
bbox = await element.bounding_box()
details["bounding_box"] = bbox
except:
details["bounding_box"] = None
except Exception as e:
details["error"] = str(e)
return details
async def _find_similar_elements(self, target_element: Locator) -> List[Dict[str, Any]]:
"""Find similar elements on the page."""
similar_elements = []
try:
# Get all interactive elements
all_elements = await self.page.locator("input, button, select, textarea, a").all()
for i, element in enumerate(all_elements):
try:
element_details = await self._get_element_details(element)
if element_details.get("exists"):
similar_elements.append({
"index": i,
"tag_name": element_details.get("tag_name"),
"attributes": element_details.get("attributes", {}),
"text_content": element_details.get("text_content"),
"visible": element_details.get("visible", False)
})
except:
continue
except Exception as e:
similar_elements.append({"error": str(e)})
return similar_elements[:10] # Limit to 10 similar elements
async def _extract_page_context(self) -> Dict[str, Any]:
"""Extract current page context information."""
context = {
"url": None,
"title": None,
"form_count": 0,
"input_count": 0,
"button_count": 0,
"page_loaded": False,
"viewport_size": None,
"user_agent": None
}
try:
context["url"] = self.page.url
context["title"] = await self.page.title()
context["form_count"] = await self.page.locator("form").count()
context["input_count"] = await self.page.locator("input").count()
context["button_count"] = await self.page.locator("button").count()
context["page_loaded"] = True
# Get viewport size
viewport = await self.page.viewport_size()
if viewport:
context["viewport_size"] = f"{viewport['width']}x{viewport['height']}"
# Get user agent
context["user_agent"] = await self.page.evaluate("navigator.userAgent")
except Exception as e:
context["extraction_error"] = str(e)
return context
def _analyze_error(self, error: Exception, element_info: Dict[str, Any],
page_context: Dict[str, Any], action_context: Dict[str, Any]) -> Dict[str, Any]:
"""Analyze error and provide intelligent suggestions."""
error_message = str(error).lower()
error_type = type(error).__name__
# Determine severity
severity = "medium"
if "timeout" in error_message or "waiting" in error_message:
severity = "high"
elif "not found" in error_message or "not visible" in error_message:
severity = "high"
elif "permission" in error_message or "blocked" in error_message:
severity = "critical"
elif "network" in error_message or "connection" in error_message:
severity = "high"
# Generate suggestions based on error type
suggested_fix = self._generate_suggestions(error_message, error_type, element_info, page_context, action_context)
return {
"severity": severity,
"suggested_fix": suggested_fix
}
def _generate_suggestions(self, error_message: str, error_type: str,
element_info: Dict[str, Any], page_context: Dict[str, Any],
action_context: Dict[str, Any]) -> str:
"""Generate intelligent suggestions for fixing the error."""
suggestions = []
# Element not found errors
if "not found" in error_message or "no element" in error_message:
suggestions.append("🔍 Element not found. Try these solutions:")
suggestions.append(" • Wait for the page to fully load")
suggestions.append(" • Check if the element selector is correct")
suggestions.append(" • Verify the element exists on the current page")
if element_info.get("similar_elements"):
suggestions.append(" • Similar elements found on page:")
for elem in element_info["similar_elements"][:3]:
if elem.get("tag_name"):
suggestions.append(f" - {elem['tag_name']}: {elem.get('text_content', 'No text')[:50]}")
# Element not visible errors
elif "not visible" in error_message or "not attached" in error_message:
suggestions.append("👁️ Element not visible. Try these solutions:")
suggestions.append(" • Scroll to the element first")
suggestions.append(" • Wait for animations to complete")
suggestions.append(" • Check if element is hidden by CSS")
suggestions.append(" • Try clicking a parent element first")
# Input text errors
elif "input text" in error_message or "failed to input" in error_message:
suggestions.append("⌨️ Input text failed. Try these solutions:")
suggestions.append(" • Clear the field first")
suggestions.append(" • Check if the field is enabled")
suggestions.append(" • Verify the field accepts text input")
suggestions.append(" • Try typing character by character")
if action_context.get("input_value"):
suggestions.append(f" • Input value was: '{action_context['input_value']}'")
# Click errors
elif "click" in error_message or "failed to click" in error_message:
suggestions.append("🖱️ Click failed. Try these solutions:")
suggestions.append(" • Wait for element to be clickable")
suggestions.append(" • Check if element is covered by another element")
suggestions.append(" • Try force clicking")
suggestions.append(" • Scroll to element before clicking")
# Timeout errors
elif "timeout" in error_message or "waiting" in error_message:
suggestions.append("⏰ Timeout occurred. Try these solutions:")
suggestions.append(" • Increase wait timeout")
suggestions.append(" • Check if page is still loading")
suggestions.append(" • Verify network connection")
suggestions.append(" • Try refreshing the page")
# Network errors
elif "network" in error_message or "connection" in error_message:
suggestions.append("🌐 Network error. Try these solutions:")
suggestions.append(" • Check internet connection")
suggestions.append(" • Verify the website is accessible")
suggestions.append(" • Try again in a few moments")
suggestions.append(" • Check for firewall/proxy issues")
# Generic suggestions
if not suggestions:
suggestions.append("🔧 General troubleshooting:")
suggestions.append(" • Refresh the page and try again")
suggestions.append(" • Check browser console for errors")
suggestions.append(" • Verify the website is working properly")
suggestions.append(" • Try a different approach or element")
# Add context-specific suggestions
if element_info.get("index") is not None:
suggestions.append(f" • Element index: {element_info['index']}")
if page_context.get("input_count", 0) > 0:
suggestions.append(f" • Page has {page_context['input_count']} input elements")
return "\n".join(suggestions)
def _log_detailed_error(self, error: DetailedError):
"""Log detailed error information."""
logger.error(f"🚨 Detailed Error Report:")
logger.error(f" Type: {error.error_type}")
logger.error(f" Message: {error.error_message}")
logger.error(f" Severity: {error.severity}")
logger.error(f" Element Index: {error.element_info.get('index', 'N/A')}")
logger.error(f" Element Selector: {error.element_info.get('selector', 'N/A')}")
logger.error(f" Element Exists: {error.element_info.get('exists', 'N/A')}")
logger.error(f" Element Visible: {error.element_info.get('visible', 'N/A')}")
logger.error(f" Page URL: {error.page_context.get('url', 'N/A')}")
logger.error(f" Page Title: {error.page_context.get('title', 'N/A')}")
logger.error(f" Suggested Fix:\n{error.suggested_fix}")
if error.severity in ["high", "critical"]:
logger.error(f" Stack Trace:\n{error.stack_trace}")
async def attempt_error_recovery(self, error: DetailedError) -> bool:
"""Attempt to recover from an error automatically."""
recovery_key = f"{error.error_type}_{error.action_context.get('action', 'unknown')}"
# Limit recovery attempts
if self.recovery_attempts.get(recovery_key, 0) >= 3:
logger.warning(f"⚠️ Maximum recovery attempts reached for {recovery_key}")
return False
self.recovery_attempts[recovery_key] = self.recovery_attempts.get(recovery_key, 0) + 1
try:
# Recovery strategies based on error type
if "not found" in error.error_message.lower():
return await self._recover_element_not_found(error)
elif "not visible" in error.error_message.lower():
return await self._recover_element_not_visible(error)
elif "input text" in error.error_message.lower():
return await self._recover_input_text_error(error)
elif "timeout" in error.error_message.lower():
return await self._recover_timeout_error(error)
except Exception as recovery_error:
logger.error(f"❌ Recovery attempt failed: {recovery_error}")
return False
async def _recover_element_not_found(self, error: DetailedError) -> bool:
"""Recover from element not found error."""
try:
# Wait a bit for dynamic content
await asyncio.sleep(2)
# Try to find similar elements
if error.element_info.get("similar_elements"):
for elem in error.element_info["similar_elements"][:3]:
if elem.get("visible") and elem.get("tag_name"):
logger.info(f"🔄 Trying similar element: {elem['tag_name']}")
return True
return False
except:
return False
async def _recover_element_not_visible(self, error: DetailedError) -> bool:
"""Recover from element not visible error."""
try:
# Scroll to element
if error.element_info.get("index") is not None:
elements = await self.page.locator("input, button, select, textarea").all()
if 0 <= error.element_info["index"] < len(elements):
await elements[error.element_info["index"]].scroll_into_view_if_needed()
await asyncio.sleep(1)
return True
return False
except:
return False
async def _recover_input_text_error(self, error: DetailedError) -> bool:
"""Recover from input text error."""
try:
# Clear field first
if error.element_info.get("index") is not None:
elements = await self.page.locator("input, textarea").all()
if 0 <= error.element_info["index"] < len(elements):
await elements[error.element_info["index"]].clear()
await asyncio.sleep(0.5)
return True
return False
except:
return False
async def _recover_timeout_error(self, error: DetailedError) -> bool:
"""Recover from timeout error."""
try:
# Wait longer
await asyncio.sleep(5)
return True
except:
return False
def get_error_summary(self) -> Dict[str, Any]:
"""Get summary of all errors encountered."""
if not self.error_history:
return {"message": "No errors recorded"}
error_counts = {}
severity_counts = {}
for error in self.error_history:
error_type = error.error_type
severity = error.severity
error_counts[error_type] = error_counts.get(error_type, 0) + 1
severity_counts[severity] = severity_counts.get(severity, 0) + 1
return {
"total_errors": len(self.error_history),
"error_types": error_counts,
"severity_distribution": severity_counts,
"recovery_attempts": self.recovery_attempts,
"recent_errors": [
{
"type": e.error_type,
"message": e.error_message,
"severity": e.severity,
"timestamp": e.timestamp.isoformat(),
"suggested_fix": e.suggested_fix
}
for e in self.error_history[-5:] # Last 5 errors
]
}
|