107 lines
6.1 KiB
TypeScript
107 lines
6.1 KiB
TypeScript
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;
|
|
}
|
|
if (pathname === '/api/v1/host') {
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ hardware: { status: { state: 'healthy' }, capabilities: [], temperatures: [], fans: [], gpus: [] } }) });
|
|
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(8);
|
|
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('Wijzigen: beheerder')).toBeVisible();
|
|
await expect(hub.getByText('Wijzigen: operator')).toBeVisible();
|
|
const capabilities = page.getByRole('region', { name: 'Capabilitystatus' });
|
|
await expect(capabilities).toContainText('Unraid');
|
|
await expect(capabilities).toContainText('HardwaretelemetrieBeschikbaar');
|
|
await expect(capabilities).toContainText('UPSNiet geconfigureerd');
|
|
await expect(capabilities.getByRole('link', { name: 'Open systeemstatus en back-up' })).toHaveAttribute('href', '/status');
|
|
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);
|
|
const reconfigure = page.getByRole('button', { name: 'Herconfiguratie openen' });
|
|
if (testInfo.project.name === 'mobile-chromium') {
|
|
await reconfigure.scrollIntoViewIfNeeded();
|
|
expect(await reconfigure.evaluate((button) => {
|
|
const box = button.getBoundingClientRect();
|
|
const target = document.elementFromPoint(box.left + box.width / 2, box.top + box.height / 2);
|
|
return target === button || button.contains(target);
|
|
})).toBe(true);
|
|
await reconfigure.focus();
|
|
await page.keyboard.press('Enter');
|
|
} else {
|
|
await reconfigure.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);
|
|
}
|
|
});
|