import AxeBuilder from '@axe-core/playwright'; import { expect, test, type Page } from '@playwright/test'; import path from 'node:path'; const dashboard = { id: 'polish-dashboard', slug: 'operations', name: 'Netwerkoperaties', description: 'Actuele netwerkbelasting en operationele wijzigingen.', scope: 'system', revision: 4, currentVersion: 7 }; const widgets = [ { id: 'network', title: 'Netwerkbelasting', type: 'timeseries', data: { sourceType: 'semantic-metric', metric: 'host.network.receive', aggregation: 'avg' }, visualization: { unit: 'bytesPerSecond', decimals: 0, legend: true }, behavior: { locked: false, hidden: false, liveIntervalSeconds: 30 }, layouts: { desktop: { x: 0, y: 0, w: 9, h: 5, visible: true } }, }, { id: 'events', title: 'Recente wijzigingen', type: 'event-timeline', data: { sourceType: 'events', limit: 12 }, behavior: { locked: false, hidden: false, liveIntervalSeconds: 30 }, layouts: { desktop: { x: 9, y: 0, w: 9, h: 5, visible: true } }, }, ]; async function mockDashboard(page: Page): Promise { await page.route('**/api/v1/**', async (route) => { const requestPath = new URL(route.request().url()).pathname; const body = requestPath === '/api/v1/dashboards/polish-dashboard' ? { dashboard, version: { document: { schemaVersion: 2, widgets, variables: [], settings: { defaultTimeRange: '1h' } } } } : requestPath === '/api/v1/metrics/query-range' ? { status: 'success', data: { result: [{ metric: { __name__: 'host_network_receive', host: 'tower', interface: 'eth0' }, values: [[1786420800, '1200'], [1786420815, '1500']] }] }, provenance: { source: 'prometheus', metric: 'host.network.receive', catalogVersion: '1', cacheKey: 'test' }, sourceObservedAt: '2026-08-11T04:01:00Z', receivedAt: '2026-08-11T04:01:01Z', freshness: 'fresh', cacheHit: false } : requestPath === '/api/v1/events' ? { items: [{ id: 'event-1', type: 'container.restart', severity: 'warning', summary: 'container.restart', occurredAt: '2026-08-11T04:00:00Z' }] } : requestPath === '/api/v1/system/status' ? { version: '1', generatedAt: '2026-08-11T04:01:00Z', overallState: 'healthy', components: [{ id: 'database', state: 'healthy', reason: 'database_ready' }], backup: { state: 'healthy', reason: 'backup_verified' }, sourceLag: [] } : {}; await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(body) }); }); } async function expectNoSeriousAxeViolations(page: Page): Promise { const results = await new AxeBuilder({ page }).analyze(); const violations = results.violations.filter((item) => item.impact === 'critical' || item.impact === 'serious'); expect(violations, violations.map((item) => `${item.id}: ${item.help}`).join('\n')).toEqual([]); } test('dashboard and editor use human labels, safe modes and accessible keyboard controls', async ({ page }, testInfo) => { test.skip(testInfo.project.name !== 'desktop-chromium', 'The editor acceptance proof uses the desktop canvas.'); await mockDashboard(page); await page.goto('/dashboards/polish-dashboard'); await expect(page.getByRole('heading', { level: 1, name: 'Netwerkoperaties' })).toBeVisible(); await expect(page.getByRole('heading', { level: 2, name: 'Dashboardwidgets' })).toBeAttached(); await expect(page.getByRole('heading', { level: 3, name: 'Netwerkbelasting' })).toBeVisible(); await expect(page.getByRole('list', { name: 'Legenda' })).toContainText('tower ยท eth0'); await expect(page.locator('.metric-chart-line')).toHaveAttribute('d', 'M 28.000 192.000 L 628.000 12.000'); await expect(page.getByText('Container herstart', { exact: false })).toBeVisible(); await expect(page.getByText('container.restart', { exact: true })).toHaveCount(0); await expect(page.locator('body')).not.toContainText('{"__name__"'); await expectNoSeriousAxeViolations(page); if (process.env.PULSE_CAPTURE_VISUALS) await page.screenshot({ path: path.resolve('../../artifacts/evidence/M12-07/dashboard-human-labels.png'), fullPage: true }); await page.getByRole('button', { name: 'Bewerken' }).click(); await expect(page.getByRole('heading', { level: 1, name: 'Dashboard aanpassen' })).toBeVisible(); await expect(page.getByRole('heading', { level: 2, name: 'Dashboardindeling' })).toBeAttached(); const advanced = page.getByText('Dashboardvariabelen, sjablonen en gegevensoverdracht', { exact: true }); await expect(advanced).toBeVisible(); await expect(page.getByRole('heading', { name: 'Import, export en templates' })).toBeHidden(); await expect(page.locator('.editor-widget-actions').first()).toContainText('Omhoog'); await expect(page.locator('.editor-widget-actions').first()).toContainText('Omlaag'); await expect(page.locator('.editor-widget-actions').first()).toContainText('Breedte'); await expect(page.getByText('Weergavemodus')).toHaveCount(0); const editorWidgets = page.locator('.editor-widget'); await expect(editorWidgets.locator('h3')).toHaveText(['Netwerkbelasting', 'Recente wijzigingen']); const firstWidget = await editorWidgets.first().boundingBox(); expect(firstWidget).not.toBeNull(); await page.mouse.move(firstWidget!.x + 30, firstWidget!.y + 30); await page.mouse.down(); await page.mouse.move(firstWidget!.x + 30, firstWidget!.y + 75, { steps: 4 }); await page.mouse.up(); await expect(editorWidgets.locator('h3')).toHaveText(['Recente wijzigingen', 'Netwerkbelasting']); const resize = page.getByRole('slider', { name: 'Breedte aanpassen: Netwerkbelasting' }); await expect(resize).toHaveAttribute('aria-valuenow', '9'); await resize.focus(); await page.keyboard.press('ArrowRight'); await expect(resize).toHaveAttribute('aria-valuenow', '10'); if (process.env.PULSE_CAPTURE_VISUALS) await page.screenshot({ path: path.resolve('../../artifacts/evidence/M12-07/editor-keyboard-and-disclosure.png'), fullPage: true }); await advanced.click(); await expect(page.getByRole('heading', { level: 2, name: 'Import, export en templates' })).toBeVisible(); await expectNoSeriousAxeViolations(page); expect(await page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth)).toBe(true); if (process.env.PULSE_CAPTURE_VISUALS) await page.screenshot({ path: path.resolve('../../artifacts/evidence/M12-07/editor-advanced-open.png'), fullPage: true }); });