// Playwright-Check admin_funktionen: statischer Server + JSON-Fixtures, nicht Teil von npm test. // Aufruf: node test/ui/admin-funktionen.ui.mjs import http from 'node:http' import { readFile } 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 TYPES = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon' } const news = (state, extra = {}) => ({ state, siteKey: 'abcdefghijklmnop', ticketWoid: null, adminNote: '', ticketMissing: false, ...extra }) const projects = [ { projectId: 'p1', projectName: 'Bäckerei', features: { news: news('off') } }, { projectId: 'p2', projectName: 'Café', features: { news: news('setup', { ticketMissing: true }) } }, { projectId: 'p3', projectName: 'Kiosk', features: { news: news('active', { ticketWoid: '1234' }) } }, ] const posts = [] const json = (res, status, body) => { res.writeHead(status, { 'content-type': 'application/json' }) res.end(JSON.stringify(body)) } const server = http.createServer(async (req, res) => { const url = new URL(req.url, 'http://x') if (url.pathname === '/api/auth/me') return json(res, 200, { authenticated: true, role: 'admin', admin: { name: 'Kenso', email: 'k@w.de' } }) if (url.pathname === '/api/portal-admin/customers') { return json(res, 200, { customers: [{ id: 'c1', code: '1001', name: 'Test GmbH', email: 't@t.de', customerStatus: 'customer', portalAccessEnabled: true, projectCount: 3 }] }) } if (url.pathname === '/api/portal-admin/funktionen/c1') return json(res, 200, { projects }) if (url.pathname === '/api/portal-admin/funktionen/c1/news' && req.method === 'POST') { let raw = '' for await (const chunk of req) raw += chunk const body = JSON.parse(raw) posts.push(body) const p = projects.find((x) => x.projectId === body.projectId) p.features.news = news(body.action === 'activate' ? 'active' : 'disabled', { adminNote: `2026-09-30T10:00:00.000Z Kenso: ${body.action}${body.note ? ` – ${body.note}` : ''}` }) return json(res, 200, { projects: [p] }) } if (url.pathname.startsWith('/api/')) return json(res, 200, {}) try { const file = path.join(pub, url.pathname === '/' ? 'admin.html' : url.pathname) res.writeHead(200, { 'content-type': TYPES[path.extname(file)] || 'application/octet-stream' }) res.end(await readFile(file)) } catch { res.writeHead(404) res.end() } }) await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) let failed = 0 const check = (name, ok, detail = '') => { console.log(`${ok ? 'OK ' : 'FAIL'} ${name}${detail ? ` (${detail})` : ''}`) if (!ok) failed += 1 } const browser = await chromium.launch() try { const page = await browser.newPage() const dialogs = [] page.on('dialog', (d) => { dialogs.push(d.type()); d.dismiss() }) page.on('pageerror', (e) => check('keine Seitenfehler', false, e.message)) await page.goto(`http://127.0.0.1:${server.address().port}/admin.html`) await page.click('.admin-tab[data-tab="customers"]') await page.click('[data-features="c1"]') await page.waitForSelector('[data-feature-project="p1"]') const row = (id) => page.locator(`[data-feature-project="${id}"]`) check('3 Zeilen', (await page.locator('[data-feature-project]').count()) === 3) check('Ticket fehlt sichtbar', (await row('p2').innerText()).includes('Ticket fehlt')) // Aktivieren: erst Rückfrage in der Zeile, nichts gesendet await row('p1').locator('[data-feature-ask="activate"]').click() check('Rückfrage statt Sofort-Aktion', posts.length === 0 && (await row('p1').innerText()).includes('ohne Zahlung aktivieren?')) await row('p1').locator('[data-feature-no]').click() check('Abbrechen stellt Knöpfe wieder her', (await row('p1').locator('[data-feature-ask="activate"]').count()) === 1 && posts.length === 0) await row('p1').locator('[data-feature-ask="activate"]').click() await row('p1').locator('[data-feature-note]').fill('Kulanz') await row('p1').locator('[data-feature-yes]').click() await page.waitForFunction(() => document.querySelector('[data-feature-project="p1"]')?.innerText.includes('Aktiv')) check('POST activate mit Notiz', JSON.stringify(posts[0]) === JSON.stringify({ projectId: 'p1', action: 'activate', note: 'Kulanz' }), JSON.stringify(posts[0])) check('Zeile zeigt Aktiv und Notiz', (await row('p1').innerText()).includes('Kenso: activate – Kulanz')) // Abschalten await row('p3').locator('[data-feature-ask="disable"]').click() await row('p3').locator('[data-feature-yes]').click() await page.waitForFunction(() => document.querySelector('[data-feature-project="p3"]')?.innerText.includes('Abgeschaltet')) check('Abschalten aktualisiert Zeile', posts[1]?.action === 'disable') check('keine Browser-Dialoge', dialogs.length === 0) await page.screenshot({ path: '/tmp/claude-0/-root/a97f1ff7-2f71-45a7-bb9f-eaeacf7e64f1/scratchpad/admin-funktionen.png' }) } finally { await browser.close() server.close() } console.log(failed ? `admin_funktionen: ${failed} FEHLER` : 'admin_funktionen: alle Prüfungen bestanden') process.exit(failed ? 1 : 0)