import AxeBuilder from '@axe-core/playwright'; import { expect, test } from '@playwright/test'; import path from 'node:path'; const systemStatus = { version: '1.3.0', release: { version: '1.3.0', commit: 'abc1234', builtAt: '2026-08-21T12:00:00Z', migrationVersion: '0024' }, generatedAt: new Date().toISOString(), overallState: 'healthy', components: [], backup: { state: 'healthy', reason: 'backup_verified', ageSeconds: 30 * 60 * 60, verifiedAt: '2026-08-20T06:00:00Z' }, sourceLag: [{ sourceId: 'unraid', state: 'healthy', reason: 'fresh', ageSeconds: 5 }], }; const onboarding = { state: { completed: true, step: 'completed', dashboardChoice: 'default', rulesChoice: 'default', dashboardId: 'overview', rulesReady: true }, capabilities: [ { id: 'auth', state: 'ready', detail: 'Aanmelding geconfigureerd.' }, { id: 'database', state: 'ready', detail: 'Database beschikbaar.' }, { id: 'prometheus', state: 'ready', detail: 'Meetgegevens beschikbaar.' }, { id: 'unraid', state: 'ready', detail: 'Unraid-bron beschikbaar.' }, ], resume: false, }; test('beheerhub, afgeronde onboarding en backupouderdom blijven taakgericht en waarheidsgetrouw', async ({ page }, testInfo) => { test.skip(testInfo.project.name === 'wallboard-chromium', 'Beheerflows gebruiken de desktop-, tablet- en mobiele shell.'); await page.route('**/api/v1/**', async (route) => { const pathname = new URL(route.request().url()).pathname; if (pathname === '/api/v1/system/status') { if (route.request().method() === 'POST') { await route.fulfill({ status: 403, contentType: 'application/problem+json', body: '{}' }); return; } await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ ...systemStatus, generatedAt: new Date().toISOString() }) }); return; } if (pathname === '/api/v1/onboarding') { await route.fulfill({ contentType: 'application/json', body: JSON.stringify(onboarding) }); return; } await route.fulfill({ status: 404, contentType: 'application/problem+json', body: '{}' }); }); await page.goto('/settings'); await expect(page.getByRole('heading', { name: 'Pulse configureren' })).toBeVisible(); const hub = page.getByRole('region', { name: 'Beheerfuncties' }); await expect(hub.getByRole('link')).toHaveCount(6); await expect(hub.getByRole('link', { name: /Systeemstatus en backup/ })).toHaveAttribute('href', '/status'); await expect(hub.getByRole('link', { name: /Eerste configuratie/ })).toHaveAttribute('href', '/onboarding'); await expect(hub.getByRole('link', { name: /Alertregels/ })).toHaveAttribute('href', '/alerts?section=rules'); await expect(hub.getByRole('link', { name: /Stiltes en onderhoud/ })).toHaveAttribute('href', '/alerts?section=controls'); await expect(hub.getByText('Backupactie: beheerder')).toBeVisible(); await expect(hub.getByText('Wijzigen: operator')).toBeVisible(); if (process.env.PULSE_CAPTURE_VISUALS && (testInfo.project.name === 'desktop-chromium' || testInfo.project.name === 'mobile-chromium')) { await page.screenshot({ path: path.resolve('../../artifacts/evidence/M14-03', `settings-${testInfo.project.name}.png`), fullPage: true }); } await hub.getByRole('link', { name: /Systeemstatus en backup/ }).click(); const backup = page.getByRole('article').filter({ has: page.getByText('Backupstatus', { exact: true }) }); await expect(page.getByRole('heading', { name: 'Pulse-systeemstatus' })).toBeVisible(); await expect(backup.locator('.status-badge')).toContainText('Aandacht'); await expect(backup.getByText(/backup is verlopen/i)).toBeVisible(); await expect(backup.getByText(/1 dag geleden/)).toBeVisible(); await expect(backup.getByText(/ouder dan 24 uur/)).toBeVisible(); await backup.getByRole('button', { name: 'Maak geverifieerde backup' }).click(); await expect(backup.getByRole('alert')).toContainText('Alleen beheerders'); await page.goto('/settings'); await page.getByRole('region', { name: 'Beheerfuncties' }).locator('a[href="/onboarding"]').click(); await expect(page.getByRole('heading', { name: 'Pulse is geconfigureerd' })).toBeVisible(); await expect(page.getByRole('radio')).toHaveCount(0); await page.getByRole('button', { name: 'Herconfiguratie openen' }).click(); await expect(page.getByRole('radio')).toHaveCount(4); await page.getByRole('button', { name: 'Annuleren' }).click(); await expect(page.getByRole('radio')).toHaveCount(0); expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true); const axe = await new AxeBuilder({ page }).analyze(); expect(axe.violations.filter((item) => item.impact === 'serious' || item.impact === 'critical')).toEqual([]); if (testInfo.project.name === 'mobile-chromium') { const linkHeights = await page.locator('.settings-hub-card a').evaluateAll((links) => links.map((link) => link.getBoundingClientRect().height)); expect(linkHeights.every((height) => height >= 44)).toBe(true); } });