debug: mouse.move() with coords, nearby-avatar search, screenshots

This commit is contained in:
Oliver Hofmann 2026-05-17 17:38:10 +02:00
parent d6e7cde9dc
commit ad0a0d07b2

View File

@ -45,54 +45,83 @@ class Resolver:
encoding="utf-8", encoding="utf-8",
) )
def _hover_sender(self, display_name: str) -> str: def _hover_sender(self, display_name: str) -> tuple[str, dict]:
"""Find the sender's avatar/name and hover to open the profile card. """Find the sender element and hover to open the profile card.
Returns the strategy used, or '' if not found.""" Returns (strategy, bbox) or ('', {}) if not found."""
# Strategy 1: reply-message-header (channel meetings) — avatar next to name def _mouse_move_to(el) -> dict | None:
"""Scroll element into view, get its bbox, move mouse there."""
el.scroll_into_view_if_needed()
bbox = el.bounding_box()
if not bbox or bbox['width'] == 0:
return None
x = bbox['x'] + bbox['width'] / 2
y = bbox['y'] + bbox['height'] / 2
self._page.mouse.move(x, y)
return bbox
# Strategy 1: reply-message-header (channel meetings)
for header in self._page.query_selector_all("[data-tid='reply-message-header']"): for header in self._page.query_selector_all("[data-tid='reply-message-header']"):
try: try:
if display_name not in header.inner_text(): if display_name not in header.inner_text():
continue continue
avatar = header.query_selector("[data-tid='reply-message-header-avatar']") avatar = header.query_selector("[data-tid='reply-message-header-avatar']")
if avatar: if avatar:
avatar.scroll_into_view_if_needed() bbox = _mouse_move_to(avatar)
avatar.hover() if bbox:
return "reply-avatar" return "reply-avatar", bbox
for span in header.query_selector_all("span"): for span in header.query_selector_all("span"):
if span.inner_text().strip() == display_name: if span.inner_text().strip() == display_name:
span.scroll_into_view_if_needed() bbox = _mouse_move_to(span)
span.hover() if bbox:
return "reply-span" return "reply-span", bbox
except Exception: except Exception:
continue continue
# Strategy 2: aria-label containing the name (Fluent UI persona elements) # Strategy 2: aria-label
safe_name = display_name.replace("'", "\\'") safe_name = display_name.replace("'", "\\'")
for el in self._page.query_selector_all(f"[aria-label*='{safe_name}']"): for el in self._page.query_selector_all(f"[aria-label*='{safe_name}']"):
try: try:
if el.is_visible(): if el.is_visible():
el.scroll_into_view_if_needed() bbox = _mouse_move_to(el)
el.hover() if bbox:
return "aria-label" return "aria-label", bbox
except Exception: except Exception:
continue continue
# Strategy 3: message-author-name (private meeting chat) # Strategy 3: message-author-name — also try avatar to the left
for el in self._page.query_selector_all("[data-tid='message-author-name']"): for el in self._page.query_selector_all("[data-tid='message-author-name']"):
try: try:
if el.inner_text().strip() == display_name: if el.inner_text().strip() != display_name:
el.scroll_into_view_if_needed() continue
el.hover() # Look for avatar in parent tree (usually 3-4 levels up)
return "author-name" avatar_bbox = self._page.evaluate("""(nameEl) => {
let p = nameEl.parentElement;
for (let i = 0; i < 6; i++) {
if (!p) break;
const av = p.querySelector('[data-tid*="avatar"], [class*="avatar"] img, [class*="Avatar"] img');
if (av) {
const r = av.getBoundingClientRect();
if (r.width > 0) return {x: r.x + r.width/2, y: r.y + r.height/2, w: r.width, h: r.height};
}
p = p.parentElement;
}
return null;
}""", el)
if avatar_bbox:
self._page.mouse.move(avatar_bbox['x'], avatar_bbox['y'])
return "nearby-avatar", avatar_bbox
bbox = _mouse_move_to(el)
if bbox:
return "author-name", bbox
except Exception: except Exception:
continue continue
return "" return "", {}
def _extract_email_from_profile(self, display_name: str) -> str | None: def _extract_email_from_profile(self, display_name: str) -> str | None:
try: try:
strategy = self._hover_sender(display_name) strategy, bbox = self._hover_sender(display_name)
except Exception as e: except Exception as e:
print(f" Hover auf '{display_name}' fehlgeschlagen: {e}") print(f" Hover auf '{display_name}' fehlgeschlagen: {e}")
return None return None
@ -100,7 +129,18 @@ class Resolver:
if not strategy: if not strategy:
print(f" '{display_name}' nicht im sichtbaren Chat gefunden.") print(f" '{display_name}' nicht im sichtbaren Chat gefunden.")
return None return None
print(f" '{display_name}' via '{strategy}' gehovered...") print(f" '{display_name}' via '{strategy}' bei {bbox} — warte auf Karte...")
# Screenshot right after hover for debugging
try:
from pathlib import Path as _Path
debug_dir = _Path("debug"); debug_dir.mkdir(exist_ok=True)
safe = display_name.replace(" ", "_")
self._page.screenshot(path=str(debug_dir / f"{safe}_after_hover.png"))
except Exception:
pass
time.sleep(0.5) # Give Teams time to register the hover
try: try:
# Stage 1: wait for card container to appear # Stage 1: wait for card container to appear
@ -136,12 +176,22 @@ class Resolver:
time.sleep(0.3) time.sleep(0.3)
return email or None return email or None
except Exception: except Exception:
# Diagnose: show shadow root text if available # Screenshot + shadow root text for diagnosis
try:
from pathlib import Path as _Path
debug_dir = _Path("debug"); debug_dir.mkdir(exist_ok=True)
safe = display_name.replace(" ", "_")
self._page.screenshot(path=str(debug_dir / f"{safe}_card_state.png"))
except Exception:
pass
card_text = self._page.evaluate("""() => { card_text = self._page.evaluate("""() => {
const lpcCard = document.querySelector('.lpc_ip_root_class lpc-card'); const lpcCard = document.querySelector('.lpc_ip_root_class lpc-card');
if (!lpcCard || !lpcCard.shadowRoot) return '(kein Shadow Root)'; if (!lpcCard) return '(kein lpc-card Element)';
return lpcCard.shadowRoot.textContent.trim().substring(0, 200); if (!lpcCard.shadowRoot) return '(kein Shadow Root)';
return lpcCard.shadowRoot.textContent.trim().substring(0, 300);
}""") or "(leer)" }""") or "(leer)"
print(f" Karte für '{display_name}' offen, E-Mail nicht im Shadow Root. Inhalt: {card_text!r}") print(f" Karte für '{display_name}' offen, E-Mail nicht gefunden.")
print(f" Shadow-Root-Inhalt: {card_text!r}")
print(f" Screenshots in debug/{safe}_*.png")
self._page.mouse.move(0, 0) self._page.mouse.move(0, 0)
return None return None