Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,424 @@
# 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