Files
browser-use/browser_use/agent/playwright_script_helpers.py

95 lines
4.0 KiB
Python

from playwright.async_api import Page
# --- Helper Function for Replacing Sensitive Data ---
def replace_sensitive_data(text: str, sensitive_map: dict) -> str:
"""Replaces sensitive data placeholders in text."""
if not isinstance(text, str):
return text
for placeholder, value in sensitive_map.items():
replacement_value = str(value) if value is not None else ''
text = text.replace(f'<secret>{placeholder}</secret>', replacement_value)
return text
# --- Helper Function for Robust Action Execution ---
class PlaywrightActionError(Exception):
"""Custom exception for errors during Playwright script action execution."""
pass
async def _try_locate_and_act(page: Page, selector: str, action_type: str, text: str | None = None, step_info: str = '') -> None:
"""
Attempts an action (click/fill) with XPath fallback by trimming prefixes.
Raises PlaywrightActionError if the action fails after all fallbacks.
"""
print(f'Attempting {action_type} ({step_info}) using selector: {repr(selector)}')
original_selector = selector
MAX_FALLBACKS = 50 # Increased fallbacks
# Increased timeouts for potentially slow pages
INITIAL_TIMEOUT = 10000 # Milliseconds for the first attempt (10 seconds)
FALLBACK_TIMEOUT = 1000 # Shorter timeout for fallback attempts (1 second)
try:
locator = page.locator(selector).first
if action_type == 'click':
await locator.click(timeout=INITIAL_TIMEOUT)
elif action_type == 'fill' and text is not None:
await locator.fill(text, timeout=INITIAL_TIMEOUT)
else:
# This case should ideally not happen if called correctly
raise PlaywrightActionError(f"Invalid action_type '{action_type}' or missing text for fill. ({step_info})")
print(f" Action '{action_type}' successful with original selector.")
await page.wait_for_timeout(500) # Wait after successful action
return # Successful exit
except Exception as e:
print(f" Warning: Action '{action_type}' failed with original selector ({repr(selector)}): {e}. Starting fallback...")
# Fallback only works for XPath selectors
if not selector.startswith('xpath='):
# Raise error immediately if not XPath, as fallback won't work
raise PlaywrightActionError(
f"Action '{action_type}' failed. Fallback not possible for non-XPath selector: {repr(selector)}. ({step_info})"
)
xpath_parts = selector.split('=', 1)
if len(xpath_parts) < 2:
raise PlaywrightActionError(
f"Action '{action_type}' failed. Could not extract XPath string from selector: {repr(selector)}. ({step_info})"
)
xpath = xpath_parts[1] # Correctly get the XPath string
segments = [seg for seg in xpath.split('/') if seg]
for i in range(1, min(MAX_FALLBACKS + 1, len(segments))):
trimmed_xpath_raw = '/'.join(segments[i:])
fallback_xpath = f'xpath=//{trimmed_xpath_raw}'
print(f' Fallback attempt {i}/{MAX_FALLBACKS}: Trying selector: {repr(fallback_xpath)}')
try:
locator = page.locator(fallback_xpath).first
if action_type == 'click':
await locator.click(timeout=FALLBACK_TIMEOUT)
elif action_type == 'fill' and text is not None:
try:
await locator.clear(timeout=FALLBACK_TIMEOUT)
await page.wait_for_timeout(100)
except Exception as clear_error:
print(f' Warning: Failed to clear field during fallback ({step_info}): {clear_error}')
await locator.fill(text, timeout=FALLBACK_TIMEOUT)
print(f" Action '{action_type}' successful with fallback selector: {repr(fallback_xpath)}")
await page.wait_for_timeout(500)
return # Successful exit after fallback
except Exception as fallback_e:
print(f' Fallback attempt {i} failed: {fallback_e}')
if i == MAX_FALLBACKS:
# Raise exception after exhausting fallbacks
raise PlaywrightActionError(
f"Action '{action_type}' failed after {MAX_FALLBACKS} fallback attempts. Original selector: {repr(original_selector)}. ({step_info})"
)
# This part should not be reachable if logic is correct, but added as safeguard
raise PlaywrightActionError(f"Action '{action_type}' failed unexpectedly for {repr(original_selector)}. ({step_info})")