diff --git a/.gitignore b/.gitignore index 34c8ec3..65d6508 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ release/ tmp_e2e_full/ tmp_bugtest/ tmp_dl/ +tmp_ui_shots/ # Dev-Scripts ohne Token (Stub, nicht produktiv) codeberg_api_upload.sh diff --git a/package-lock.json b/package-lock.json index bbf543a..ab9650e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "twitch-vod-manager", - "version": "5.0.12", + "version": "5.0.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "twitch-vod-manager", - "version": "5.0.12", + "version": "5.0.13", "license": "MIT", "dependencies": { "axios": "^1.16.1", diff --git a/package.json b/package.json index 4494ddd..50097b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "twitch-vod-manager", - "version": "5.0.12", + "version": "5.0.13", "description": "Twitch VOD Manager - Download Twitch VODs easily", "main": "dist/main.js", "author": "xRangerDE", diff --git a/scripts/ui-screenshot.js b/scripts/ui-screenshot.js new file mode 100644 index 0000000..aae720a --- /dev/null +++ b/scripts/ui-screenshot.js @@ -0,0 +1,119 @@ +// 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); }); diff --git a/src/index.html b/src/index.html index f953560..1adeedf 100644 --- a/src/index.html +++ b/src/index.html @@ -330,7 +330,7 @@
Video auswahlen um Vorschau zu sehen
+Video auswaehlen um Vorschau zu sehen