Assert offline-node visibility against the mock fleet state
Some checks are pending
Build and Test / Secret Scan (push) Waiting to run
Build and Test / Frontend & Backend (push) Waiting to run
Canonical Governance / governance (push) Waiting to run
Helm CI / Lint and Render Chart (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 1/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 2/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 3/4) (push) Waiting to run
Core E2E Tests / Playwright Core E2E (shard 4/4) (push) Waiting to run
Core E2E Tests / E2E verdict (push) Blocked by required conditions

Platform pages render websocket state, so the offline-node guard's REST
stubs never reached the surface it was checking; it also targeted the
retired /infrastructure route. The mock scenario already forces pve5
(Disaster Recovery B) offline deterministically, which is the exact
state the guard exists for: the spec now asserts on /proxmox that the
offline node stays listed with dashed live metrics while keeping its
web-interface link and detail expansion.
This commit is contained in:
rcourtman 2026-07-08 16:20:00 +01:00
parent fca5975c38
commit 02c8e5cce3

View file

@ -6,7 +6,6 @@ import { expect, test as base } from '@playwright/test';
import { createAuthenticatedStorageState } from './helpers';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ARTIFACTS_DIR = path.resolve(__dirname, '..', '..', 'tmp', 'offline-proxmox-node-visibility');
type WorkerFixtures = {
authStorageStatePath: string;
@ -35,157 +34,37 @@ const test = base.extend<{}, WorkerFixtures>({
}, { scope: 'worker' }],
});
const OFFLINE_RESOURCE = {
id: 'offline-pve',
type: 'agent',
name: 'pve-offline',
displayName: 'PVE Offline',
platformId: 'offline-pve',
platformType: 'proxmox-pve',
sourceType: 'api',
sources: ['proxmox-pve'],
status: 'offline',
lastSeen: '2026-04-21T20:00:00Z',
canonicalIdentity: {
displayName: 'PVE Offline',
hostname: 'pve-offline.lab.local',
platformId: 'offline-pve',
},
agent: {
hostname: 'pve-offline.lab.local',
platform: 'Proxmox VE',
uptimeSeconds: 0,
memory: { total: 0, used: 0, free: 0, usage: 0 },
disks: [],
networkInterfaces: [],
raid: [],
},
platformData: {
sources: ['proxmox-pve'],
proxmox: {
instance: 'homelab',
nodeName: 'pve-offline',
clusterName: 'homelab',
connectionHealth: 'error',
pveVersion: '8.3-1',
kernelVersion: '6.8.12-9-pve',
uptime: 0,
cpuInfo: {
model: 'Intel Xeon',
cores: 8,
sockets: 1,
mhz: '2400',
},
memory: { total: 0, used: 0, free: 0, usage: 0 },
disk: { total: 0, used: 0, free: 0, usage: 0 },
loadAverage: [],
linkedAgentId: '',
},
},
} as const;
const EMPTY_INFRASTRUCTURE_CHARTS = {
nodeData: {},
agentData: {},
dockerHostData: {},
timestamp: Date.now(),
stats: {
oldestDataTimestamp: 0,
range: '1h',
rangeSeconds: 3600,
pointCounts: {
total: 0,
nodes: 0,
agents: 0,
dockerHosts: 0,
},
},
} as const;
const EMPTY_STORAGE_SUMMARY_TREND = {
capacity: [],
stats: {
oldestDataTimestamp: 0,
range: '24h',
rangeSeconds: 86400,
pointCounts: {
total: 0,
storage: 0,
},
},
} as const;
const EMPTY_RECOVERY_ROLLUPS = {
data: [],
meta: {
page: 1,
limit: 500,
total: 0,
totalPages: 1,
},
} as const;
// The mock scenario deterministically forces pve5 (Disaster Recovery B)
// offline, which is exactly the state this guard exists for: an offline
// node must stay listed on its platform page instead of silently vanishing
// from the fleet view. Platform pages render websocket state, so the guard
// asserts against the mock dataset rather than REST stubs.
test.describe('Offline Proxmox node visibility', () => {
test.setTimeout(180_000);
test('keeps an offline Proxmox node visible on the infrastructure surface', async ({
test('keeps an offline Proxmox node visible on the Proxmox platform surface', async ({
page,
}, testInfo) => {
test.skip(testInfo.project.name.startsWith('mobile-'), 'Desktop runtime proof');
fs.mkdirSync(ARTIFACTS_DIR, { recursive: true });
await page.route('**/api/resources**', async (route) => {
const requestUrl = new URL(route.request().url());
await page.goto('/proxmox', { waitUntil: 'domcontentloaded' });
await expect(page.getByTestId('proxmox-page')).toBeVisible();
if (requestUrl.pathname === '/api/resources') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
data: [OFFLINE_RESOURCE],
meta: {
page: 1,
limit: 200,
total: 1,
totalPages: 1,
},
}),
});
return;
}
const offlineRow = page
.locator('[data-testid="proxmox-page"] tr')
.filter({ hasText: 'Disaster Recovery B' })
.first();
await offlineRow.scrollIntoViewIfNeeded();
await expect(offlineRow).toBeVisible();
await route.continue();
});
await page.route('**/api/charts/infrastructure**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(EMPTY_INFRASTRUCTURE_CHARTS),
});
});
await page.route('**/api/charts/storage-summary**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(EMPTY_STORAGE_SUMMARY_TREND),
});
});
await page.route('**/api/recovery/rollups**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(EMPTY_RECOVERY_ROLLUPS),
});
});
await page.goto('/infrastructure?source=proxmox-pve', { waitUntil: 'domcontentloaded' });
await expect(page.getByTestId('infrastructure-page')).toBeVisible();
const infrastructureTable = page.locator('[data-testid="infrastructure-page"] table').first();
await expect(infrastructureTable).toContainText('PVE Offline');
await expect(infrastructureTable).toContainText('Offline');
// Offline presentation: live metrics are dashed out, but the node keeps
// its identity and management affordances.
await expect(offlineRow).toContainText('—');
await expect(
offlineRow.getByRole('link', { name: 'Open web interface for Disaster Recovery B' }),
).toBeVisible();
await expect(
offlineRow.getByRole('button', { name: 'Expand details for Disaster Recovery B' }),
).toBeVisible();
});
});