From 8180fddc05e654883f003fa93e993fd3ae54932f Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Mon, 1 Jun 2026 23:21:02 +0000 Subject: [PATCH] test(ui): e2e specs for merged Cluster page and old-route redirects Signed-off-by: Ettore Di Giacinto --- core/http/react-ui/e2e/cluster.spec.js | 37 +++++++++++ core/http/react-ui/e2e/navigation.spec.js | 7 +++ core/http/react-ui/e2e/p2p.spec.js | 75 +++++++++++++++++------ 3 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 core/http/react-ui/e2e/cluster.spec.js diff --git a/core/http/react-ui/e2e/cluster.spec.js b/core/http/react-ui/e2e/cluster.spec.js new file mode 100644 index 000000000..523e0f60d --- /dev/null +++ b/core/http/react-ui/e2e/cluster.spec.js @@ -0,0 +1,37 @@ +import { test, expect } from './coverage-fixtures.js' + +// The Cluster page composes two capability sections: "Distributed (NATS)" (the +// former Nodes page) and "Swarm (p2p)" (the former P2P page). Each section only +// mounts when its mode is enabled — distributed when /api/nodes answers OK, swarm +// when a non-empty p2p network token is present. We mock those probes so the page +// renders against the standalone ui-test-server without NATS / p2p running. + +async function mockDistributedOnly(page) { + await page.route('**/api/nodes', (route) => { + route.fulfill({ status: 200, contentType: 'application/json', body: '[]' }) + }) + await page.route('**/api/nodes/scheduling', (route) => { + route.fulfill({ status: 200, contentType: 'application/json', body: '[]' }) + }) + // Swarm disabled: token probe fails, so the swarm section stays hidden. + await page.route('**/api/p2p/token', (route) => { + route.fulfill({ status: 503, contentType: 'text/plain', body: '' }) + }) +} + +test.describe('Cluster page', () => { + test('shows the page title', async ({ page }) => { + await mockDistributedOnly(page) + await page.goto('/app/cluster') + await expect(page).toHaveURL(/\/app\/cluster$/) + await expect(page.getByRole('heading', { name: /Cluster/i })).toBeVisible() + }) + + test('shows the distributed section when /api/nodes responds', async ({ page }) => { + await mockDistributedOnly(page) + await page.goto('/app/cluster') + await expect(page).toHaveURL(/\/app\/cluster$/) + // The distributed capability section is titled "Distributed (NATS)". + await expect(page.getByText(/Distributed \(NATS\)/i)).toBeVisible() + }) +}) diff --git a/core/http/react-ui/e2e/navigation.spec.js b/core/http/react-ui/e2e/navigation.spec.js index d22dbe0a6..cf2cf0a9a 100644 --- a/core/http/react-ui/e2e/navigation.spec.js +++ b/core/http/react-ui/e2e/navigation.spec.js @@ -23,4 +23,11 @@ test.describe('Navigation', () => { await expect(page).toHaveURL(/\/app\/traces/) await expect(page.getByRole('heading', { name: 'Traces', exact: true })).toBeVisible() }) + + test('old cluster routes redirect to /app/cluster', async ({ page }) => { + await page.goto('/app/p2p') + await expect(page).toHaveURL(/\/app\/cluster$/) + await page.goto('/app/nodes') + await expect(page).toHaveURL(/\/app\/cluster$/) + }) }) diff --git a/core/http/react-ui/e2e/p2p.spec.js b/core/http/react-ui/e2e/p2p.spec.js index 8ea92faf8..70d0e8f39 100644 --- a/core/http/react-ui/e2e/p2p.spec.js +++ b/core/http/react-ui/e2e/p2p.spec.js @@ -1,26 +1,65 @@ import { test, expect } from './coverage-fixtures.js' -// P2P (Swarm) admin page — renders in the no-auth test harness (isAdmin). -test.describe('P2P page', () => { - test.beforeEach(async ({ page }) => { +// The standalone P2P (Swarm) page was merged into the Cluster page: /app/p2p now +// redirects to /app/cluster, and the p2p content lives under the "Swarm (p2p)" +// section. That section only mounts when p2p is enabled (a network token is +// present), so we mock /api/p2p/token to return a non-empty token and assert the +// swarm content renders under the cluster page. +const P2P_TOKEN = 'test-network-token' + +async function mockSwarmEnabled(page) { + await page.route('**/api/p2p/token', (route) => { + route.fulfill({ + status: 200, + contentType: 'text/plain', + body: P2P_TOKEN, + }) + }) + await page.route('**/api/p2p/workers', (route) => { + route.fulfill({ status: 200, contentType: 'application/json', body: '{"nodes":[]}' }) + }) + await page.route('**/api/p2p/federation', (route) => { + route.fulfill({ status: 200, contentType: 'application/json', body: '{"nodes":[]}' }) + }) + await page.route('**/api/p2p/stats', (route) => { + route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + llama_cpp_workers: { online: 0, total: 0 }, + federated: { online: 0, total: 0 }, + mlx_workers: { online: 0, total: 0 }, + }), + }) + }) + // The cluster page also probes /api/nodes for the distributed section; keep it + // failing (distributed disabled) so only the swarm section renders here. + await page.route('**/api/nodes', (route) => { + route.fulfill({ status: 503, contentType: 'application/json', body: '{}' }) + }) +} + +test.describe('P2P (Swarm) section on the Cluster page', () => { + test('the old /app/p2p route lands on the cluster page', async ({ page }) => { + await mockSwarmEnabled(page) + // /app/p2p redirects to /app/cluster. await page.goto('/app/p2p') + await expect(page).toHaveURL(/\/app\/cluster$/) + await expect(page.getByRole('heading', { name: /Cluster/i })).toBeVisible() }) - test('renders the P2P distribution overview and capability cards', async ({ page }) => { - await expect(page).toHaveURL(/\/app\/p2p$/) - await expect(page.getByRole('heading', { name: /P2P Distribution Not Enabled/i })).toBeVisible() - await expect(page.getByRole('heading', { name: 'Instance Federation' })).toBeVisible() - await expect(page.getByRole('heading', { name: 'Model Sharding' })).toBeVisible() - await expect(page.getByRole('heading', { name: 'Resource Sharing' })).toBeVisible() - await expect(page.getByRole('heading', { name: /How to Enable P2P/i })).toBeVisible() - }) + test('renders the Swarm (p2p) section when p2p is enabled', async ({ page }) => { + await mockSwarmEnabled(page) + await page.goto('/app/cluster') + await expect(page).toHaveURL(/\/app\/cluster$/) - test('hardware selector offers build targets and responds to selection', async ({ page }) => { - const cpu = page.getByRole('button').filter({ hasText: /^CPU$/ }) - const cuda = page.getByRole('button').filter({ hasText: /^CUDA 12$/ }) - await expect(cpu).toBeVisible() - await expect(cuda).toBeVisible() - await cuda.click() // selecting a build target must not break the page - await expect(page.getByRole('heading', { name: /How to Enable P2P/i })).toBeVisible() + // The collapsible swarm section is titled "Swarm (p2p)". + await expect(page.getByText(/Swarm \(p2p\)/i)).toBeVisible() + + // The enabled p2p content (Network Token panel + the federation / sharding + // tabs) is rendered inside the swarm section. + await expect(page.getByRole('heading', { name: /Network Token/i })).toBeVisible() + await expect(page.getByText('Federation', { exact: true })).toBeVisible() + await expect(page.getByText('Model Sharding', { exact: true })).toBeVisible() }) })