import { expect, test, type Page } from '@playwright/test'; import path from 'node:path'; const observedAt = new Date().toISOString(); async function mockResponsiveData(page: Page): Promise { await page.route('**/api/v1/**', async (route) => { const pathname = new URL(route.request().url()).pathname; const body = pathname === '/api/v1/system/status' ? { version: '1', generatedAt: observedAt, overallState: 'degraded', components: [ { id: 'database', state: 'healthy', reason: 'ok' }, { id: 'prometheus', state: 'unknown', reason: 'source_stale' }, ], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [{ sourceId: 'prometheus', state: 'unknown', reason: 'source_stale', ageSeconds: 900 }], } : pathname === '/api/v1/host' ? { identity: { name: 'responsive-fixture' }, cpu: { totalPercent: 42, perCore: [42, 38] }, memory: { utilizationPercent: 61 }, source: { state: 'healthy', freshness: 'fresh' }, } : pathname === '/api/v1/containers' ? { source: { state: 'healthy', freshness: 'fresh' }, containers: [], total: 0 } : pathname === '/api/v1/pools' ? { source: { state: 'healthy', freshness: 'fresh' }, pools: [], total: 0 } : pathname === '/api/v1/services' ? { capabilityState: 'available', configurationState: 'not_configured', services: [], total: 0 } : pathname === '/api/v1/incidents' ? { items: [] } : {}; await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(body) }); }); } test.beforeEach(async ({ page }) => mockResponsiveData(page)); test('mobile primary navigation and operational reasons remain readable at 390px', async ({ page }, testInfo) => { test.skip(testInfo.project.name !== 'mobile-chromium', 'Mobile evidence uses the supported 390px viewport.'); await page.goto('/'); await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); const labels = page.locator('.mobile-primary-list .nav-link span:last-child'); await expect(labels).toHaveCount(4); const metrics = await labels.evaluateAll((nodes) => nodes.map((node) => { const box = node.getBoundingClientRect(); return { left: box.left, right: box.right, top: box.top, bottom: box.bottom, font: Number.parseFloat(getComputedStyle(node).fontSize) }; })); for (let index = 1; index < metrics.length; index += 1) expect(metrics[index - 1].right).toBeLessThanOrEqual(metrics[index].left); expect(metrics.every((metric) => metric.font >= 12 && metric.bottom - metric.top <= 16)).toBe(true); const reason = page.locator('.action-queue small').first(); await expect(reason).toBeVisible(); await expect(reason).toHaveCSS('white-space', 'normal'); const overflow = await page.evaluate(() => [...document.querySelectorAll('body *')] .map((element) => ({ tag: element.tagName, className: element.className, right: Math.round(element.getBoundingClientRect().right), scrollWidth: element.scrollWidth, clientWidth: element.clientWidth })) .filter((item) => item.right > innerWidth + 1 || item.scrollWidth > item.clientWidth + 1) .slice(0, 12)); expect(overflow).toEqual([]); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(390); if (process.env.PULSE_CAPTURE_VISUALS) await page.screenshot({ path: path.resolve('../../artifacts/evidence/M12-06/responsive-mobile-390.png'), fullPage: true }); }); test('tablet sidebar stays compact and content uses the remaining viewport', async ({ page }, testInfo) => { test.skip(testInfo.project.name !== 'tablet-chromium', 'Tablet evidence uses the supported 1024px viewport.'); await page.goto('/'); await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); const sidebar = await page.locator('.sidebar').boundingBox(); const workspace = await page.locator('.app-workspace').boundingBox(); const queue = await page.locator('.action-queue').boundingBox(); const dataPlane = await page.locator('.signal-path-panel').boundingBox(); expect(sidebar?.width).toBeLessThanOrEqual(208); expect(workspace?.width).toBeGreaterThanOrEqual(816); expect(queue?.width).toBeGreaterThan(300); expect(dataPlane?.width).toBeGreaterThan(500); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(1024); if (process.env.PULSE_CAPTURE_VISUALS) await page.screenshot({ path: path.resolve('../../artifacts/evidence/M12-06/responsive-tablet-1024.png'), fullPage: true }); });