9587726936
- BrowserBehaviorView: laedt gespeicherte Matrix (GET), sonst "Browser-Test starten" (POST run, Live-Lauf). Per-Browser-Tabelle (Cookies vor Consent / nach Ablehnen / Ablehnen respektiert / Oberflaeche / Score), Engine-Detail mit Banner-Screenshot + Oberflaechen-Befunden, Mobil-Badge, "nicht verfuegbar"-Zeilen fuer fehlende Browser (arm64-Dev). - Proxys browser-behavior (GET) + browser-behavior/run (POST, langer Timeout). - page.tsx: Tab "Browser-Verhalten" (sichtbar sobald scanbare URL im Snapshot). - consent-tester scan_matrix_summary: banner_findings je Engine im summary (Text/Severity/Norm) → Oberflaechen-Befunde im Tab. - tsc strict clean; Vitest BrowserBehaviorView (2). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
96 lines
4.2 KiB
Python
96 lines
4.2 KiB
Python
"""Kompakte Per-Engine-Projektion eines ConsentTestResult für die Browser-Matrix.
|
||
|
||
Die Matrix braucht NICHT die volle `/scan`-Antwort — nur die Felder, die je
|
||
Browser-Zeile angezeigt + persistiert werden: Cookies vor Consent / nach
|
||
Ablehnen, ob „Ablehnen" respektiert wurde, Oberflächen-Signale, Screenshot.
|
||
Bewusst schlank gehalten, damit der in `banner_result.browser_matrix` (JSONB)
|
||
persistierte Block klein bleibt — 6 Engines × voller Cookie-Liste + Screenshot
|
||
würde sonst schnell mehrere MB groß (BMW: ~780 Cookies je Phase).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
# Cookie-Namen je Phase deckeln — die Matrix zeigt Zahlen + Beispiele, nicht
|
||
# die volle Liste (die steckt im textbasierten Cookie-Modul).
|
||
_NAME_CAP = 40
|
||
_TRACK_CAP = 20
|
||
|
||
|
||
def _vdict(v: Any) -> dict:
|
||
"""Violation (dataclass/obj/dict) → serialisierbares dict."""
|
||
if isinstance(v, dict):
|
||
return v
|
||
return getattr(v, "__dict__", None) or {"text": str(v)}
|
||
|
||
|
||
def matrix_scan_dict(result: Any) -> dict:
|
||
"""`ConsentTestResult` → dict in der Form, die
|
||
`multi_browser_scanner._extract_dimensions` liest (phases/banner_checks)
|
||
plus ein kompakter `summary`-Block für Frontend + Persistenz.
|
||
|
||
Defensiv via getattr — funktioniert auch, falls der Scanner mal ein
|
||
bereits serialisiertes dict liefert (dann greifen die Defaults)."""
|
||
before = list(getattr(result, "before_cookies", []) or [])
|
||
after = list(getattr(result, "reject_cookies", []) or [])
|
||
before_violations = list(getattr(result, "before_violations", []) or [])
|
||
reject_violations = list(getattr(result, "reject_violations", []) or [])
|
||
reject_new_tracking = list(getattr(result, "reject_new_tracking", []) or [])
|
||
banner_text_violations = list(
|
||
getattr(result, "banner_text_violations", []) or [])
|
||
provider = getattr(result, "banner_provider", "") or ""
|
||
|
||
summary = {
|
||
"cookies_before_consent": len(before),
|
||
"cookies_after_reject": len(after),
|
||
"cookies_before_names": before[:_NAME_CAP],
|
||
"cookies_after_reject_names": after[:_NAME_CAP],
|
||
# „Ablehnen respektiert" = nach dem Klick auf „Ablehnen" keine Verstöße
|
||
# UND kein neuer Tracker. Verbleibende essentielle Cookies (z.B. die
|
||
# gespeicherte Consent-Entscheidung selbst) sind erlaubt → NICHT über
|
||
# die reine Cookie-Zahl bewerten (sonst False Positive).
|
||
"reject_respected": (len(reject_violations) == 0
|
||
and len(reject_new_tracking) == 0),
|
||
"reject_new_tracking": reject_new_tracking[:_TRACK_CAP],
|
||
"banner_detected": bool(getattr(result, "banner_detected", False)),
|
||
"banner_provider": provider,
|
||
"banner_screenshot_b64": getattr(result, "banner_screenshot_b64", "") or "",
|
||
"surface": {
|
||
"has_impressum_link": bool(
|
||
getattr(result, "banner_has_impressum_link", False)),
|
||
"has_dse_link": bool(
|
||
getattr(result, "banner_has_dse_link", False)),
|
||
"banner_text_issues": len(banner_text_violations),
|
||
},
|
||
# Oberflächen-Befunde je Engine (die 20 Banner-Checks: Button-Prominenz,
|
||
# Toggle-Vorauswahl, Einleitungstext/Links …) — Text + Severity +
|
||
# Norm-Bezug. Aggregierte Maßnahmen folgen im Cross-Finding.
|
||
"banner_findings": [
|
||
{"text": d.get("text", ""),
|
||
"severity": d.get("severity", "MEDIUM"),
|
||
"legal_ref": d.get("legal_ref", ""),
|
||
"service": d.get("service", "")}
|
||
for d in (_vdict(v) for v in banner_text_violations)
|
||
][:20],
|
||
"violations": {
|
||
"before_consent": len(before_violations),
|
||
"after_reject": len(reject_violations),
|
||
"banner_text": len(banner_text_violations),
|
||
},
|
||
}
|
||
|
||
return {
|
||
"banner_detected": bool(getattr(result, "banner_detected", False)),
|
||
"banner_provider": provider,
|
||
# Minimal-Form für _extract_dimensions (nur cookies-Listen + violations):
|
||
"phases": {
|
||
"before_consent": {"cookies": before},
|
||
"after_reject": {"cookies": after},
|
||
},
|
||
"banner_checks": {
|
||
"violations": [_vdict(v) for v in banner_text_violations],
|
||
},
|
||
"summary": summary,
|
||
}
|