mirror of
https://github.com/browser-use/browser-use
synced 2026-05-06 17:52:15 +02:00
95 lines
4.0 KiB
Python
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})")
|