""" Gmail UI Helper - Hilfsfunktionen für UI-Interaktionen """ import logging import time import random import os from typing import Optional, List from playwright.sync_api import Page, ElementHandle logger = logging.getLogger("gmail_ui_helper") class GmailUIHelper: """ Enhanced Hilfsklasse für Gmail UI-Interaktionen mit 2025 Optimierungen """ def __init__(self, page: Page, screenshots_dir: str = None, save_screenshots: bool = True): """ Initialisiert den UI Helper """ self.page = page self.screenshots_dir = screenshots_dir or "logs/screenshots" self.save_screenshots = save_screenshots # Screenshot-Verzeichnis erstellen falls nötig if self.save_screenshots and not os.path.exists(self.screenshots_dir): os.makedirs(self.screenshots_dir) def take_screenshot(self, name: str) -> Optional[str]: """ Erstellt einen Screenshot """ if not self.save_screenshots: return None try: timestamp = int(time.time()) filename = f"{name}_{timestamp}.png" filepath = os.path.join(self.screenshots_dir, filename) self.page.screenshot(path=filepath) logger.debug(f"Screenshot gespeichert: {filepath}") return filepath except Exception as e: logger.warning(f"Fehler beim Erstellen des Screenshots: {e}") return None def wait_for_element(self, selector: str, timeout: int = 30000) -> bool: """ Wartet auf ein Element """ try: self.page.wait_for_selector(selector, timeout=timeout) return True except Exception as e: logger.error(f"Element {selector} nicht gefunden nach {timeout}ms") return False def type_with_delay(self, selector: str, text: str, delay_min: float = 0.05, delay_max: float = 0.15): """ Tippt Text mit menschenähnlicher Verzögerung """ try: element = self.page.locator(selector) element.click() for char in text: element.type(char) time.sleep(random.uniform(delay_min, delay_max)) except Exception as e: logger.error(f"Fehler beim Tippen in {selector}: {e}") raise def click_with_retry(self, selector: str, max_attempts: int = 3) -> bool: """ Enhanced Click mit mehreren Strategien und Wiederholungsversuchen """ for attempt in range(max_attempts): try: # Strategie 1: Normaler Click if attempt == 0: self.page.click(selector) return True # Strategie 2: Force Click elif attempt == 1: self.page.locator(selector).click(force=True) return True # Strategie 3: JavaScript Click else: self.page.evaluate(f""" const element = document.querySelector('{selector}'); if (element) {{ element.click(); }} """) return True except Exception as e: logger.warning(f"Klick-Versuch {attempt + 1} fehlgeschlagen: {e}") if attempt < max_attempts - 1: time.sleep(random.uniform(1, 2)) return False def scroll_to_element(self, selector: str): """ Scrollt zu einem Element """ try: self.page.locator(selector).scroll_into_view_if_needed() time.sleep(random.uniform(0.5, 1)) except Exception as e: logger.warning(f"Fehler beim Scrollen zu {selector}: {e}") def is_element_visible(self, selector: str, timeout: int = 1000) -> bool: """ Prüft ob ein Element sichtbar ist mit optionalem Timeout """ try: return self.page.locator(selector).is_visible(timeout=timeout) except: return False def get_element_text(self, selector: str) -> Optional[str]: """ Holt den Text eines Elements """ try: return self.page.locator(selector).text_content() except Exception as e: logger.warning(f"Fehler beim Lesen des Texts von {selector}: {e}") return None def select_dropdown_option(self, selector: str, value: str): """ Wählt eine Option aus einem Dropdown """ try: self.page.select_option(selector, value) time.sleep(random.uniform(0.3, 0.6)) except Exception as e: logger.error(f"Fehler beim Auswählen von {value} in {selector}: {e}") raise def safe_fill(self, selector: str, text: str): """ Füllt ein Eingabefeld sicher (vorher leeren, dann schreiben). """ try: el = self.page.locator(selector) el.click() # Leeren via fill (überschreibt) – zuverlässiger als Tastenkombinationen el.fill("") time.sleep(random.uniform(0.1, 0.2)) el.fill(str(text)) time.sleep(random.uniform(0.2, 0.4)) except Exception as e: logger.error(f"Fehler beim Befüllen von {selector}: {e}") raise def select_dropdown_by_label(self, label_texts: List[str], option_texts: List[str]) -> bool: """ Öffnet ein Dropdown anhand seiner Beschriftung und wählt eine Option per sichtbarem Text. """ try: for label in label_texts: try: dd = self.page.get_by_label(label) if dd.count() > 0: dd.first.click() time.sleep(random.uniform(0.3, 0.6)) for opt in option_texts: try: # Bevorzugt per Rolle/Name self.page.get_by_role("option", name=opt).first.click() time.sleep(random.uniform(0.2, 0.4)) return True except Exception: # Fallback: per Text-Locator try: self.page.locator(f"text={opt}").first.click() time.sleep(random.uniform(0.2, 0.4)) return True except Exception: continue except Exception: continue except Exception as e: logger.debug(f"Dropdown-Auswahl per Label fehlgeschlagen: {e}") return False def select_dropdown_by_selector(self, selector: str, option_texts: List[str]) -> bool: """ Öffnet ein Dropdown anhand eines Selektors und wählt eine Option per sichtbarem Text. """ try: if self.is_element_visible(selector): self.page.click(selector) time.sleep(random.uniform(0.3, 0.6)) for opt in option_texts: try: self.page.get_by_role("option", name=opt).first.click() time.sleep(random.uniform(0.2, 0.4)) return True except Exception: try: self.page.locator(f"text={opt}").first.click() time.sleep(random.uniform(0.2, 0.4)) return True except Exception: continue except Exception as e: logger.debug(f"Dropdown-Auswahl per Selektor fehlgeschlagen: {e}") return False def wait_for_navigation(self, timeout: int = 30000): """ Wartet auf Navigation """ try: self.page.wait_for_load_state("networkidle", timeout=timeout) except Exception as e: logger.warning(f"Navigation-Timeout nach {timeout}ms: {e}") def wait_for_loading_to_finish(self, extra_wait: bool = True): """ Enhanced Wartet bis Ladeanimation verschwunden ist mit 2025 Optimierungen """ try: # Warte bis der Loading Spinner nicht mehr sichtbar ist from social_networks.gmail import gmail_selectors as selectors if self.is_element_visible(selectors.LOADING_SPINNER, timeout=500): self.page.wait_for_selector(selectors.LOADING_SPINNER, state="hidden", timeout=10000) time.sleep(random.uniform(0.5, 1)) except: pass # Zusätzliche Stabilitätsprüfung für 2025 if extra_wait: try: # Warte auf stabilen DOM self.page.wait_for_load_state("domcontentloaded") self.page.wait_for_load_state("networkidle", timeout=3000) except: pass # Kleine zusätzliche Wartezeit für JavaScript-Rendering time.sleep(random.uniform(0.3, 0.7))