Better error reporting

This commit is contained in:
dgtlmoon
2025-04-22 11:11:57 +02:00
parent 0d1366dfb9
commit c9f0921b02
2 changed files with 92 additions and 17 deletions

View File

@@ -56,11 +56,14 @@ def construct_blueprint(datastore: ChangeDetectionStore):
try:
browsersteps_start_session['browser'] = io_interface_context.chromium.connect_over_cdp(base_url)
except Exception as e:
if 'ECONNREFUSED' in str(e):
return make_response('Unable to start the Playwright Browser session, is it running?', 401)
error_message = str(e)
if 'ECONNREFUSED' in error_message:
return make_response('Could not connect to the virtual browser. Please check if the browser service is running.', 401)
elif "'Response' object is not subscriptable" in error_message:
return make_response('Could not connect to the virtual browser. Connection to browser failed with invalid response.', 401)
else:
# Other errors, bad URL syntax, bad reply etc
return make_response(str(e), 401)
return make_response(f'Could not connect to the virtual browser: {error_message.splitlines()[0]}', 401)
proxy_id = datastore.get_preferred_proxy_for_watch(uuid=watch_uuid)
proxy = None
@@ -155,7 +158,7 @@ def construct_blueprint(datastore: ChangeDetectionStore):
return make_response('No browsersteps_session_id specified', 500)
if not browsersteps_sessions.get(browsersteps_session_id):
return make_response('No session exists under that ID', 500)
return make_response('Could not connect to the virtual browser. The session has expired or does not exist.', 401)
is_last_step = False
# Actions - step/apply/etc, do the thing and return state
@@ -174,9 +177,17 @@ def construct_blueprint(datastore: ChangeDetectionStore):
optional_value=step_optional_value)
except Exception as e:
logger.error(f"Exception when calling step operation {step_operation} {str(e)}")
# Try to find something of value to give back to the user
return make_response(str(e).splitlines()[0], 401)
error_message = str(e)
logger.error(f"Exception when calling step operation {step_operation} {error_message}")
# Provide user-friendly error messages
if "'Response' object is not subscriptable" in error_message:
return make_response('Could not connect to the virtual browser. Connection was lost or failed.', 401)
elif "timed out" in error_message.lower() or "timeout" in error_message.lower():
return make_response('Browser operation timed out. The page might be loading too slowly.', 401)
else:
# Try to find something of value to give back to the user
return make_response(f'Browser operation failed: {error_message.splitlines()[0]}', 401)
# if not this_session.page:
@@ -194,9 +205,19 @@ def construct_blueprint(datastore: ChangeDetectionStore):
watch.save_xpath_data(data=xpath_data)
except playwright._impl._api_types.Error as e:
return make_response("Browser session ran out of time :( Please reload this page."+str(e), 401)
error_message = str(e)
if "'Response' object is not subscriptable" in error_message:
return make_response("Could not connect to the virtual browser. Connection was lost or failed.", 401)
elif "session closed" in error_message.lower() or "target closed" in error_message.lower():
return make_response("Browser session has closed. Please reload the page to start a new session.", 401)
else:
return make_response("Browser session ran out of time. Please reload this page.", 401)
except Exception as e:
return make_response("Error fetching screenshot and element data - " + str(e), 401)
error_message = str(e)
if "'Response' object is not subscriptable" in error_message:
return make_response("Could not connect to the virtual browser. Connection was lost or failed.", 401)
else:
return make_response("Error fetching screenshot and element data: " + error_message.splitlines()[0], 401)
# SEND THIS BACK TO THE BROWSER

View File

@@ -65,24 +65,41 @@ class steppable_browser_interface():
"""Safely execute a page operation with error handling"""
if self.page is None:
logger.warning("Attempted operation on None page object")
return default_return
raise Exception("Could not connect to the virtual browser. Session may have expired.")
try:
return operation_fn()
except Exception as e:
logger.debug(f"Page operation failed: {str(e)}")
error_message = str(e)
logger.debug(f"Page operation failed: {error_message}")
# Try to reclaim memory if possible
try:
self.page.request_gc()
except:
pass
return default_return
# Provide more user-friendly error messages for common issues
if "'Response' object is not subscriptable" in error_message:
raise Exception("Could not connect to the virtual browser. Connection was lost or failed.")
elif "session closed" in error_message.lower() or "target closed" in error_message.lower():
raise Exception("Browser session has closed. Please reload the page to start a new session.")
elif "timeout" in error_message.lower():
raise Exception("Browser operation timed out. The page might be loading too slowly.")
else:
# Re-raise the original exception to be handled by the caller
raise
# Convert and perform "Click Button" for example
def call_action(self, action_name, selector=None, optional_value=None):
if self.page is None:
logger.warning("Cannot call action on None page object")
return
raise Exception("Could not connect to the virtual browser. Session may have expired.")
# Check if session has expired
if self.has_expired:
logger.warning("Attempting to use an expired browser session")
raise Exception("Browser session has expired. Please reload the page to start a new session.")
now = time.time()
call_action_name = re.sub('[^0-9a-zA-Z]+', '_', action_name.lower())
@@ -97,7 +114,7 @@ class steppable_browser_interface():
# Check if action handler exists
if not hasattr(self, "action_" + call_action_name):
logger.warning(f"Action handler for '{call_action_name}' not found")
return
raise Exception(f"Unknown browser operation: {action_name}")
action_handler = getattr(self, "action_" + call_action_name)
@@ -116,12 +133,23 @@ class steppable_browser_interface():
self.safe_page_operation(wait_timeout)
logger.debug(f"Call action done in {time.time()-now:.2f}s")
except Exception as e:
logger.error(f"Error executing action '{call_action_name}': {str(e)}")
error_message = str(e)
logger.error(f"Error executing action '{call_action_name}': {error_message}")
# Request garbage collection to free up resources after error
try:
self.page.request_gc()
except:
pass
# Rethrow with improved messages for specific error types
if "'Response' object is not subscriptable" in error_message:
raise Exception("Could not connect to the virtual browser. Connection was lost or failed.")
elif "session closed" in error_message.lower() or "target closed" in error_message.lower():
raise Exception("Browser session has closed. Please reload the page to start a new session.")
else:
# Rethrow the exception to be handled by the caller
raise
def action_goto_url(self, selector=None, value=None):
if not value:
@@ -483,7 +511,12 @@ class browsersteps_live_ui(steppable_browser_interface):
# Safety check - don't proceed if resources are cleaned up
if self._is_cleaned_up or self.page is None:
logger.warning("Attempted to get current state after cleanup")
return (None, None)
raise Exception("Could not connect to the virtual browser. Session may have expired.")
# Check if session has expired
if self.has_expired:
logger.warning("Attempting to use an expired browser session")
raise Exception("Browser session has expired. Please reload the page to start a new session.")
xpath_element_js = importlib.resources.files("changedetectionio.content_fetchers.res").joinpath('xpath_element_scraper.js').read_text()
@@ -496,6 +529,9 @@ class browsersteps_live_ui(steppable_browser_interface):
try:
# Get screenshot first
screenshot = capture_full_page(page=self.page)
if screenshot is None:
raise Exception("Could not capture screenshot from the virtual browser")
logger.debug(f"Time to get screenshot from browser {time.time() - now:.2f}s")
# Then get interactive elements
@@ -510,6 +546,9 @@ class browsersteps_live_ui(steppable_browser_interface):
"visualselector_xpath_selectors": scan_elements,
"max_height": MAX_TOTAL_HEIGHT
}))
if xpath_data is None:
raise Exception("Could not extract page elements from the virtual browser")
self.page.request_gc()
# Sort elements by size
@@ -517,18 +556,33 @@ class browsersteps_live_ui(steppable_browser_interface):
logger.debug(f"Time to scrape xPath element data in browser {time.time()-now:.2f}s")
except Exception as e:
logger.error(f"Error getting current state: {str(e)}")
error_message = str(e)
logger.error(f"Error getting current state: {error_message}")
# Attempt recovery - force garbage collection
try:
self.page.request_gc()
except:
pass
# Raise appropriate error message
if "'Response' object is not subscriptable" in error_message:
raise Exception("Could not connect to the virtual browser. Connection was lost or failed.")
elif "session closed" in error_message.lower() or "target closed" in error_message.lower():
raise Exception("Browser session has closed. Please reload the page to start a new session.")
else:
# Rethrow the exception to be handled by the caller
raise
# Request garbage collection one final time
try:
self.page.request_gc()
except:
pass
# Final validation before returning
if screenshot is None or xpath_data is None:
raise Exception("Could not retrieve data from the virtual browser")
return (screenshot, xpath_data)