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",
)
def _hover_sender(self, display_name: str) -> str:
"""Find the sender's avatar/name and hover to open the profile card.
Returns the strategy used, or '' if not found."""
def _hover_sender(self, display_name: str) -> tuple[str, dict]:
"""Find the sender element and hover to open the profile card.
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']"):
try:
if display_name not in header.inner_text():
continue
avatar = header.query_selector("[data-tid='reply-message-header-avatar']")
if avatar:
avatar.scroll_into_view_if_needed()
avatar.hover()
return "reply-avatar"
bbox = _mouse_move_to(avatar)
if bbox:
return "reply-avatar", bbox
for span in header.query_selector_all("span"):
if span.inner_text().strip() == display_name:
span.scroll_into_view_if_needed()
span.hover()
return "reply-span"
bbox = _mouse_move_to(span)
if bbox:
return "reply-span", bbox
except Exception:
continue
# Strategy 2: aria-label containing the name (Fluent UI persona elements)
# Strategy 2: aria-label
safe_name = display_name.replace("'", "\\'")
for el in self._page.query_selector_all(f"[aria-label*='{safe_name}']"):
try:
if el.is_visible():
el.scroll_into_view_if_needed()
el.hover()
return "aria-label"
bbox = _mouse_move_to(el)
if bbox:
return "aria-label", bbox
except Exception:
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']"):
try:
if el.inner_text().strip() == display_name:
el.scroll_into_view_if_needed()
el.hover()
return "author-name"
if el.inner_text().strip() != display_name:
continue
# Look for avatar in parent tree (usually 3-4 levels up)
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:
continue
return ""
return "", {}
def _extract_email_from_profile(self, display_name: str) -> str | None:
try:
strategy = self._hover_sender(display_name)
strategy, bbox = self._hover_sender(display_name)
except Exception as e:
print(f" Hover auf '{display_name}' fehlgeschlagen: {e}")
return None
@ -100,7 +129,18 @@ class Resolver:
if not strategy:
print(f" '{display_name}' nicht im sichtbaren Chat gefunden.")
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:
# Stage 1: wait for card container to appear
@ -136,12 +176,22 @@ class Resolver:
time.sleep(0.3)
return email or None
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("""() => {
const lpcCard = document.querySelector('.lpc_ip_root_class lpc-card');
if (!lpcCard || !lpcCard.shadowRoot) return '(kein Shadow Root)';
return lpcCard.shadowRoot.textContent.trim().substring(0, 200);
if (!lpcCard) return '(kein lpc-card Element)';
if (!lpcCard.shadowRoot) return '(kein Shadow Root)';
return lpcCard.shadowRoot.textContent.trim().substring(0, 300);
}""") 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)
return None