// UI-Screenshot-Harness — laedt die App, navigiert durch Tabs + Streamer // + Themes und schreibt PNGs nach tmp_ui_shots/. Kein Test-Assert, nur // visuelle Forensik fuer UI-Polishing. // // Run: node scripts/ui-screenshot.js const { _electron: electron } = require('playwright'); const fs = require('fs'); const path = require('path'); const OUT_DIR = path.join(process.cwd(), 'tmp_ui_shots'); async function run() { fs.rmSync(OUT_DIR, { recursive: true, force: true }); fs.mkdirSync(OUT_DIR, { recursive: true }); const electronPath = require('electron'); const app = await electron.launch({ executablePath: electronPath, args: ['.'], cwd: process.cwd() }); const win = await app.firstWindow(); await win.setViewportSize({ width: 1280, height: 900 }); await win.waitForTimeout(2500); const shot = async (name) => { const file = path.join(OUT_DIR, `${name}.png`); try { await win.screenshot({ path: file, animations: 'disabled', timeout: 8000 }); process.stdout.write(`shot: ${name}\n`); } catch (e) { process.stdout.write(`shot FAILED: ${name} — ${String(e).split('\n')[0]}\n`); } }; // Misst die Y-Position aller .vod-actions relativ zu ihrer Grid-Reihe. // Buttons in derselben Reihe muessen die gleiche bottom-Y haben. const checkButtonAlignment = async (label) => { const rows = await win.evaluate(() => { const cards = Array.from(document.querySelectorAll('.vod-card')); const byRow = {}; for (const card of cards) { const actions = card.querySelector('.vod-actions'); if (!actions) continue; const cardRect = card.getBoundingClientRect(); const actRect = actions.getBoundingClientRect(); const rowKey = Math.round(cardRect.top / 10) * 10; // Reihe nach top gruppieren (byRow[rowKey] = byRow[rowKey] || []).push(Math.round(actRect.top)); } // Pro Reihe: max-min der button-tops (sollte ~0 sein wenn aligned) const result = []; for (const [row, tops] of Object.entries(byRow)) { if (tops.length < 2) continue; result.push({ row: Number(row), spread: Math.max(...tops) - Math.min(...tops), count: tops.length }); } return result; }); const maxSpread = rows.reduce((m, r) => Math.max(m, r.spread), 0); process.stdout.write(` [align ${label}] rows=${rows.length} maxButtonTopSpread=${maxSpread}px ${maxSpread <= 2 ? 'OK' : 'MISALIGNED'}\n`); return maxSpread; }; // 1. Settings tab (alle Inputs/Selects/Checkboxes sichtbar) await win.evaluate(() => window.showTab('settings')); await win.waitForTimeout(600); await shot('01-settings'); // 2. Streamer mit vielen VODs + variablen Titel-Laengen const streamers = ['xqc', 'papaplatte', 'xrohat']; for (const s of streamers) { await win.evaluate(() => window.showTab('vods')); await win.evaluate(async (name) => { await window.selectStreamer(name); }, s); await win.waitForTimeout(4500); const count = await win.locator('.vod-card').count(); process.stdout.write(` ${s}: ${count} vod-cards\n`); await checkButtonAlignment(s); await shot(`02-vods-${s}`); } // 3. Queue (mit einem item drin) await win.evaluate(() => window.showTab('vods')); const vodCount = await win.locator('.vod-card').count(); if (vodCount > 0) { await win.locator('.vod-card .vod-btn.primary').first().click().catch(() => {}); await win.waitForTimeout(800); } await win.evaluate(() => window.showTab('queue')); await win.waitForTimeout(500); await shot('03-queue'); // 4. Clips / Cutter / Merge / Stats / Archive tabs for (const tab of ['clips', 'cutter', 'merge', 'stats', 'archive']) { await win.evaluate((t) => window.showTab(t), tab); await win.waitForTimeout(600); await shot(`04-tab-${tab}`); } // 5. Themes durchschalten — auf VODs-Tab mit xqc geladen await win.evaluate(() => window.showTab('vods')); await win.evaluate(async () => { await window.selectStreamer('xqc'); }); await win.waitForTimeout(3500); for (const theme of ['discord', 'youtube', 'apple', 'light', 'twitch']) { await win.evaluate((t) => window.changeTheme(t), theme); await win.waitForTimeout(500); await shot(`05-theme-${theme}`); } // 6. Settings im Light-Theme (Input-Backgrounds pruefen) await win.evaluate(() => window.changeTheme('light')); await win.evaluate(() => window.showTab('settings')); await win.waitForTimeout(600); await shot('06-settings-light'); await win.evaluate(() => window.changeTheme('twitch')); await app.close(); process.stdout.write(`\nDone. PNGs in ${OUT_DIR}\n`); } run().catch((e) => { process.stderr.write(String(e) + '\n'); process.exit(1); });