<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Shadow DOM – minimal test page</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; margin: 2rem; }
h1 { font-size: 1.25rem; }
.note { font-size: .9rem; color: #444; margin-bottom: 1rem; }
section { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; margin-bottom: 1.25rem; }
code { background: #f6f7f8; padding: 0 .25rem; border-radius: 4px; }
</style>
</head>
<body>
<h1>Shadow DOM – abgespeckte Testseite</h1>
<p class="note">
Diese Seite enthält mehrere Varianten, damit ihr Locators/Shadow‑Piercing in Playwright/TestWeasel prüfen könnt:
<br>1) <code><cookie-banner-demo open></code> (offenes ShadowRoot)
<br>2) <code><cookie-banner-demo-closed></code> (geschlossenes ShadowRoot)
<br>3) Geschachtelte Shadow‑Roots via <code><brand-logo></code>
<br>4) <code><slot></code> / <code>::slotted</code>
<br>5) Iframe im ShadowRoot (separater Frame‑Context nötig)
</p>
<section>
<cookie-banner-demo id="open-banner" data-testid="open-banner">
<span slot="headline">Ein Moment für Deine Privatsphäre (Demo)</span>
<a slot="policy" href="#" target="_blank">Datenschutzerklärung</a>
<a slot="imprint" href="#" target="_blank">Impressum</a>
</cookie-banner-demo>
</section>
<section>
<cookie-banner-demo-closed id="closed-banner" data-testid="closed-banner"></cookie-banner-demo-closed>
<p class="note">Der obige Banner hat ein <strong>geschlossenes</strong> ShadowRoot. Playwright‑Standard‑Locators sollten hier <em>nicht</em> hineinsehen können.</p>
</section>
<template id="cookie-banner-template">
<style>
:host { --bg:#ffffff; --fg:#1a1a1a; --accent:#002878; font: 14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; }
.container { position: relative; max-width: 720px; margin: 0 auto; box-shadow: 0 10px 30px rgba(0,0,0,.15); border-radius: 8px; overflow: hidden; border: 1px solid #e6e6e6; }
header { display:flex; gap:.75rem; align-items:center; padding: 1rem; background: var(--bg); color: var(--fg); }
.text { flex: 1; }
.title { margin:0; font-weight:700; }
.links { display:flex; gap:1rem; font-size:.9rem; }
.actions { display:flex; gap:.5rem; padding: .75rem 1rem 1rem; }
button { border: 0; border-radius: 20px; padding:.6rem .9rem; font-weight:700; }
[data-testid="uc-deny-all-button"] { background:#e9e9ef; color:#111; }
[data-testid="uc-accept-all-button"] { background: var(--accent); color: white; }
.floating { position:absolute; right: .5rem; top:.5rem; }
.floating > button { width: 44px; height: 44px; border-radius: 50%; display:grid; place-items:center; background: var(--accent); color:#fff; }
/* Slot demo */
::slotted([slot="headline"]) { font-weight:700; }
::slotted(a[slot]) { color: var(--accent); text-decoration: underline; }
/* Nested component host area */
.brand { padding-left: .25rem; }
iframe { width:100%; height:100px; border:0; border-top: 1px dashed #dde; }
</style>
<div class="container" role="dialog" aria-modal="true">
<div class="floating">
<button aria-label="Öffnen Privatsphäre-Einstellungen" data-testid="uc-privacy-button">⚙️</button>
</div>
<header>
<brand-logo data-testid="brand-logo"></brand-logo>
<div class="text">
<h2 class="title" data-testid="uc-heading-title"><slot name="headline">Ein Moment für Deine Privatsphäre</slot></h2>
<div class="links">
<slot name="policy"></slot>
<slot name="imprint"></slot>
</div>
</div>
</header>
<div class="content" data-testid="uc-message-container" style="padding:0 1rem 1rem">
<p>Wir verwenden Cookies und ähnliche Technologien, um Inhalte zu personalisieren und die Nutzung zu analysieren.</p>
</div>
<div class="actions" data-testid="uc-buttons-container">
<button role="button" data-testid="uc-deny-all-button">Einwilligung ablehnen</button>
<button role="button" data-testid="uc-accept-all-button">Alles akzeptieren</button>
</div>
<iframe title="Demo Iframe" data-testid="uc-iframe" srcdoc="<!doctype html><html><body><p>Iframe im ShadowRoot. Frame‑Context nötig.</p><button id='inside-frame'>Klick mich</button></body></html>"></iframe>
</div>
</template>
<template id="brand-logo-template">
<style>
:host { display:inline-flex; align-items:center; gap:.5rem; }
.circle { width: 36px; height: 36px; border-radius: 50%; background: #002878; display:grid; place-items:center; color:#fff; font-weight: 800; }
.name { font-weight: 700; }
</style>
<div class="circle" aria-hidden="true">TW</div>
<div class="brand"><span class="name">TestWeasel</span></div>
</template>
<script>
// Brand logo with its own (nested) open ShadowRoot
class BrandLogo extends HTMLElement {
constructor(){ super(); this.attachShadow({mode:'open'}).append(document.getElementById('brand-logo-template').content.cloneNode(true)); }
}
customElements.define('brand-logo', BrandLogo);
// Open shadow-root banner
class CookieBannerDemo extends HTMLElement {
constructor(){
super();
const root = this.attachShadow({mode:'open'});
root.append(document.getElementById('cookie-banner-template').content.cloneNode(true));
}
}
customElements.define('cookie-banner-demo', CookieBannerDemo);
// Closed shadow-root variant
class CookieBannerDemoClosed extends HTMLElement {
constructor(){
super();
const root = this.attachShadow({mode:'closed'});
root.append(document.getElementById('cookie-banner-template').content.cloneNode(true));
// expose a tiny test handle to confirm it's closed (will be undefined from the outside)
this._shadowRootRef = root; // not reachable via element.shadowRoot
}
}
customElements.define('cookie-banner-demo-closed', CookieBannerDemoClosed);
</script>
</body>
</html>