Gmail weiter gemacht
Dieser Commit ist enthalten in:
@ -6,14 +6,14 @@ import logging
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
from playwright.sync_api import Page, ElementHandle
|
||||
|
||||
logger = logging.getLogger("gmail_ui_helper")
|
||||
|
||||
class GmailUIHelper:
|
||||
"""
|
||||
Hilfsklasse für Gmail UI-Interaktionen
|
||||
Enhanced Hilfsklasse für Gmail UI-Interaktionen mit 2025 Optimierungen
|
||||
"""
|
||||
|
||||
def __init__(self, page: Page, screenshots_dir: str = None, save_screenshots: bool = True):
|
||||
@ -75,12 +75,27 @@ class GmailUIHelper:
|
||||
|
||||
def click_with_retry(self, selector: str, max_attempts: int = 3) -> bool:
|
||||
"""
|
||||
Klickt auf ein Element mit Wiederholungsversuchen
|
||||
Enhanced Click mit mehreren Strategien und Wiederholungsversuchen
|
||||
"""
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
self.page.click(selector)
|
||||
return True
|
||||
# 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:
|
||||
@ -98,12 +113,12 @@ class GmailUIHelper:
|
||||
except Exception as e:
|
||||
logger.warning(f"Fehler beim Scrollen zu {selector}: {e}")
|
||||
|
||||
def is_element_visible(self, selector: str) -> bool:
|
||||
def is_element_visible(self, selector: str, timeout: int = 1000) -> bool:
|
||||
"""
|
||||
Prüft ob ein Element sichtbar ist
|
||||
Prüft ob ein Element sichtbar ist mit optionalem Timeout
|
||||
"""
|
||||
try:
|
||||
return self.page.locator(selector).is_visible()
|
||||
return self.page.locator(selector).is_visible(timeout=timeout)
|
||||
except:
|
||||
return False
|
||||
|
||||
@ -127,6 +142,77 @@ class GmailUIHelper:
|
||||
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):
|
||||
"""
|
||||
@ -137,15 +223,27 @@ class GmailUIHelper:
|
||||
except Exception as e:
|
||||
logger.warning(f"Navigation-Timeout nach {timeout}ms: {e}")
|
||||
|
||||
def wait_for_loading_to_finish(self):
|
||||
def wait_for_loading_to_finish(self, extra_wait: bool = True):
|
||||
"""
|
||||
Wartet bis Ladeanimation verschwunden ist
|
||||
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):
|
||||
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
|
||||
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))
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren