This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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<void> {
|
||||
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<void> {
|
||||
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 });
|
||||
});
|
||||
Reference in New Issue
Block a user