7938e377b6
CI / branch-name (push) Has been skipped
CI / detect-changes (push) Successful in 11s
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / validate-canonical-controls (push) Successful in 14s
CI / loc-budget (push) Failing after 15s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Failing after 48s
CI / iace-gt-coverage (push) Successful in 25s
CI / test-python-backend (push) Successful in 43s
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
User-Feedback in einer Session: "Wir erzeugen nur Panik. Egal was da steht,
es dauert Wochen. Wir sind Tool an der Seite von CMO/GF/CIO, nicht Gegner."
Memory: feedback_breakpilot_tonalitaet.md (gilt fuer ALLE Module + Marketing).
P89 Critical-Findings-Block ENTFERNT/UMGEBAUT — keine Panik-Rot-Box mehr.
- Statt "🚨 SOFORTMASSNAHMEN ERFORDERLICH" -> "Zusammenfassung fuer
die Geschaeftsfuehrung", blauer dezenter Block
- Statt "VERSTOSSE" -> "Themen zur Besprechung mit DSB, Marketing
und Entwicklung"
- Statt "Bussgeldrahmen 4% Weltumsatz" als Erstes -> realistische
Einordnung (0,1-1%) in dezenter Schluss-Notiz mit Konfidenz-Hinweis
- "Sofortmassnahme" -> "Empfehlung"
- "Themen 1, 2, 3..." statt "HIGH"-Badges (P87-Vorbereitung)
- Explizite Zeitschaetzung "4-8 Wochen (DSB -> Agentur -> Dev -> Freigabe)"
P76 Mercedes-Sekundaer-Buttons (Datenschutzerklaerung + Impressum klein
unter den 3 Haupt-Buttons) erkennen. Walker scant jetzt label-basiert
ALLE klickbaren Elemente im Shadow-DOM (wb7-link, wb7-link-secondary,
wb7-button-text, span[onclick], small a, [role=button], etc.).
Vermeidet Mercedes-Impressum-False-Positive der Phase 1.
P91 VVT-Tabellen-Renderer in neuer Co-Pilot-Tonalitaet. Statt
"Verstoss-Liste mit Bussgeldpotenzial" -> Wahrscheinlichkeits-Aussage:
"Bei Anbieter-Reduktion + Wechsel zu europaeischen Alternativen ist
Reduktion des Tracking-Footprints + Lizenz-Einsparung wahrscheinlich.
Fundierte Bewertung erfordert DSB-Abstimmung."
BMW-Bug B1-B4 (P90) bewusst nicht in diesem Commit: BMW-Lauf hat ePaaS
4x captured im consent-tester, aber Backend bekommt 0 cmp_payloads.
Wiring-Bug zwischen consent-tester /dsi-discovery und Backend
_fetch_text — eigene Diagnose-Session noetig (siehe Task P90).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
128 lines
5.3 KiB
Python
128 lines
5.3 KiB
Python
"""
|
|
Browser-side DOM walkers for Web-Component CMPs and OEM design-systems.
|
|
|
|
Centralizes the JavaScript snippets used by banner_text_checker.py so the
|
|
checker file stays under the 500-LOC cap. Each function returns a JS string
|
|
that Playwright passes to `page.evaluate()`.
|
|
|
|
Two walkers:
|
|
* SHADOW_BANNER_WALKER_JS — pierces shadow DOM (Mercedes cmm-cookie-banner,
|
|
BMW cookie-consent-banner, etc.) and extracts banner text + label-based
|
|
legal links (P63 — recognizes wb7-link/role=link/button, not just
|
|
<a href>, since OEM design-systems wrap navigation).
|
|
* FOOTER_LABELS_WALKER_JS — collects unique footer link labels from any
|
|
candidate footer root (footer, [role=contentinfo], wb7-footer, ...) with
|
|
a bottom-25%-of-viewport fallback (P64).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
SHADOW_BANNER_WALKER_JS = """() => {
|
|
const LEGAL_KW = {
|
|
impressum: ['impressum','imprint','legal notice','mentions legales','colophon'],
|
|
dse: ['datenschutz','privacy','dsgvo','data protection','politique de confidentialite'],
|
|
};
|
|
function isLegalLabel(txt) {
|
|
const t = (txt||'').toLowerCase();
|
|
if (!t || t.length > 60) return null;
|
|
for (const k of LEGAL_KW.impressum) if (t.includes(k)) return 'impressum';
|
|
for (const k of LEGAL_KW.dse) if (t.includes(k)) return 'dse';
|
|
return null;
|
|
}
|
|
// P76: EXTENDED — scan ANY clickable element by label, not just
|
|
// <a href> or named web-components. Mercedes uses small secondary
|
|
// buttons below the main 3 actions: "Datenschutzerklaerung" + "Impressum"
|
|
// as <wb7-link>/<button>/<small><a> — generic label-based scan catches them.
|
|
function collectLegalLinksFromRoot(rootEl, acc) {
|
|
if (!rootEl || !rootEl.querySelectorAll) return;
|
|
// Generic scan: ALLE klickbaren/Link-aussehenden Elemente
|
|
const cands = rootEl.querySelectorAll(
|
|
'a, button, [role="link"], [role="button"], ' +
|
|
'wb7-link, wb7-button, wb7-button-text, wb7-link-secondary, ' +
|
|
'span[onclick], small a, small button, ' +
|
|
'[class*="link" i], [class*="button" i]'
|
|
);
|
|
const seen = new Set();
|
|
for (const c of cands) {
|
|
const label = (c.textContent || '').trim();
|
|
if (!label || label.length > 60) continue;
|
|
const which = isLegalLabel(label);
|
|
if (!which) continue;
|
|
const key = which + '|' + label.toLowerCase();
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
const href = (c.getAttribute('href') ||
|
|
c.getAttribute('data-href') ||
|
|
c.getAttribute('data-uri') ||
|
|
c.getAttribute('data-url') || '').toLowerCase();
|
|
acc.links.push({
|
|
href: href || ('#label-' + which),
|
|
text: label.toLowerCase(),
|
|
});
|
|
}
|
|
}
|
|
function walk(root, acc) {
|
|
if (!root) return;
|
|
const all = root.querySelectorAll ? root.querySelectorAll('*') : [];
|
|
for (const el of all) {
|
|
if (el.shadowRoot) walk(el.shadowRoot, acc);
|
|
}
|
|
const tags = ['cmm-cookie-banner', 'cookie-consent-banner',
|
|
'consent-banner', 'cookie-banner', 'cmp-banner',
|
|
'ot-banner', 'usercentrics-banner'];
|
|
for (const tag of tags) {
|
|
const els = root.querySelectorAll ? root.querySelectorAll(tag) : [];
|
|
for (const el of els) {
|
|
if (el.shadowRoot) {
|
|
const txt = (el.shadowRoot.textContent || '').trim();
|
|
if (txt) acc.text += ' ' + txt;
|
|
// P76: full label-based scan of shadow content
|
|
collectLegalLinksFromRoot(el.shadowRoot, acc);
|
|
// Legacy: plain <a href> for backward compatibility
|
|
const links = el.shadowRoot.querySelectorAll('a[href]');
|
|
for (const a of links) {
|
|
acc.links.push({
|
|
href: (a.getAttribute('href') || '').toLowerCase(),
|
|
text: (a.textContent || '').trim().toLowerCase(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const acc = { text: '', links: [] };
|
|
walk(document, acc);
|
|
return acc;
|
|
}"""
|
|
|
|
|
|
FOOTER_LABELS_WALKER_JS = """() => {
|
|
const out = new Set();
|
|
const roots = [
|
|
...document.querySelectorAll(
|
|
'footer, [role="contentinfo"], ' +
|
|
'wb7-footer, wb-footer, b-footer, cmm-footer, ' +
|
|
'[class*="footer" i], [id*="footer" i]'
|
|
)
|
|
];
|
|
if (roots.length === 0) {
|
|
const viewH = window.innerHeight;
|
|
for (const el of document.querySelectorAll('a, button, [role="link"], wb7-link')) {
|
|
const r = el.getBoundingClientRect();
|
|
if (r.top > viewH * 0.75) roots.push(el.parentElement);
|
|
}
|
|
}
|
|
for (const root of roots) {
|
|
if (!root) continue;
|
|
const cands = root.querySelectorAll('a, button, [role="link"], wb7-link, wb7-button');
|
|
let n = 0;
|
|
for (const c of cands) {
|
|
if (n++ > 80) break;
|
|
const t = (c.textContent || '').trim().toLowerCase();
|
|
if (t && t.length < 60) out.add(t);
|
|
}
|
|
}
|
|
return [...out];
|
|
}"""
|