fix: click avatar/author via element handles, prevent wild scrolling

This commit is contained in:
Oliver Hofmann 2026-05-17 15:03:16 +02:00
parent 2caffab1fa
commit 00bbd196bf

View File

@ -6,6 +6,15 @@ from playwright.sync_api import Page
from teampulse.monitor import _PROFILE_EMAIL_SELECTOR from teampulse.monitor import _PROFILE_EMAIL_SELECTOR
# Selectors for the clickable sender element (avatar or name) that opens the profile card
_AUTHOR_SELECTORS = [
# Channel meeting: avatar next to reply-message-header name
"[data-tid='reply-message-header-avatar']",
"[data-tid='post-message-header-avatar']",
# Meeting chat: author name element
"[data-tid='message-author-name']",
]
class Resolver: class Resolver:
def __init__(self, cache_path: Path, page: Page): def __init__(self, cache_path: Path, page: Page):
@ -36,40 +45,57 @@ class Resolver:
encoding="utf-8", encoding="utf-8",
) )
def _extract_email_from_profile(self, display_name: str) -> str | None: def _find_and_click_sender(self, display_name: str) -> bool:
# Use Playwright's native click (real mouse events) to trigger the Teams profile card. """Find the sender in the visible chat and click to open their profile card."""
# Try multiple selectors that contain the sender's name.
selectors = [
f"[data-tid='message-author-name']:has-text('{display_name}')",
f"[data-tid='reply-message-header'] span:has-text('{display_name}')",
f"span:has-text('{display_name}')",
]
clicked = False # Strategy 1: find reply-message-header containing the name, click its avatar
for selector in selectors: for header in self._page.query_selector_all("[data-tid='reply-message-header']"):
try: try:
loc = self._page.locator(selector).first name_spans = header.query_selector_all("span")
if loc.is_visible(timeout=1000): for span in name_spans:
loc.click() if span.inner_text().strip() == display_name:
clicked = True avatar = header.query_selector(
break "[data-tid='reply-message-header-avatar']"
)
target = avatar or span
target.scroll_into_view_if_needed()
target.click()
return True
except Exception: except Exception:
continue continue
# Strategy 2: message-author-name (private meeting chat)
for el in self._page.query_selector_all("[data-tid='message-author-name']"):
try:
if el.inner_text().strip() == display_name:
el.scroll_into_view_if_needed()
el.click()
return True
except Exception:
continue
return False
def _extract_email_from_profile(self, display_name: str) -> str | None:
try:
clicked = self._find_and_click_sender(display_name)
except Exception as e:
print(f" Klick auf '{display_name}' fehlgeschlagen: {e}")
return None
if not clicked: if not clicked:
print(f" Sender '{display_name}' nicht im Chat gefunden.") print(f" '{display_name}' nicht im sichtbaren Chat gefunden.")
return None return None
try: try:
# Step 1: wait for the profile card container to appear # Wait for profile card container
self._page.wait_for_selector(".lpc_ip_root_class", timeout=5000) self._page.wait_for_selector(".lpc_ip_root_class", timeout=5000)
except Exception: except Exception:
print(f" Profilkarte für '{display_name}' öffnet sich nicht (Klick hat nicht gewirkt).") print(f" Profilkarte für '{display_name}' öffnet sich nicht.")
self._page.keyboard.press("Escape") self._page.keyboard.press("Escape")
return None return None
try: try:
# Step 2: extract email — try mailto link first, then plain text fallbacks
email = None email = None
for selector in [ for selector in [
".lpc_ip_root_class a[href*='mailto:']", ".lpc_ip_root_class a[href*='mailto:']",
@ -86,14 +112,14 @@ class Resolver:
break break
if not email: if not email:
# Diagnose: show card text content
card = self._page.query_selector(".lpc_ip_root_class") card = self._page.query_selector(".lpc_ip_root_class")
card_text = card.inner_text().strip()[:200] if card else "(leer)" card_text = card.inner_text().strip()[:200] if card else "(leer)"
print(f" Karte geöffnet, E-Mail nicht gefunden. Karteninhalt: {card_text!r}") print(f" Karte geöffnet, E-Mail nicht gefunden. Inhalt: {card_text!r}")
self._page.keyboard.press("Escape") self._page.keyboard.press("Escape")
time.sleep(0.5) time.sleep(0.5)
return email return email
except Exception as e: except Exception as e:
print(f" Fehler beim Lesen der Profilkarte: {e}") print(f" Fehler beim Lesen der Profilkarte: {e}")
self._page.keyboard.press("Escape") self._page.keyboard.press("Escape")