import { expect, test } from '@playwright/test'; import { loginToDashboard } from './fixtures'; import { ensureLiveStubAgent, isLiveStubReady } from './live-stub'; import { E2E_STUB_AGENT_HOSTNAME, E2E_STUB_AGENT_ID } from './stub-agent'; const SESSION_ID = 'e2e-rs-lanes-sess'; test.describe('Path Tracer E2E', () => { test.beforeAll(async ({ request }) => { await ensureLiveStubAgent(request); }); test.beforeEach(async ({ page }) => { test.skip( !isLiveStubReady(), 'Requires live E2E server (test-suite phase 8 on :18989 or AETHERFORGE_URL)', ); await page.route('**/pathtrace/start', async (route) => { await route.fulfill({ json: { session_id: SESSION_ID, hops: [{ agent_id: E2E_STUB_AGENT_ID, status: 'pending' }], }, }); }); await page.route(`**/pathtrace/${SESSION_ID}/status`, async (route) => { await route.fulfill({ json: { session_id: SESSION_ID, ready: false, hops: [{ agent_id: E2E_STUB_AGENT_ID, status: 'pending' }], spread_routes: [ { target_subnet: '192.168.1.0/24', seed_agent_id: E2E_STUB_AGENT_ID, seed_agent_name: E2E_STUB_AGENT_HOSTNAME, egress_agent_id: E2E_STUB_AGENT_ID, join_lane: 'dns_txt', score: 0.85, erasure_lanes_enabled: true, }, ], }, }); }); await loginToDashboard(page); await page.goto('/pathtracer'); await expect(page.getByRole('heading', { name: /Path Tracer/i })).toBeVisible({ timeout: 10_000, }); }); test('shows RS lanes hint on spread routes when erasure is enabled', async ({ page }) => { const card = page.locator('.pt-agent-card').filter({ hasText: E2E_STUB_AGENT_HOSTNAME }); await expect(card).toBeVisible({ timeout: 15_000 }); await card.click(); const traceBtn = page.locator('.pt-actions').getByRole('button', { name: /TRACE/i }); await expect(traceBtn).toBeEnabled({ timeout: 10_000 }); await traceBtn.click(); await expect(page.getByText('Spread Routes')).toBeVisible({ timeout: 15_000 }); await expect(page.getByText(/192\.168\.1\.0\/24/)).toBeVisible(); await expect(page.getByText(/dns_txt/)).toBeVisible(); await expect(page.getByText(/RS lanes/)).toBeVisible(); }); test('onion timeline fork button and mermaid panel', async ({ page }) => { await page.route(`**/pathtrace/${SESSION_ID}/status`, async (route) => { await route.fulfill({ json: { session_id: SESSION_ID, ready: true, hops: [{ agent_id: E2E_STUB_AGENT_ID, status: 'ready', agent_name: E2E_STUB_AGENT_HOSTNAME }], }, }); }); await page.route(`**/pathtrace/${SESSION_ID}/qr`, async (route) => { await route.fulfill({ json: { config: '[Interface]', qr_png_b64: 'iVBORw0KGgo=' }, }); }); await page.route('**/pathtrace/fork', async (route) => { await route.fulfill({ json: { ok: true, session_id: SESSION_ID, branches: [ { id: 'ghost-e2e-1', fork_hop_index: 0, persona: 'aggressive', status: 'running', is_ghost: true, active_tier: 'smb', }, ], mermaid: 'graph TD\n fork --> ghost_e2e', }, }); }); const card = page.locator('.pt-agent-card').filter({ hasText: E2E_STUB_AGENT_HOSTNAME }); await card.click(); await page.locator('.pt-actions').getByRole('button', { name: /TRACE/i }).click(); const forkBtn = page.locator('.pt-fork-btn').first(); await expect(forkBtn).toBeVisible({ timeout: 20_000 }); await page.evaluate(() => { document.querySelectorAll('.pt-modal-backdrop').forEach((el) => el.remove()); }); const forkResponse = page.waitForResponse( (res) => res.url().includes('/pathtrace/fork') && res.request().method() === 'POST', ); await forkBtn.click(); await forkResponse; await expect(page.getByTestId('pt-timeline-tree')).toBeVisible({ timeout: 15_000 }); await expect(page.getByText(/aggressive/i)).toBeVisible(); await page.getByText('Mermaid branch graph').click(); await expect(page.getByTestId('pt-mermaid-src')).toContainText('graph TD'); }); });