From 005100432b1a1f11a3bf5c70371c86d8346bc45b Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 8 Jul 2026 11:15:13 +0100 Subject: [PATCH] Retire e2e specs that asserted the removed unified-route contracts The standalone /infrastructure, /workloads, /storage, and /recovery routes were deliberately retired with the platform-first navigation (abb6f86ae); these specs existed to pin those routes' filter, handoff, and scoped-routing contracts, so there is no platform-first surface for them to assert. The subjects that do survive the rework are covered elsewhere: workload drawers and filters by the platform-page and embedded-workloads specs, and refresh resilience by the workloads stability spec being ported to the Proxmox page. --- ...-truenas-workloads-platform-filter.spec.ts | 146 -------- .../24-truenas-storage-source-filter.spec.ts | 210 ----------- .../25-truenas-recovery-route-filter.spec.ts | 204 ---------- ...ruenas-workloads-resource-deeplink.spec.ts | 128 ------- ...ruenas-infrastructure-route-filter.spec.ts | 118 ------ .../61-infrastructure-column-layout.spec.ts | 164 -------- ...cture-workloads-resource-coherence.spec.ts | 352 ------------------ 7 files changed, 1322 deletions(-) delete mode 100644 tests/integration/tests/22-truenas-workloads-platform-filter.spec.ts delete mode 100644 tests/integration/tests/24-truenas-storage-source-filter.spec.ts delete mode 100644 tests/integration/tests/25-truenas-recovery-route-filter.spec.ts delete mode 100644 tests/integration/tests/32-truenas-workloads-resource-deeplink.spec.ts delete mode 100644 tests/integration/tests/33-truenas-infrastructure-route-filter.spec.ts delete mode 100644 tests/integration/tests/61-infrastructure-column-layout.spec.ts delete mode 100644 tests/integration/tests/74-infrastructure-workloads-resource-coherence.spec.ts diff --git a/tests/integration/tests/22-truenas-workloads-platform-filter.spec.ts b/tests/integration/tests/22-truenas-workloads-platform-filter.spec.ts deleted file mode 100644 index 7f34940aa..000000000 --- a/tests/integration/tests/22-truenas-workloads-platform-filter.spec.ts +++ /dev/null @@ -1,146 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { test as base, expect } from '@playwright/test'; -import { createAuthenticatedStorageState } from './helpers'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -type WorkerFixtures = { - authStorageStatePath: string; -}; - -const SCREENSHOT_PATH = '/tmp/truenas-workloads-platform-filter.png'; - -const test = base.extend<{}, WorkerFixtures>({ - storageState: async ({ authStorageStatePath }, use) => { - await use(authStorageStatePath); - }, - authStorageStatePath: [async ({ browser }, use, workerInfo) => { - const storageStatePath = path.resolve( - __dirname, - '..', - '..', - 'tmp', - 'playwright-auth', - `truenas-workloads-platform-filter-${workerInfo.project.name}.json`, - ); - fs.mkdirSync(path.dirname(storageStatePath), { recursive: true }); - await createAuthenticatedStorageState(browser, storageStatePath); - try { - await use(storageStatePath); - } finally { - fs.rmSync(storageStatePath, { force: true }); - } - }, { scope: 'worker' }], -}); - -test.describe('TrueNAS workloads platform filter', () => { - test.setTimeout(180_000); - - test('keeps canonical platform scoping on the workloads route', async ({ page }) => { - await page.route('**/api/resources**', async (route) => { - const requestUrl = new URL(route.request().url()); - if ( - requestUrl.pathname !== '/api/resources' || - requestUrl.searchParams.get('type') !== 'vm,system-container,app-container,pod' - ) { - await route.continue(); - return; - } - - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [ - { - id: 'app-container:truenas-main:nextcloud', - type: 'app-container', - name: 'nextcloud', - status: 'running', - lastSeen: '2026-03-29T21:00:00Z', - node: 'truenas-main', - sources: ['truenas'], - parentName: 'truenas-main', - metrics: { - cpu: { value: 12, percent: 12 }, - memory: { total: 8 * 1024 * 1024 * 1024, used: 2 * 1024 * 1024 * 1024 }, - disk: { total: 100 * 1024 * 1024 * 1024, used: 40 * 1024 * 1024 * 1024 }, - netIn: { value: 2048 }, - netOut: { value: 1024 }, - diskRead: { value: 512 }, - diskWrite: { value: 256 }, - }, - docker: { - containerId: 'ix-nextcloud', - hostname: 'truenas-main', - imageName: 'nextcloud:29', - runtime: 'docker', - hostSourceId: 'truenas-main', - updateStatus: { - updateAvailable: true, - currentDigest: 'sha256:1111111111111111111111111111111111111111111111111111111111111111', - latestDigest: 'sha256:2222222222222222222222222222222222222222222222222222222222222222', - lastChecked: 1743362400000, - }, - }, - }, - { - id: 'app-container:docker-main:grafana', - type: 'app-container', - name: 'grafana', - status: 'running', - lastSeen: '2026-03-29T21:00:00Z', - node: 'docker-main', - sources: ['docker'], - parentName: 'docker-main', - metrics: { - cpu: { value: 8, percent: 8 }, - memory: { total: 4 * 1024 * 1024 * 1024, used: 1 * 1024 * 1024 * 1024 }, - disk: { total: 40 * 1024 * 1024 * 1024, used: 10 * 1024 * 1024 * 1024 }, - netIn: { value: 1024 }, - netOut: { value: 512 }, - diskRead: { value: 128 }, - diskWrite: { value: 64 }, - }, - docker: { - containerId: 'docker-grafana', - hostname: 'docker-main', - imageName: 'grafana:11', - runtime: 'docker', - hostSourceId: 'docker-main', - }, - }, - ], - meta: { - page: 1, - limit: 200, - total: 2, - totalPages: 1, - }, - }), - }); - }); - - await page.goto('/workloads?type=app-container&platform=truenas&agent=truenas-main', { - waitUntil: 'domcontentloaded', - }); - - await page.waitForURL(/\/workloads\?type=app-container&platform=truenas&agent=.*truenas-main/); - await expect(page.locator('#dashboard-type-filter')).toHaveValue('app-container'); - await expect(page.locator('#workloads-platform-filter')).toHaveValue('truenas'); - await expect(page.locator('#workloads-node-filter')).toHaveValue(/truenas-main/); - - const workloadTable = page.locator('table').first(); - await expect(workloadTable).toContainText('nextcloud'); - await expect(workloadTable).not.toContainText('grafana'); - await expect( - page.locator( - 'tr[data-guest-id="app-container:truenas-main:nextcloud"] [data-prevent-toggle]', - ), - ).toHaveCount(0); - - await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true }); - }); -}); diff --git a/tests/integration/tests/24-truenas-storage-source-filter.spec.ts b/tests/integration/tests/24-truenas-storage-source-filter.spec.ts deleted file mode 100644 index 690396887..000000000 --- a/tests/integration/tests/24-truenas-storage-source-filter.spec.ts +++ /dev/null @@ -1,210 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { test as base, expect } from '@playwright/test'; -import { createAuthenticatedStorageState } from './helpers'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -type WorkerFixtures = { - authStorageStatePath: string; -}; - -const SCREENSHOT_PATH = '/tmp/truenas-storage-source-filter.png'; - -const test = base.extend<{}, WorkerFixtures>({ - storageState: async ({ authStorageStatePath }, use) => { - await use(authStorageStatePath); - }, - authStorageStatePath: [async ({ browser }, use, workerInfo) => { - const storageStatePath = path.resolve( - __dirname, - '..', - '..', - 'tmp', - 'playwright-auth', - `truenas-storage-source-filter-${workerInfo.project.name}.json`, - ); - fs.mkdirSync(path.dirname(storageStatePath), { recursive: true }); - await createAuthenticatedStorageState(browser, storageStatePath); - try { - await use(storageStatePath); - } finally { - fs.rmSync(storageStatePath, { force: true }); - } - }, { scope: 'worker' }], -}); - -test.describe('TrueNAS storage source filter', () => { - test.setTimeout(180_000); - - test('restores the canonical TrueNAS storage handoff on the storage route', async ({ page }) => { - await page.route('**/api/resources**', async (route) => { - const requestUrl = new URL(route.request().url()); - if (requestUrl.pathname !== '/api/resources') { - await route.continue(); - return; - } - - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [ - { - id: 'truenas-main', - type: 'agent', - name: 'truenas-main', - displayName: 'TrueNAS Main', - platformId: 'truenas-main', - platformType: 'truenas', - sourceType: 'hybrid', - sources: ['agent', 'truenas'], - status: 'online', - lastSeen: '2026-03-29T22:00:00Z', - canonicalIdentity: { - displayName: 'TrueNAS Main', - hostname: 'truenas-main', - platformId: 'truenas-main', - }, - agent: { - hostname: 'truenas-main', - platform: 'TrueNAS SCALE', - uptimeSeconds: 86400, - }, - platformData: { - sources: ['agent', 'truenas'], - }, - }, - { - id: 'pve-main', - type: 'agent', - name: 'pve-main', - displayName: 'PVE Main', - platformId: 'pve-main', - platformType: 'proxmox-pve', - sourceType: 'hybrid', - sources: ['agent', 'proxmox-pve'], - status: 'online', - lastSeen: '2026-03-29T22:00:00Z', - canonicalIdentity: { - displayName: 'PVE Main', - hostname: 'pve-main', - platformId: 'pve-main', - }, - agent: { - hostname: 'pve-main', - platform: 'Debian', - uptimeSeconds: 86400, - }, - platformData: { - sources: ['agent', 'proxmox-pve'], - }, - }, - { - id: 'storage-truenas-display', - type: 'storage', - name: 'tank', - displayName: 'tank', - parentId: 'truenas-main', - parentName: 'truenas-main', - platformId: 'truenas-1', - platformType: 'truenas', - sourceType: 'api', - sources: ['truenas'], - status: 'online', - lastSeen: '2026-03-29T22:00:00Z', - canonicalIdentity: { - displayName: 'tank', - platformId: 'truenas-1', - }, - metrics: { - disk: { - total: 2_000 * 1024 * 1024 * 1024, - used: 840 * 1024 * 1024 * 1024, - percent: 42, - }, - }, - storage: { - platform: 'truenas', - type: 'zfs-pool', - topology: 'pool', - isZfs: true, - }, - platformData: { - sources: ['truenas'], - }, - }, - { - id: 'storage-pve-local', - type: 'storage', - name: 'local-zfs', - displayName: 'local-zfs', - parentId: 'pve-main', - parentName: 'pve-main', - platformId: 'cluster-a', - platformType: 'proxmox-pve', - sourceType: 'api', - sources: ['proxmox-pve'], - status: 'online', - lastSeen: '2026-03-29T22:00:00Z', - canonicalIdentity: { - displayName: 'local-zfs', - platformId: 'cluster-a', - }, - metrics: { - disk: { - total: 1_000 * 1024 * 1024 * 1024, - used: 400 * 1024 * 1024 * 1024, - percent: 40, - }, - }, - storage: { - platform: 'proxmox-pve', - type: 'zfspool', - topology: 'pool', - isZfs: true, - }, - platformData: { - node: 'pve-main', - sources: ['proxmox-pve'], - }, - }, - ], - meta: { - page: 1, - limit: 200, - total: 4, - totalPages: 1, - }, - }), - }); - }); - - await page.goto('/storage?source=truenas&node=truenas-main', { - waitUntil: 'domcontentloaded', - }); - - await expect(page).toHaveURL(/\/storage\?source=truenas&node=truenas-main/); - await expect(page.locator('#storage-source-filter')).toHaveValue('truenas'); - await expect(page.getByLabel('Node')).toHaveValue('truenas-main'); - - const sourceOptions = await page.locator('#storage-source-filter option').evaluateAll((options) => - options.map((option) => ({ - value: option.getAttribute('value'), - label: option.textContent?.trim(), - })), - ); - expect(sourceOptions).toEqual([ - { value: 'all', label: 'All Sources' }, - { value: 'proxmox-pve', label: 'PVE' }, - { value: 'truenas', label: 'TrueNAS' }, - ]); - - const storageTable = page.locator('table').first(); - await expect(storageTable).toContainText('tank'); - await expect(storageTable).not.toContainText('local-zfs'); - - await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true }); - }); -}); diff --git a/tests/integration/tests/25-truenas-recovery-route-filter.spec.ts b/tests/integration/tests/25-truenas-recovery-route-filter.spec.ts deleted file mode 100644 index 739996d87..000000000 --- a/tests/integration/tests/25-truenas-recovery-route-filter.spec.ts +++ /dev/null @@ -1,204 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { expect, test as base } from '@playwright/test'; - -import { createAuthenticatedStorageState } from './helpers'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const SCREENSHOT_PATH = '/tmp/truenas-recovery-route-filter.png'; - -type WorkerFixtures = { - authStorageStatePath: string; -}; - -const test = base.extend<{}, WorkerFixtures>({ - storageState: async ({ authStorageStatePath }, use) => { - await use(authStorageStatePath); - }, - authStorageStatePath: [async ({ browser }, use, workerInfo) => { - const storageStatePath = path.resolve( - __dirname, - '..', - '..', - 'tmp', - 'playwright-auth', - `truenas-recovery-route-filter-${workerInfo.project.name}.json`, - ); - fs.mkdirSync(path.dirname(storageStatePath), { recursive: true }); - await createAuthenticatedStorageState(browser, storageStatePath); - try { - await use(storageStatePath); - } finally { - fs.rmSync(storageStatePath, { force: true }); - } - }, { scope: 'worker' }], -}); - -test.describe('TrueNAS recovery route filters', () => { - test.setTimeout(180_000); - - test('keeps route-owned platform and node filters visible while recovery data warms', async ({ - page, - }) => { - const recoveryRequests: string[] = []; - let releaseRecoveryResponses: (() => void) | null = null; - const recoveryResponseGate = new Promise((resolve) => { - releaseRecoveryResponses = resolve; - }); - - await page.route('**/api/resources**', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [ - { - id: 'truenas-main', - type: 'agent', - name: 'tower', - displayName: 'TrueNAS Tower', - platformId: 'truenas-main', - platformType: 'truenas', - sourceType: 'api', - status: 'online', - lastSeen: '2026-03-29T22:00:00Z', - platformData: { - sources: ['truenas'], - }, - }, - ], - meta: { page: 1, limit: 200, total: 1, totalPages: 1 }, - }), - }); - }); - - await page.route('**/api/recovery/rollups*', async (route) => { - recoveryRequests.push(route.request().url()); - await recoveryResponseGate; - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [ - { - rollupId: 'ext:truenas-1', - itemRef: { type: 'truenas-dataset', name: 'tank/apps', id: 'tank/apps' }, - display: { itemType: 'dataset', subjectLabel: 'tank/apps', nodeHostLabel: 'tower' }, - lastAttemptAt: '2026-03-29T09:00:00.000Z', - lastSuccessAt: '2026-03-29T09:00:00.000Z', - lastOutcome: 'success', - platforms: ['truenas'], - }, - ], - meta: { page: 1, limit: 500, total: 1, totalPages: 1 }, - }), - }); - }); - - await page.route('**/api/recovery/points*', async (route) => { - recoveryRequests.push(route.request().url()); - await recoveryResponseGate; - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [ - { - id: 'truenas-point-1', - platform: 'truenas', - kind: 'snapshot', - mode: 'snapshot', - outcome: 'success', - completedAt: '2026-03-29T09:00:00.000Z', - node: 'tower', - itemRef: { - type: 'truenas-dataset', - name: 'tank/apps', - id: 'tank/apps', - }, - display: { - itemType: 'dataset', - subjectType: 'truenas-dataset', - subjectLabel: 'tank/apps', - nodeHostLabel: 'tower', - }, - }, - ], - meta: { page: 1, limit: 200, total: 1, totalPages: 1 }, - }), - }); - }); - - await page.route('**/api/recovery/facets*', async (route) => { - recoveryRequests.push(route.request().url()); - await recoveryResponseGate; - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: { - clusters: [], - nodesAgents: ['tower'], - namespaces: [], - itemTypes: ['dataset'], - hasSize: false, - hasVerification: false, - hasEntityId: false, - }, - }), - }); - }); - - await page.route('**/api/recovery/series*', async (route) => { - recoveryRequests.push(route.request().url()); - await recoveryResponseGate; - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [{ day: '2026-03-29', total: 1, snapshot: 1, local: 0, remote: 0 }], - }), - }); - }); - - await page.goto('/recovery?view=events&platform=truenas&node=tower', { - waitUntil: 'domcontentloaded', - }); - - await expect(page).toHaveURL(/\/recovery\?view=events&platform=truenas&node=tower/); - await expect.poll(() => recoveryRequests.length).toBeGreaterThan(0); - - releaseRecoveryResponses?.(); - - await expect(page.getByTestId('recovery-page')).toBeVisible(); - await expect(page.getByLabel('Platform')).toHaveValue('truenas'); - await expect(page.getByText('Host / Agent')).toBeVisible(); - await expect(page.getByText('tower')).toBeVisible(); - await expect(page.getByText('tank/apps')).toBeVisible(); - - expect( - recoveryRequests.some( - (url) => url.includes('/api/recovery/rollups') && url.includes('platform=truenas'), - ), - ).toBe(true); - expect( - recoveryRequests.some( - (url) => - url.includes('/api/recovery/points') && - url.includes('platform=truenas') && - url.includes('node=tower'), - ), - ).toBe(true); - expect( - recoveryRequests.some( - (url) => - url.includes('/api/recovery/facets') && - url.includes('platform=truenas') && - url.includes('node=tower'), - ), - ).toBe(true); - - await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true }); - }); -}); diff --git a/tests/integration/tests/32-truenas-workloads-resource-deeplink.spec.ts b/tests/integration/tests/32-truenas-workloads-resource-deeplink.spec.ts deleted file mode 100644 index 8f9d76de6..000000000 --- a/tests/integration/tests/32-truenas-workloads-resource-deeplink.spec.ts +++ /dev/null @@ -1,128 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { test as base, expect } from '@playwright/test'; -import { createAuthenticatedStorageState } from './helpers'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - -type WorkerFixtures = { - authStorageStatePath: string; -}; - -const SCREENSHOT_PATH = '/tmp/truenas-workloads-resource-deeplink.png'; - -const test = base.extend<{}, WorkerFixtures>({ - storageState: async ({ authStorageStatePath }, use) => { - await use(authStorageStatePath); - }, - authStorageStatePath: [async ({ browser }, use, workerInfo) => { - const storageStatePath = path.resolve( - __dirname, - '..', - '..', - 'tmp', - 'playwright-auth', - `truenas-workloads-resource-deeplink-${workerInfo.project.name}.json`, - ); - fs.mkdirSync(path.dirname(storageStatePath), { recursive: true }); - await createAuthenticatedStorageState(browser, storageStatePath); - try { - await use(storageStatePath); - } finally { - fs.rmSync(storageStatePath, { force: true }); - } - }, { scope: 'worker' }], -}); - -test.describe('TrueNAS workloads resource deep links', () => { - test.setTimeout(180_000); - - test('opens the canonical workload drawer without inventing a node scope', async ({ page }) => { - await page.route('**/api/resources**', async (route) => { - const requestUrl = new URL(route.request().url()); - if ( - requestUrl.pathname !== '/api/resources' || - requestUrl.searchParams.get('type') !== 'vm,system-container,app-container,pod' - ) { - await route.continue(); - return; - } - - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [ - { - id: 'app-container:truenas-main:nextcloud', - type: 'app-container', - name: 'nextcloud', - status: 'running', - lastSeen: '2026-03-29T21:00:00Z', - node: 'truenas-main', - instance: 'truenas-main', - sources: ['truenas'], - parentName: 'truenas-main', - metrics: { - cpu: { value: 12, percent: 12 }, - memory: { total: 8 * 1024 * 1024 * 1024, used: 2 * 1024 * 1024 * 1024 }, - disk: { total: 100 * 1024 * 1024 * 1024, used: 40 * 1024 * 1024 * 1024 }, - netIn: { value: 2048 }, - netOut: { value: 1024 }, - diskRead: { value: 512 }, - diskWrite: { value: 256 }, - }, - docker: { - containerId: 'ix-nextcloud', - hostname: 'truenas-main', - imageName: 'nextcloud:29', - runtime: 'docker', - hostSourceId: 'truenas-main', - }, - }, - { - id: 'cluster-a:pve1:101', - type: 'vm', - name: 'vm-101', - status: 'running', - lastSeen: '2026-03-29T21:00:00Z', - node: 'pve1', - instance: 'cluster-a', - vmid: 101, - sources: ['proxmox'], - }, - ], - meta: { - page: 1, - limit: 200, - total: 2, - totalPages: 1, - }, - }), - }); - }); - - await page.goto( - '/workloads?type=app-container&platform=truenas&agent=truenas-main&resource=app-container%3Atruenas-main%3Anextcloud', - { - waitUntil: 'domcontentloaded', - }, - ); - - await page.waitForURL( - /\/workloads\?type=app-container&platform=truenas&agent=truenas-main&resource=app-container%3Atruenas-main%3Anextcloud/, - ); - await expect(page.locator('#dashboard-type-filter')).toHaveValue('app-container'); - await expect(page.locator('#workloads-platform-filter')).toHaveValue('truenas'); - await expect(page.locator('#workloads-node-filter')).toHaveValue(/truenas-main/); - await expect( - page.locator('tr[data-guest-id="app-container:truenas-main:nextcloud"]'), - ).toContainText('nextcloud'); - await expect(page.getByText('Open related infrastructure', { exact: true })).toBeVisible(); - await expect(page.getByRole('button', { name: 'Overview' })).toBeVisible(); - await expect(page.getByRole('button', { name: 'Discovery' })).toHaveCount(0); - - await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true }); - }); -}); diff --git a/tests/integration/tests/33-truenas-infrastructure-route-filter.spec.ts b/tests/integration/tests/33-truenas-infrastructure-route-filter.spec.ts deleted file mode 100644 index 8e959bb95..000000000 --- a/tests/integration/tests/33-truenas-infrastructure-route-filter.spec.ts +++ /dev/null @@ -1,118 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { expect, test as base } from '@playwright/test'; - -import { createAuthenticatedStorageState } from './helpers'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const SCREENSHOT_PATH = '/tmp/truenas-infrastructure-route-filter.png'; - -type WorkerFixtures = { - authStorageStatePath: string; -}; - -const test = base.extend<{}, WorkerFixtures>({ - storageState: async ({ authStorageStatePath }, use) => { - await use(authStorageStatePath); - }, - authStorageStatePath: [async ({ browser }, use, workerInfo) => { - const storageStatePath = path.resolve( - __dirname, - '..', - '..', - 'tmp', - 'playwright-auth', - `truenas-infrastructure-route-filter-${workerInfo.project.name}.json`, - ); - fs.mkdirSync(path.dirname(storageStatePath), { recursive: true }); - await createAuthenticatedStorageState(browser, storageStatePath); - try { - await use(storageStatePath); - } finally { - fs.rmSync(storageStatePath, { force: true }); - } - }, { scope: 'worker' }], -}); - -test.describe('TrueNAS infrastructure route filter', () => { - test.setTimeout(180_000); - - test('keeps a route-owned TrueNAS source selection visible when current infrastructure data has no TrueNAS resources', async ({ - page, - }) => { - await page.route('**/api/resources**', async (route) => { - const requestUrl = new URL(route.request().url()); - if (requestUrl.pathname !== '/api/resources') { - await route.continue(); - return; - } - - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ - data: [ - { - id: 'pve-main', - type: 'agent', - name: 'pve-main', - displayName: 'PVE Main', - platformId: 'pve-main', - platformType: 'proxmox-pve', - sourceType: 'hybrid', - sources: ['agent', 'proxmox-pve'], - status: 'online', - lastSeen: '2026-03-29T22:00:00Z', - canonicalIdentity: { - displayName: 'PVE Main', - hostname: 'pve-main', - platformId: 'pve-main', - }, - agent: { - hostname: 'pve-main', - platform: 'Debian', - uptimeSeconds: 86400, - }, - platformData: { - sources: ['agent', 'proxmox-pve'], - }, - }, - ], - meta: { - page: 1, - limit: 200, - total: 1, - totalPages: 1, - }, - }), - }); - }); - - await page.goto('/infrastructure?source=truenas', { - waitUntil: 'domcontentloaded', - }); - - await expect(page).toHaveURL(/\/infrastructure\?source=truenas/); - await expect(page.getByTestId('infrastructure-page')).toBeVisible(); - await expect(page.locator('#infra-source-filter')).toHaveValue('truenas'); - - const sourceOptions = await page.locator('#infra-source-filter option').evaluateAll((options) => - options.map((option) => ({ - value: option.getAttribute('value'), - label: option.textContent?.trim(), - })), - ); - expect(sourceOptions).toEqual([ - { value: '', label: 'All' }, - { value: 'proxmox-pve', label: 'PVE' }, - { value: 'agent', label: 'Agent' }, - { value: 'truenas', label: 'TrueNAS' }, - ]); - - await expect(page.getByText('No resources match filters')).toBeVisible(); - await expect(page.getByText('Try adjusting the search, source, or status filters.')).toBeVisible(); - - await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true }); - }); -}); diff --git a/tests/integration/tests/61-infrastructure-column-layout.spec.ts b/tests/integration/tests/61-infrastructure-column-layout.spec.ts deleted file mode 100644 index 55ba2c72f..000000000 --- a/tests/integration/tests/61-infrastructure-column-layout.spec.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { expect, test as base, type Page } from '@playwright/test'; - -import { getMockMode, setMockMode } from './helpers'; - -type InfrastructureHeaderMetric = { - text: string; - width: number; - attrWidth: number | null; -}; - -type InfrastructureTableMetric = { - kind: 'host' | 'pbs' | 'pmg' | 'unknown'; - wrapperClientWidth: number; - wrapperScrollWidth: number; - tableScrollWidth: number; - headers: InfrastructureHeaderMetric[]; -}; - -type InfrastructureColumnLayout = { - tables: InfrastructureTableMetric[]; -}; - -let mockModeWasEnabled: boolean | null = null; - -const HTTP_CREDENTIALS = { - username: 'admin', - password: 'adminadminadmin', -}; - -const test = base; - -test.use({ - httpCredentials: HTTP_CREDENTIALS, -}); - -async function ensureMockModeEnabled(page: Page): Promise { - const state = await getMockMode(page); - if (mockModeWasEnabled === null) { - mockModeWasEnabled = state.enabled; - } - if (!state.enabled) { - await setMockMode(page, true); - } -} - -async function readInfrastructureColumnLayout(page: Page): Promise { - return page.evaluate(() => { - const tables = Array.from( - document.querySelectorAll('[data-testid="infrastructure-table-surface"] table'), - ); - - const classifyTable = (headers: string[]): InfrastructureTableMetric['kind'] => { - if (headers.includes('CPU') && headers.includes('Net I/O')) return 'host'; - if (headers.includes('Datastores') && headers.includes('Jobs')) return 'pbs'; - if (headers.includes('Queue') && headers.includes('Deferred')) return 'pmg'; - return 'unknown'; - }; - - return { - tables: tables.map((table) => { - const wrapper = table.closest('div.overflow-x-auto'); - const headers = Array.from(table.querySelectorAll('thead th')).map( - (header) => { - const rawAttrWidth = header.getAttribute('width'); - return { - text: header.textContent?.replace(/\s+/g, ' ').trim() ?? '', - width: Math.round(header.getBoundingClientRect().width), - attrWidth: - rawAttrWidth === null || Number.isNaN(Number(rawAttrWidth)) - ? null - : Number(rawAttrWidth), - }; - }, - ); - - return { - kind: classifyTable(headers.map((header) => header.text)), - wrapperClientWidth: wrapper?.clientWidth ?? 0, - wrapperScrollWidth: wrapper?.scrollWidth ?? 0, - tableScrollWidth: table.scrollWidth, - headers, - }; - }), - }; - }); -} - -test.describe.serial('Infrastructure column layout', () => { - test.afterAll(async ({ browser }) => { - if (mockModeWasEnabled === null) { - return; - } - - const context = await browser.newContext({ httpCredentials: HTTP_CREDENTIALS }); - const page = await context.newPage(); - try { - const current = await getMockMode(page); - if (current.enabled !== mockModeWasEnabled) { - await setMockMode(page, mockModeWasEnabled); - } - } finally { - await context.close(); - } - }); - - test('distributes full-width desktop space across infrastructure columns', async ({ - page, - }, testInfo) => { - test.skip(testInfo.project.name.startsWith('mobile-'), 'Desktop runtime proof'); - - await page.setViewportSize({ width: 1920, height: 1200 }); - await page.addInitScript(() => { - localStorage.setItem('fullWidthMode', 'full-width'); - }); - - await ensureMockModeEnabled(page); - await page.goto('/infrastructure', { waitUntil: 'domcontentloaded' }); - await expect(page.locator('.pulse-shell--full-width').first()).toBeVisible(); - await expect(page.getByTestId('infrastructure-table-surface')).toBeVisible(); - await expect(page.locator('[data-testid="infrastructure-table-surface"] [data-summary-series-id]').first()).toBeVisible(); - await page.waitForTimeout(1000); - - const layout = await readInfrastructureColumnLayout(page); - const hostTable = layout.tables.find((table) => table.kind === 'host'); - - expect(hostTable, 'Expected the infrastructure host table to be visible in mock mode').toBeDefined(); - expect(layout.tables.length, 'Expected visible infrastructure tables to validate').toBeGreaterThan(0); - - for (const table of layout.tables) { - const resourceHeader = table.headers[0]; - const fixedPeerHeaders = table.headers.slice(1).filter((header) => header.attrWidth !== null); - const widestPeerWidth = Math.max(...fixedPeerHeaders.map((header) => header.width)); - - expect( - table.tableScrollWidth, - `${table.kind} table should fit the full-width desktop shell without horizontal scrolling`, - ).toBeLessThanOrEqual(table.wrapperClientWidth + 1); - - expect( - table.wrapperScrollWidth, - `${table.kind} wrapper should not need extra horizontal scroll room in full-width mode`, - ).toBeLessThanOrEqual(table.wrapperClientWidth + 1); - - expect( - fixedPeerHeaders.length, - `Expected ${table.kind} table to expose fixed-width peer columns for validation`, - ).toBeGreaterThan(0); - - expect( - resourceHeader.width / widestPeerWidth, - `${table.kind} resource column should not monopolize the full-width layout`, - ).toBeLessThanOrEqual(1.8); - - const underExpandedPeers = fixedPeerHeaders.filter( - (header) => header.width <= (header.attrWidth ?? 0) + 5, - ); - - expect( - underExpandedPeers, - `${table.kind} table should distribute extra full-width space across peer columns: ${JSON.stringify(table.headers)}`, - ).toEqual([]); - } - }); -}); diff --git a/tests/integration/tests/74-infrastructure-workloads-resource-coherence.spec.ts b/tests/integration/tests/74-infrastructure-workloads-resource-coherence.spec.ts deleted file mode 100644 index a8f674a4f..000000000 --- a/tests/integration/tests/74-infrastructure-workloads-resource-coherence.spec.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { expect, test as base, type Page } from "@playwright/test"; - -import { - apiRequest, - ensureAuthenticated, - getMockMode, - setMockMode, -} from "./helpers"; - -type APIResource = { - id: string; - type?: string; - name?: string; - sources?: string[]; - parentId?: string; - parentName?: string; - proxmox?: { - nodeName?: string; - instance?: string; - vmid?: number; - }; -}; - -const test = base; - -let mockModeWasEnabled: boolean | null = null; - -const isProxmoxResource = (resource: APIResource): boolean => - Boolean(resource.proxmox) || - (resource.sources ?? []).some((source) => source === "proxmox"); - -const isProxmoxWorkload = (resource: APIResource): boolean => - (resource.type === "vm" || resource.type === "system-container") && - isProxmoxResource(resource); - -const cssAttrValue = (value: string): string => - value.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); - -async function ensureMockModeEnabled(page: Page): Promise { - const state = await getMockMode(page); - if (mockModeWasEnabled === null) { - mockModeWasEnabled = state.enabled; - } - if (!state.enabled) { - await setMockMode(page, true); - } -} - -async function readResources(page: Page): Promise { - const response = await apiRequest( - page, - "/api/resources?type=agent,vm,system-container&page=1&limit=500", - ); - expect( - response.ok(), - `Expected /api/resources to succeed, got ${response.status()}`, - ).toBe(true); - const payload = (await response.json()) as { data?: APIResource[] }; - return payload.data ?? []; -} - -function buildProxmoxWorkloadRouteId(resource: APIResource): string { - const instance = (resource.proxmox?.instance ?? "").trim(); - const node = (resource.proxmox?.nodeName ?? "").trim(); - const vmid = resource.proxmox?.vmid ?? 0; - expect( - instance, - `Expected Proxmox workload ${resource.id} to expose an instance`, - ).not.toBe(""); - expect( - node, - `Expected Proxmox workload ${resource.id} to expose a node name`, - ).not.toBe(""); - expect( - vmid, - `Expected Proxmox workload ${resource.id} to expose a VMID`, - ).toBeGreaterThan(0); - return `${instance}:${node}:${vmid}`; -} - -function buildProxmoxNodeFilterValue(resource: APIResource): string { - const instance = (resource.proxmox?.instance ?? "").trim(); - const node = (resource.proxmox?.nodeName ?? "").trim(); - expect( - instance, - `Expected Proxmox workload ${resource.id} to expose an instance`, - ).not.toBe(""); - expect( - node, - `Expected Proxmox workload ${resource.id} to expose a node name`, - ).not.toBe(""); - return `${instance}-${node}`; -} - -function isWorkloadsResourcesRequest(requestUrl: URL): boolean { - if (requestUrl.pathname !== "/api/resources") return false; - const typeParam = requestUrl.searchParams.get("type") ?? ""; - return ( - typeParam.includes("vm") && - typeParam.includes("system-container") && - typeParam.includes("app-container") - ); -} - -async function assertSurfaceDoesNotBlank( - page: Page, - testId: string, - durationMs: number, -): Promise { - const result = await page.evaluate( - async ({ durationMs: sampleDurationMs, testId: targetTestId }) => { - const failures: Array<{ - elapsedMs: number; - bodyText: string; - rootChildren: number; - }> = []; - const startedAt = performance.now(); - let samples = 0; - - while (performance.now() - startedAt < sampleDurationMs) { - const root = document.getElementById("root"); - const target = document.querySelector( - `[data-testid="${targetTestId}"]`, - ); - const rect = target?.getBoundingClientRect(); - const targetVisible = Boolean( - rect && rect.width > 0 && rect.height > 0, - ); - const rootChildren = root?.childElementCount ?? 0; - - if (!root || rootChildren === 0 || !target || !targetVisible) { - failures.push({ - elapsedMs: Math.round(performance.now() - startedAt), - bodyText: (document.body?.innerText ?? "") - .replace(/\s+/g, " ") - .trim() - .slice(0, 120), - rootChildren, - }); - } - - samples += 1; - await new Promise((resolve) => window.setTimeout(resolve, 250)); - } - - return { failures, samples }; - }, - { durationMs, testId }, - ); - - expect( - result.samples, - "Expected browser stability sampling to run", - ).toBeGreaterThan(0); - expect( - result.failures, - `Expected ${testId} to stay mounted and visible without a blank route flash`, - ).toEqual([]); -} - -test.describe.serial("Infrastructure and workloads resource coherence", () => { - test.setTimeout(180_000); - - test.afterAll(async ({ browser }) => { - if (mockModeWasEnabled === null) return; - - const context = await browser.newContext(); - const page = await context.newPage(); - try { - const current = await getMockMode(page); - if (current.enabled !== mockModeWasEnabled) { - await setMockMode(page, mockModeWasEnabled); - } - } finally { - await context.close(); - } - }); - - test("keeps a Proxmox workload visible from its Infrastructure parent through scoped Workloads routing", async ({ - page, - }, testInfo) => { - test.skip( - testInfo.project.name.startsWith("mobile-"), - "Desktop runtime proof", - ); - - await ensureAuthenticated(page); - await ensureMockModeEnabled(page); - - const resources = await readResources(page); - const agents = resources.filter((resource) => resource.type === "agent"); - const workload = resources.find( - (resource) => - isProxmoxWorkload(resource) && - Boolean(resource.parentId) && - Boolean(resource.proxmox?.nodeName) && - Boolean(resource.proxmox?.instance) && - Boolean(resource.proxmox?.vmid), - ); - - expect( - workload, - "Expected mock/runtime data to include a parented Proxmox workload", - ).toBeDefined(); - const selectedWorkload = workload as APIResource; - const parent = agents.find( - (resource) => resource.id === selectedWorkload.parentId, - ); - - expect( - parent, - `Expected Proxmox workload ${selectedWorkload.id} to resolve to one Infrastructure parent`, - ).toBeDefined(); - - const selectedParent = parent as APIResource; - const duplicateParentIds = agents.filter( - (resource) => resource.id === selectedParent.id, - ); - expect( - duplicateParentIds, - `Expected parent ${selectedParent.id} to appear once in the resource API`, - ).toHaveLength(1); - - const parentName = ( - selectedWorkload.parentName || - selectedParent.name || - selectedParent.id - ).trim(); - const nodeName = (selectedWorkload.proxmox?.nodeName ?? "").trim(); - const workloadRouteId = buildProxmoxWorkloadRouteId(selectedWorkload); - const nodeFilterValue = buildProxmoxNodeFilterValue(selectedWorkload); - - await page.goto( - `/infrastructure?source=proxmox-pve&q=${encodeURIComponent(nodeName)}`, - { - waitUntil: "domcontentloaded", - }, - ); - - await expect(page.getByTestId("infrastructure-page")).toBeVisible(); - await expect( - page.getByTestId("infrastructure-table-surface"), - ).toContainText(parentName); - - const parentRows = page.locator( - `[data-testid="infrastructure-table-surface"] [data-summary-series-id="${cssAttrValue(selectedParent.id)}"]`, - ); - await expect( - parentRows, - `Expected Infrastructure to render parent ${parentName} exactly once`, - ).toHaveCount(1); - - await assertSurfaceDoesNotBlank( - page, - "infrastructure-table-surface", - 3_000, - ); - await expect( - parentRows, - `Expected Infrastructure parent ${parentName} to remain visible`, - ).toHaveCount(1); - - await page.goto( - `/workloads?type=${encodeURIComponent(selectedWorkload.type ?? "")}` + - `&platform=proxmox-pve&agent=${encodeURIComponent(nodeFilterValue)}` + - `&resource=${encodeURIComponent(workloadRouteId)}`, - { waitUntil: "domcontentloaded" }, - ); - - await expect(page.getByTestId("workloads-table-surface")).toBeVisible(); - await expect - .poll(() => new URL(page.url()).searchParams.get("platform")) - .toBe("proxmox-pve"); - await expect - .poll(() => new URL(page.url()).searchParams.get("agent")) - .toBe(nodeFilterValue); - await expect( - page.getByRole("button", { name: /Platform:\s*PVE/ }), - ).toBeVisible(); - await expect( - page.getByRole("button", { name: new RegExp(`Node:\\s*${nodeName}`) }), - ).toBeVisible(); - - const workloadRows = page.locator( - `tr[data-guest-id="${cssAttrValue(workloadRouteId)}"]`, - ); - await expect( - workloadRows, - `Expected Workloads to render ${selectedWorkload.name || workloadRouteId} from parent ${parentName}`, - ).toHaveCount(1); - - await assertSurfaceDoesNotBlank(page, "workloads-table-surface", 5_000); - await expect(workloadRows).toHaveCount(1); - }); - - test("keeps Workloads mounted with the last good inventory when resource refresh fails", async ({ - page, - }, testInfo) => { - test.skip( - testInfo.project.name.startsWith("mobile-"), - "Desktop runtime proof", - ); - - await ensureAuthenticated(page); - await ensureMockModeEnabled(page); - - let workloadResourceRequests = 0; - await page.route("**/api/resources**", async (route) => { - const requestUrl = new URL(route.request().url()); - if (!isWorkloadsResourcesRequest(requestUrl)) { - await route.continue(); - return; - } - - workloadResourceRequests += 1; - if (workloadResourceRequests === 1) { - await route.continue(); - return; - } - - await route.fulfill({ - status: 503, - contentType: "application/json", - body: JSON.stringify({ - error: "simulated transient workload inventory gap", - }), - }); - }); - - await page.goto("/workloads", { waitUntil: "domcontentloaded" }); - await expect(page.getByTestId("workloads-table-surface")).toBeVisible(); - - const firstWorkloadRow = page.locator("tr[data-guest-id]").first(); - await expect(firstWorkloadRow).toBeVisible(); - const firstGuestId = await firstWorkloadRow.getAttribute("data-guest-id"); - expect(firstGuestId, "Expected first workload row to expose a stable guest id").toBeTruthy(); - - await expect - .poll(() => workloadResourceRequests, { - message: "Expected the Workloads resource poll to retry after the first render", - timeout: 8_000, - }) - .toBeGreaterThanOrEqual(2); - - await assertSurfaceDoesNotBlank(page, "workloads-table-surface", 2_000); - await expect( - page.locator(`tr[data-guest-id="${cssAttrValue(firstGuestId ?? "")}"]`), - ).toHaveCount(1); - await expect(page.getByText("Loading view...")).toHaveCount(0); - await expect(page.getByRole("heading", { name: "Welcome to Pulse" })).toHaveCount(0); - }); -});