Public source validation / validate (push) Failing after 3m8s
130 lines
7.3 KiB
TypeScript
130 lines
7.3 KiB
TypeScript
import AxeBuilder from '@axe-core/playwright';
|
|
import { expect, test, type Page } from '@playwright/test';
|
|
import path from 'node:path';
|
|
|
|
const generatedAt = new Date().toISOString();
|
|
|
|
async function mockAPI(page: Page): Promise<void> {
|
|
await page.route('**/api/v1/**', async (route) => {
|
|
const path = new URL(route.request().url()).pathname;
|
|
if (path === '/api/v1/system/status') {
|
|
await route.fulfill({
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
version: '1', generatedAt, overallState: 'degraded',
|
|
components: [{ id: 'database', state: 'healthy', reason: 'ok' }, { id: 'prometheus', state: 'unknown', reason: 'source_stale' }],
|
|
backup: { state: 'disabled', reason: 'not_configured' },
|
|
sourceLag: [{ sourceId: 'reverse-proxy', state: 'unknown', reason: 'source_unavailable', ageSeconds: 180 }],
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
if (path === '/api/v1/dashboards') {
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ items: [] }) });
|
|
return;
|
|
}
|
|
if (path === '/api/v1/host') {
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({
|
|
identity: { name: 'mobile-fixture' },
|
|
cpu: { totalPercent: 42, perCore: Array.from({ length: 16 }, (_, index) => index + 1) },
|
|
memory: { utilizationPercent: 61 }, source: { state: 'healthy', freshness: 'fresh' },
|
|
}) });
|
|
return;
|
|
}
|
|
if (path === '/api/v1/containers') {
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ source: { state: 'healthy', freshness: 'fresh' }, containers: [{ id: 'proxy', state: 'running', health: 'healthy' }], total: 1 }) });
|
|
return;
|
|
}
|
|
if (path === '/api/v1/pools') {
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ source: { state: 'healthy', freshness: 'fresh' }, pools: [{ id: 'cache', name: 'Cache', state: 'healthy', utilizationPercent: 63 }], total: 1 }) });
|
|
return;
|
|
}
|
|
if (path === '/api/v1/services') {
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ capabilityState: 'available', configurationState: 'configured', services: [{ id: 'proxy', name: 'Proxy', state: 'up' }], total: 1 }) });
|
|
return;
|
|
}
|
|
if (path === '/api/v1/incidents') {
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ items: [{ id: 'incident-1', title: 'Bronvertraging', severity: 'warning', startedAt: generatedAt }] }) });
|
|
return;
|
|
}
|
|
await route.fulfill({ status: 404, contentType: 'application/problem+json', body: JSON.stringify({ code: 'NOT_FOUND' }) });
|
|
});
|
|
}
|
|
|
|
async function expectNoCriticalA11yViolations(page: Page): Promise<void> {
|
|
const results = await new AxeBuilder({ page }).analyze();
|
|
const severe = results.violations.filter((violation) => violation.impact === 'critical' || violation.impact === 'serious');
|
|
expect(severe, severe.map((violation) => `${violation.id}: ${violation.help}`).join('\n')).toEqual([]);
|
|
}
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await mockAPI(page);
|
|
});
|
|
|
|
test('overview exposes degraded/unknown state and passes axe', async ({ page }, testInfo) => {
|
|
test.skip(testInfo.project.name === 'wallboard-chromium', 'Wallboard has a dedicated route test.');
|
|
await page.goto('/');
|
|
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
|
await expect(page.getByText(/Onbekend|Verminderd/).first()).toBeVisible();
|
|
if (testInfo.project.name === 'mobile-chromium') {
|
|
await expect(page.locator('.desktop-navigation')).toBeHidden();
|
|
await expect(page.locator('.mobile-navigation')).toBeVisible();
|
|
await expect(page.locator('.mobile-navigation')).toHaveCSS('position', 'fixed');
|
|
await expect(page.locator('.mobile-primary-list .nav-link')).toHaveCount(4);
|
|
const mobileTargets = await page.locator('.mobile-primary-list .nav-link').evaluateAll((items) => items.map((item) => item.getBoundingClientRect().height));
|
|
expect(mobileTargets.every((height) => height >= 44)).toBe(true);
|
|
await expect(page.locator('.signal-path-stage')).toHaveCount(6);
|
|
await expect(page.locator('.signal-path-inspector')).toBeVisible();
|
|
const incidentBox = await page.locator('.incident-queue').boundingBox();
|
|
const signalBox = await page.locator('.signal-path-panel').boundingBox();
|
|
expect(incidentBox?.y).toBeLessThan(signalBox?.y ?? 0);
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
|
} else {
|
|
await expect(page.locator('.desktop-navigation')).toBeVisible();
|
|
const rail = await page.locator('.sidebar').boundingBox();
|
|
const commandHeader = await page.locator('.context-bar').boundingBox();
|
|
expect(rail?.width).toBeLessThanOrEqual(72);
|
|
expect(commandHeader?.height).toBe(56);
|
|
await expect(page.locator('.overview-kpi')).toHaveCount(4);
|
|
await expect(page.locator('.source-health-strip')).toHaveAttribute('tabindex', '0');
|
|
await expect(page.locator('.nav-group')).toHaveCount(6);
|
|
await expect(page.locator('.nav-group').first()).toHaveAttribute('open', '');
|
|
await expect(page.locator('.nav-group').filter({ hasText: 'Infrastructuur' })).not.toHaveAttribute('open', '');
|
|
}
|
|
await page.keyboard.press('Tab');
|
|
await expect(page.locator(':focus')).toBeVisible();
|
|
await expectNoCriticalA11yViolations(page);
|
|
if (process.env.PULSE_E2E_REAL_BASE_URL || process.env.PULSE_CAPTURE_VISUALS) {
|
|
const evidenceDirectory = process.env.PULSE_CAPTURE_VISUALS ? 'M14-02' : 'M11-10';
|
|
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur());
|
|
await page.screenshot({ path: path.resolve('../../artifacts/evidence', evidenceDirectory, `overview-${testInfo.project.name}.png`), fullPage: true });
|
|
}
|
|
});
|
|
|
|
test('grouped navigation reaches infrastructure in two actions', async ({ page }, testInfo) => {
|
|
test.skip(testInfo.project.name === 'wallboard-chromium', 'Wallboard heeft geen productnavigatie.');
|
|
await page.goto('/');
|
|
if (testInfo.project.name === 'mobile-chromium') {
|
|
await page.getByText('Meer', { exact: true }).click();
|
|
await page.getByRole('link', { name: /Disks/ }).click();
|
|
} else {
|
|
await page.locator('.nav-group').filter({ hasText: 'Infrastructuur' }).locator('summary').click();
|
|
await page.getByRole('link', { name: /Disks/ }).click();
|
|
}
|
|
await expect(page).toHaveURL(/\/disks$/);
|
|
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true);
|
|
});
|
|
|
|
test('wallboard remains read-only, bounded and accessible', async ({ page }, testInfo) => {
|
|
test.skip(testInfo.project.name !== 'wallboard-chromium', 'Wallboard is verified at 1920x1080.');
|
|
await page.goto('/wallboard');
|
|
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
|
await expect(page.getByText(/Geen dashboards|Wallboard/).first()).toBeVisible();
|
|
await expect(page.locator('.sidebar')).toHaveCount(0);
|
|
await expect(page.getByRole('button', { name: /Bewerken|Exporteren/ })).toHaveCount(0);
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true);
|
|
expect(await page.evaluate(() => document.documentElement.scrollHeight <= window.innerHeight + 1)).toBe(true);
|
|
await expectNoCriticalA11yViolations(page);
|
|
});
|