// Playwright-Check Analyse-Panel (Einschalten-Dialog + aktives Panel): statischer Server fuer public/ + gemockte API. // Nicht Teil von npm test. Aufruf: node test/ui/analyse.ui.mjs [Ausgabeordner] import http from 'node:http' import { readFile, mkdir } from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' import { chromium } from '/root/.npm/_npx/db89d7302a373f10/node_modules/playwright-core/index.mjs' const pub = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../public') const SHOTS = process.argv[2] || '/root/reports/website-analyse-2026-10-01' await mkdir(SHOTS, { recursive: true }) const TYPES = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.webmanifest': 'application/json' } const server = http.createServer(async (req, res) => { const url = new URL(req.url, 'http://x') try { const file = path.join(pub, url.pathname === '/' ? 'dashboard.html' : url.pathname) const data = await readFile(file) res.writeHead(200, { 'content-type': TYPES[path.extname(file)] || 'application/octet-stream' }) res.end(data) } catch { res.writeHead(404); res.end('nicht gefunden') } }) await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) const base = `http://127.0.0.1:${server.address().port}` const legal = { avv: '/rechtliches/avv-website-analyse-2026-10-02.html', lb: '/rechtliches/leistungsbeschreibung-website-analyse-2026-10-02.html' } const controller = { ok: true, company: 'Bäckerei Berg GmbH', address: 'Hauptstraße 1, 12345 Musterstadt', email: 'info@baeckerei-berg.example' } const base1 = { projectId: 'p1', projectName: 'Bäckerei Berg', liveUrl: 'https://baeckerei.example', features: { news: { state: 'off' } } } const SCENARIOS = { off: { ...base1, analyse: { state: 'off', eligible: true, accentColor: '#1d4ed8', avvVersion: '2026-10-02', legal, controller } }, missing: { ...base1, analyse: { state: 'off', eligible: true, accentColor: '#1d4ed8', avvVersion: '2026-10-02', legal, controller: { ok: false, company: '', address: '', email: '', missing: ['firma'] } } }, active: { ...base1, analyse: { state: 'active', eligible: true, accentColor: '#1d4ed8', avvVersion: '2026-10-02', legal, controller } }, } const days = Array.from({ length: 14 }, (_, i) => ({ day: `2026-09-${String(17 + i).padStart(2, '0')}`, pv: 20 + ((i * 7) % 13) * 4 })) const stats = { totals: { pv: 612, visitors: 214, contacts: 18, consentRate: 0.62 }, series: days, pages: [{ key: '/', count: 300 }, { key: '/kontakt', count: 120 }], refs: [{ key: '', count: 200 }, { key: 'google.com', count: 90 }], countries: [{ key: 'DE', count: 500 }], devices: [{ key: 'm', count: 380 }, { key: 'd', count: 230 }], contacts: [{ key: 'tel', count: 12 }], banner: { shown: 300, accept: 186, reject: 114 }, } let scenario = 'off' const browser = await chromium.launch() let failed = 0 const check = (name, ok) => { console.log(`${ok ? 'OK ' : 'FAIL'} ${name}`); if (!ok) failed += 1 } try { for (const [label, viewport] of [['desktop', { width: 1280, height: 900 }], ['phone', { width: 390, height: 844 }]]) { const ctx = await browser.newContext({ viewport }) const page = await ctx.newPage() const errors = [] page.on('pageerror', (e) => errors.push(e.message)) await page.route('**/api/**', async (route) => { const p = new URL(route.request().url()).pathname const json = (b) => route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(b) }) if (p === '/api/auth/me') return json({ authenticated: true, role: 'customer', customer: { id: 'c1', code: '1001', name: 'Berta Berg', email: 'b@example.test' } }) if (p === '/api/projects') return json({ projects: [{ id: 'p1', name: 'Bäckerei Berg', status: 'live' }] }) if (p === '/api/features') return json({ features: [] }) if (p === '/api/activity') return json({ tickets: [] }) if (p === '/api/funktionen') return json({ projects: [SCENARIOS[scenario]] }) if (p === '/api/funktionen/analyse/stats') return json(stats) if (p === '/api/funktionen/analyse/deactivate') return json({ ok: true }) return json({}) }) const open = async (sc) => { scenario = sc await page.goto('about:blank') await page.goto(`${base}/dashboard.html#analyse`) await page.waitForSelector('#analyse-body .kb-panel') await page.waitForTimeout(900) } const shot = async (name) => { const height = await page.evaluate(() => Math.min(document.documentElement.scrollHeight, 3200)) await page.setViewportSize({ width: viewport.width, height: Math.max(viewport.height, height) }) await page.waitForTimeout(150) await page.screenshot({ path: `${SHOTS}/task-7-${label}-${name}.png` }) await page.setViewportSize(viewport) } const noScroll = async (n) => check(`${label} kein_horizontaler_scroll ${n}`, await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)) await open('off') const text = await page.textContent('#analyse-body') check(`${label} verantwortlicher`, text.includes('Bäckerei Berg GmbH') && text.includes('info@baeckerei-berg.example')) check(`${label} links`, (await page.$$eval('#analyse-body a[href^="/rechtliches/"]', (a) => a.map((x) => x.getAttribute('href')))).join() === `${legal.avv},${legal.lb}`) check(`${label} knopf_gesperrt_ohne_haken`, await page.isDisabled('#an-on')) await page.check('#an-avv') check(`${label} knopf_frei_mit_haken`, !(await page.isDisabled('#an-on'))) await noScroll('einschalten') await shot('einschalten') await open('missing') await page.check('#an-avv') check(`${label} gesperrt_ohne_angaben`, (await page.isDisabled('#an-on')) && Boolean(await page.$('#an-missing'))) await open('active') await page.waitForSelector('.an-kpis') await noScroll('aktiv') await shot('aktiv') await page.click('#an-off-ask') await page.click('#an-off-yes') await page.waitForSelector('#an-off-done') check(`${label} ausschalten_bestaetigung`, (await page.textContent('#an-off-done')).includes('Ab jetzt werden keine Daten mehr gespeichert')) await shot('ausgeschaltet') check(`${label} keine_js_fehler`, errors.length === 0) await ctx.close() } } finally { await browser.close() server.close() } process.exit(failed ? 1 : 0)