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.
This commit is contained in:
rcourtman 2026-07-08 11:15:13 +01:00
parent d88248363e
commit 005100432b
7 changed files with 0 additions and 1322 deletions

View file

@ -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 });
});
});

View file

@ -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 });
});
});

View file

@ -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<void>((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 });
});
});

View file

@ -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 });
});
});

View file

@ -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 });
});
});

View file

@ -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<void> {
const state = await getMockMode(page);
if (mockModeWasEnabled === null) {
mockModeWasEnabled = state.enabled;
}
if (!state.enabled) {
await setMockMode(page, true);
}
}
async function readInfrastructureColumnLayout(page: Page): Promise<InfrastructureColumnLayout> {
return page.evaluate(() => {
const tables = Array.from(
document.querySelectorAll<HTMLTableElement>('[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<HTMLDivElement>('div.overflow-x-auto');
const headers = Array.from(table.querySelectorAll<HTMLTableCellElement>('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([]);
}
});
});

View file

@ -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<void> {
const state = await getMockMode(page);
if (mockModeWasEnabled === null) {
mockModeWasEnabled = state.enabled;
}
if (!state.enabled) {
await setMockMode(page, true);
}
}
async function readResources(page: Page): Promise<APIResource[]> {
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<void> {
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<HTMLElement>(
`[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);
});
});