import { test, expect } from '@playwright/test'; const adminUser = { id: 'user-1', email: 'admin@example.test', role: 'admin', plan: 'internal', displayName: 'Dashboard Admin', emailVerified: true, currentProduct: 'bytelyst-devops', products: [{ productId: 'bytelyst-devops', plan: 'internal', role: 'admin' }], mfaEnabled: false, mfaMethods: [], }; // /hermes mounts , which calls api.getHermesOps() against the // backend's `/api/hermes/ops` endpoint. The backend shells out to systemctl / // git / ps / du on the live VM and is therefore neither available nor // deterministic in CI. We intercept the fetch with a fixture snapshot so the // E2E suite can run against the web stack alone. // Shape mirrors `HermesOpsSnapshot` in `web/src/lib/api.ts` (which mirrors the // backend Zod schema in `backend/src/modules/hermes-ops/types.ts`). Empty // `quickLinks`/`instances` arrays are deliberate — the panel is only required // to render without throwing in CI; the mission-control overview is what the // suite actually asserts on. const hermesOpsSnapshot = { generatedAt: '2026-01-01T00:00:00.000Z', tailscaleIp: '100.0.0.1', emergencyDriveUpload: { name: 'hermes-emergency-drive-upload.timer', active: false, nextRun: null, lastRun: null, }, activeSessions: { active: 0, updatedAt: '2026-01-01T00:00:00.000Z' }, cronJobs: [], recentAlerts: [], quickLinks: [], instances: [], warnings: [], }; test.describe('Hermes Mission Control', () => { test.beforeEach(async ({ page }) => { await page.addInitScript(() => { window.localStorage.setItem('access_token', 'e2e-access-token'); window.localStorage.setItem('refresh_token', 'e2e-refresh-token'); }); await page.route('**/auth/me', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(adminUser) }); }); await page.route('**/api/hermes/ops', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(hermesOpsSnapshot), }); }); }); test('renders the mission control overview and navigates to companion views', async ({ page }) => { await page.goto('/hermes'); await expect(page.getByRole('heading', { name: 'Hermes Mission Control' })).toBeVisible(); await expect(page.getByText('Active Missions')).toBeVisible(); await expect(page.getByText('Founder Attention Queue')).toBeVisible(); await expect(page.getByRole('heading', { name: 'Product Health Snapshot' })).toBeVisible(); await page.getByRole('link', { name: 'Task Ledger' }).click(); await expect(page.getByRole('heading', { name: 'Task Ledger' })).toBeVisible(); await expect(page.getByText('Task table')).toBeVisible(); await page.goto('/hermes/tasks/task-1'); await expect(page.getByRole('heading', { name: 'Hermes learning' })).toBeVisible(); await expect(page.getByText('Timeline')).toBeVisible(); await page.goto('/hermes/products'); await expect(page.getByRole('heading', { name: 'Product Portfolio' })).toBeVisible(); await page.goto('/hermes/history'); await expect(page.getByRole('heading', { name: 'Historical Activity' })).toBeVisible(); await page.goto('/hermes/agents'); await expect(page.getByRole('heading', { name: 'Agent & Tool Observability' })).toBeVisible(); await page.goto('/hermes/settings'); await expect(page.getByRole('heading', { name: 'Settings & Configuration' })).toBeVisible(); }); test('renders the mission control overview at mobile width', async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await page.goto('/hermes'); await expect(page.getByRole('heading', { name: 'Hermes Mission Control' })).toBeVisible(); await expect(page.getByRole('link', { name: 'Task Ledger' })).toBeVisible(); await expect(page.getByRole('link', { name: 'Product Portfolio' })).toBeVisible(); await page.goto('/hermes/tasks/task-1'); await expect(page.getByRole('heading', { name: 'Hermes learning' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Timeline' })).toBeVisible(); }); });