# social_networks/x/x_ui_helper.py """ X (Twitter) UI Helper - Hilfsklasse für UI-Interaktionen bei X """ import time from typing import Optional, List, Dict, Any, Tuple from playwright.sync_api import Page, ElementHandle from .x_selectors import XSelectors from utils.logger import setup_logger # Konfiguriere Logger logger = setup_logger("x_ui_helper") class XUIHelper: """ Hilfsklasse für UI-Interaktionen mit X. Bietet wiederverwendbare Methoden für häufige UI-Operationen. """ def __init__(self, automation): """ Initialisiert den X UI Helper. Args: automation: Referenz auf die Hauptautomatisierungsklasse """ self.automation = automation self.selectors = XSelectors() logger.debug("X UI Helper initialisiert") def wait_for_element(self, selector: str, timeout: int = 10000, state: str = "visible") -> Optional[ElementHandle]: """ Wartet auf ein Element und gibt es zurück. Args: selector: CSS-Selektor timeout: Timeout in Millisekunden state: Gewünschter Zustand ("visible", "attached", "detached", "hidden") Returns: Optional[ElementHandle]: Element oder None """ try: page = self.automation.browser.page element = page.wait_for_selector(selector, timeout=timeout, state=state) return element except Exception as e: logger.debug(f"Element nicht gefunden: {selector} - {e}") return None def click_element_safely(self, selector: str, timeout: int = 10000, retry_count: int = 3) -> bool: """ Klickt sicher auf ein Element mit Retry-Logik. Args: selector: CSS-Selektor timeout: Timeout in Millisekunden retry_count: Anzahl der Wiederholungsversuche Returns: bool: True bei Erfolg, False bei Fehler """ page = self.automation.browser.page for attempt in range(retry_count): try: # Warte auf Element element = self.wait_for_element(selector, timeout) if not element: logger.warning(f"Element nicht gefunden beim {attempt + 1}. Versuch: {selector}") continue # Scrolle zum Element element.scroll_into_view_if_needed() # Warte kurz self.automation.human_behavior.random_delay(0.3, 0.7) # Klicke element.click() logger.info(f"Element erfolgreich geklickt: {selector}") return True except Exception as e: logger.warning(f"Fehler beim Klicken (Versuch {attempt + 1}): {e}") if attempt < retry_count - 1: self.automation.human_behavior.random_delay(1, 2) continue logger.error(f"Konnte Element nicht klicken nach {retry_count} Versuchen: {selector}") return False def type_text_safely(self, selector: str, text: str, clear_first: bool = True, timeout: int = 10000) -> bool: """ Gibt Text sicher in ein Eingabefeld ein. Args: selector: CSS-Selektor text: Einzugebender Text clear_first: Ob das Feld zuerst geleert werden soll timeout: Timeout in Millisekunden Returns: bool: True bei Erfolg, False bei Fehler """ try: element = self.wait_for_element(selector, timeout) if not element: logger.error(f"Eingabefeld nicht gefunden: {selector}") return False # Fokussiere das Element element.focus() self.automation.human_behavior.random_delay(0.2, 0.5) # Leere das Feld wenn gewünscht if clear_first: element.click(click_count=3) # Alles auswählen self.automation.human_behavior.random_delay(0.1, 0.3) element.press("Delete") self.automation.human_behavior.random_delay(0.2, 0.5) # Tippe den Text self.automation.human_behavior.type_text(element, text) logger.info(f"Text erfolgreich eingegeben in: {selector}") return True except Exception as e: logger.error(f"Fehler beim Texteingeben: {e}") return False def select_dropdown_option(self, selector: str, value: str, timeout: int = 10000) -> bool: """ Wählt eine Option aus einem Dropdown-Menü. Args: selector: CSS-Selektor des Select-Elements value: Wert der zu wählenden Option timeout: Timeout in Millisekunden Returns: bool: True bei Erfolg, False bei Fehler """ try: page = self.automation.browser.page # Warte auf Select-Element select = self.wait_for_element(selector, timeout) if not select: logger.error(f"Dropdown nicht gefunden: {selector}") return False # Wähle Option page.select_option(selector, value) logger.info(f"Option '{value}' ausgewählt in: {selector}") return True except Exception as e: logger.error(f"Fehler beim Auswählen der Dropdown-Option: {e}") return False def handle_modal(self, action: str = "accept", timeout: int = 5000) -> bool: """ Behandelt modale Dialoge. Args: action: "accept", "dismiss" oder "close" timeout: Timeout in Millisekunden Returns: bool: True wenn Modal behandelt wurde, False sonst """ try: page = self.automation.browser.page # Prüfe ob Modal vorhanden modal = self.wait_for_element(self.selectors.MODALS["modal_container"], timeout) if not modal: logger.debug("Kein Modal gefunden") return False if action == "accept": # Suche Bestätigen-Button if self.click_element_safely(self.selectors.MODALS["confirm_button"], timeout=3000): logger.info("Modal bestätigt") return True elif action == "dismiss": # Suche Abbrechen-Button if self.click_element_safely(self.selectors.MODALS["cancel_button"], timeout=3000): logger.info("Modal abgebrochen") return True elif action == "close": # Suche Schließen-Button close_selectors = [ self.selectors.MODALS["modal_close_button"], self.selectors.MODALS["modal_close_button_en"] ] for selector in close_selectors: if self.click_element_safely(selector, timeout=3000): logger.info("Modal geschlossen") return True logger.warning(f"Konnte Modal nicht mit Aktion '{action}' behandeln") return False except Exception as e: logger.error(f"Fehler bei Modal-Behandlung: {e}") return False def check_for_errors(self, timeout: int = 2000) -> Optional[str]: """ Prüft auf Fehlermeldungen auf der Seite. Args: timeout: Timeout in Millisekunden Returns: Optional[str]: Fehlermeldung wenn gefunden, sonst None """ try: page = self.automation.browser.page # Prüfe alle Fehler-Selektoren for category, selector in [ ("error_message", self.selectors.ERRORS["error_message"]), ("error_alert", self.selectors.ERRORS["error_alert"]), ("rate_limit", self.selectors.ERRORS["rate_limit_message"]), ("suspended", self.selectors.ERRORS["suspended_message"]), ("email_taken", self.selectors.ERRORS["email_taken"]), ("invalid_credentials", self.selectors.ERRORS["invalid_credentials"]) ]: error_element = self.wait_for_element(selector, timeout=timeout) if error_element: error_text = error_element.text_content() logger.warning(f"Fehler gefunden ({category}): {error_text}") return error_text return None except Exception as e: logger.debug(f"Fehler bei Fehlerprüfung: {e}") return None def wait_for_navigation(self, timeout: int = 30000) -> bool: """ Wartet auf Navigation/Seitenwechsel. Args: timeout: Timeout in Millisekunden Returns: bool: True wenn Navigation erfolgt ist """ try: page = self.automation.browser.page with page.expect_navigation(timeout=timeout): pass logger.info("Navigation abgeschlossen") return True except Exception as e: logger.debug(f"Keine Navigation erkannt: {e}") return False def scroll_to_bottom(self, smooth: bool = True, pause_time: float = 1.0): """ Scrollt zum Ende der Seite. Args: smooth: Ob sanft gescrollt werden soll pause_time: Pausenzeit nach dem Scrollen """ try: page = self.automation.browser.page if smooth: # Sanftes Scrollen in Schritten page.evaluate(""" async () => { const distance = 100; const delay = 50; const timer = setInterval(() => { window.scrollBy(0, distance); if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) { clearInterval(timer); } }, delay); } """) else: # Direktes Scrollen page.evaluate("window.scrollTo(0, document.body.scrollHeight)") time.sleep(pause_time) logger.debug("Zum Seitenende gescrollt") except Exception as e: logger.error(f"Fehler beim Scrollen: {e}") def take_element_screenshot(self, selector: str, filename: str, timeout: int = 10000) -> bool: """ Macht einen Screenshot von einem spezifischen Element. Args: selector: CSS-Selektor filename: Dateiname für den Screenshot timeout: Timeout in Millisekunden Returns: bool: True bei Erfolg, False bei Fehler """ try: element = self.wait_for_element(selector, timeout) if not element: logger.error(f"Element für Screenshot nicht gefunden: {selector}") return False # Screenshot vom Element element.screenshot(path=f"{self.automation.screenshots_dir}/{filename}") logger.info(f"Element-Screenshot gespeichert: {filename}") return True except Exception as e: logger.error(f"Fehler beim Element-Screenshot: {e}") return False def get_element_text(self, selector: str, timeout: int = 10000) -> Optional[str]: """ Holt den Text eines Elements. Args: selector: CSS-Selektor timeout: Timeout in Millisekunden Returns: Optional[str]: Text des Elements oder None """ try: element = self.wait_for_element(selector, timeout) if element: return element.text_content() return None except Exception as e: logger.error(f"Fehler beim Abrufen des Element-Texts: {e}") return None def is_element_visible(self, selector: str, timeout: int = 1000) -> bool: """ Prüft ob ein Element sichtbar ist. Args: selector: CSS-Selektor timeout: Timeout in Millisekunden Returns: bool: True wenn sichtbar, False sonst """ try: page = self.automation.browser.page # is_visible hat kein timeout parameter in Playwright # Verwende wait_for_selector für timeout-Funktionalität try: element = page.wait_for_selector(selector, timeout=timeout, state="visible") return element is not None except: return False except Exception as e: logger.debug(f"Element nicht sichtbar: {selector} - {e}") return False def wait_for_any_selector(self, selectors: List[str], timeout: int = 10000) -> Optional[Tuple[str, ElementHandle]]: """ Wartet auf eines von mehreren Elementen. Args: selectors: Liste von CSS-Selektoren timeout: Timeout in Millisekunden Returns: Optional[Tuple[str, ElementHandle]]: Tuple aus Selektor und Element oder None """ try: page = self.automation.browser.page # Erstelle Promise für jeden Selektor promises = [] for selector in selectors: promises.append(page.wait_for_selector(selector, timeout=timeout)) # Warte auf das erste Element element = page.evaluate(f""" () => {{ const selectors = {selectors}; for (const selector of selectors) {{ const element = document.querySelector(selector); if (element) return {{selector, found: true}}; }} return {{found: false}}; }} """) if element["found"]: actual_element = page.query_selector(element["selector"]) return (element["selector"], actual_element) return None except Exception as e: logger.debug(f"Keines der Elemente gefunden: {e}") return None