// Headless dump of Authentik flow shadow DOM + screenshot. // Usage: node scripts/authentik-dom-dump.mjs [theme] import { chromium } from "@playwright/test" import { writeFileSync } from "node:fs" const url = process.argv[2] ?? "http://localhost/auth/if/flow/default-authentication-flow/" const outName = process.argv[3] ?? "login" const theme = process.argv[4] ?? "light" // light | dark const outDir = "/tmp/authentik-dom" await import("node:fs").then((fs) => fs.mkdirSync(outDir, { recursive: true })) const browser = await chromium.launch() const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 }, colorScheme: theme === "dark" ? "dark" : "light", ignoreHTTPSErrors: true, }) const page = await ctx.newPage() // Authentik advertises api.base as https://localhost (X-Forwarded-Proto), but nginx is http-only // here. Rewrite api.base to a same-origin relative URL before the flow bundle reads it. await page.addInitScript(() => { Object.defineProperty(window, "authentik", { configurable: true, set(v) { try { if (v && v.api) { v.api.base = location.origin + "/auth/" v.api.relBase = "/auth/" } } catch {} Object.defineProperty(window, "authentik", { value: v, writable: true, configurable: true, }) }, get() { return undefined }, }) }) await page.goto(url, { waitUntil: "networkidle", timeout: 30000 }) // Give Lit components time to render the stage. await page.waitForTimeout(2500) // Recursively serialize light + shadow DOM into an indented outline. const outline = await page.evaluate(() => { function attrs(el) { return [...el.attributes] .filter((a) => !["style"].includes(a.name)) .map((a) => (a.value ? `${a.name}="${a.value}"` : a.name)) .join(" ") } function walk(node, depth, lines) { const pad = " ".repeat(depth) if (node.nodeType === Node.ELEMENT_NODE) { const tag = node.tagName.toLowerCase() const a = attrs(node) lines.push(`${pad}<${tag}${a ? " " + a : ""}>`) if (node.shadowRoot) { lines.push(`${pad} #shadow-root`) for (const c of node.shadowRoot.children) walk(c, depth + 2, lines) } for (const c of node.children) walk(c, depth + 1, lines) } return lines } const lines = [] walk(document.documentElement, 0, lines) // Cap to keep output readable. return lines.slice(0, 1200).join("\n") }) writeFileSync(`${outDir}/${outName}-${theme}.dom.txt`, outline) await page.screenshot({ path: `${outDir}/${outName}-${theme}.png`, fullPage: true }) console.log(`wrote ${outDir}/${outName}-${theme}.dom.txt (+ .png)`) await browser.close()