Public source validation / validate (push) Failing after 3m8s
105 lines
7.4 KiB
TypeScript
105 lines
7.4 KiB
TypeScript
import AxeBuilder from '@axe-core/playwright';
|
|
import { expect, test } from '@playwright/test';
|
|
import path from 'node:path';
|
|
|
|
const containers = Array.from({ length: 150 }, (_, index) => ({
|
|
id: `container-${String(index + 1).padStart(3, '0')}`,
|
|
name: `container-${String(index + 1).padStart(3, '0')}`,
|
|
image: 'example/pulse:read-only', state: index % 17 === 0 ? 'exited' : 'running', health: index % 13 === 0 ? 'unhealthy' : 'healthy',
|
|
intentionalStop: false, metricsAvailable: true, lifecycleAvailable: true, uptimeSeconds: 3600, restartCount: 0, exitCode: 0,
|
|
cpuPercent: index / 10, memoryBytes: 1024 * (index + 1), memoryLimitBytes: 1024 * 1024,
|
|
networkRxBytes: 0, networkTxBytes: 0, blockReadBytes: 0, blockWriteBytes: 0,
|
|
}));
|
|
const processes = Array.from({ length: 60 }, (_, index) => ({ pid: index + 1, name: `worker-${String(index + 1).padStart(2, '0')}`, state: 'running', runtimeSeconds: 300, cpuPercent: index, memoryBytes: 2048 + index, containerName: index % 2 ? 'pulse' : 'database' }));
|
|
const entities = Array.from({ length: 60 }, (_, index) => ({ id: `entity-${index + 1}`, entityType: 'container', canonicalName: `container.${index + 1}`, displayName: `Entity ${String(index + 1).padStart(2, '0')}`, status: 'operational', factCount: 2, overrideCount: 0, relationCount: 1, sourceCount: 1, staleFactCount: 0 }));
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.route('**/api/v1/containers?**', async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const query = (url.searchParams.get('q') ?? '').toLowerCase();
|
|
const state = url.searchParams.get('state') ?? '';
|
|
const health = url.searchParams.get('health') ?? '';
|
|
const after = Number(url.searchParams.get('after') ?? '0');
|
|
const limit = Number(url.searchParams.get('limit') ?? '25');
|
|
const filtered = containers.filter((item) => (!query || item.name.includes(query)) && (!state || item.state === state) && (!health || item.health === health));
|
|
const items = filtered.slice(after, after + limit);
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ source: { id: 'target-scale', state: 'healthy' }, total: filtered.length, containers: items, nextCursor: after + limit < filtered.length ? String(after + limit) : '' }) });
|
|
});
|
|
await page.route('**/api/v1/processes?**', async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const query = (url.searchParams.get('q') ?? '').toLowerCase();
|
|
const container = (url.searchParams.get('container') ?? '').toLowerCase();
|
|
const after = Number(url.searchParams.get('after') ?? '0');
|
|
const filtered = processes.filter((item) => (!query || item.name.includes(query)) && (!container || item.containerName.includes(container)));
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ source: { id: 'target-scale', state: 'healthy' }, total: filtered.length, processes: filtered.slice(after, after + 25), nextCursor: after + 25 < filtered.length ? String(after + 25) : '' }) });
|
|
});
|
|
await page.route('**/api/v1/entities?**', async (route) => {
|
|
const url = new URL(route.request().url());
|
|
const query = (url.searchParams.get('q') ?? '').toLowerCase();
|
|
const after = Number(url.searchParams.get('after') ?? '0');
|
|
const filtered = entities.filter((item) => !query || (item.displayName + item.canonicalName).toLowerCase().includes(query));
|
|
await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ items: filtered.slice(after, after + 25), hasMore: after + 25 < filtered.length, nextCursor: after + 25 < filtered.length ? String(after + 25) : '' }) });
|
|
});
|
|
});
|
|
|
|
test('process and inventory filters survive navigation with mobile-first cards', async ({ page }, testInfo) => {
|
|
test.skip(testInfo.project.name === 'wallboard-chromium', 'Large lists target desktop and mobile routes.');
|
|
await page.goto('/processes?q=worker&container=pulse&sort=memory');
|
|
await expect(page.getByRole('heading', { name: 'Topprocessen' })).toBeVisible();
|
|
await expect(page.getByLabel('Zoeken')).toHaveValue('worker');
|
|
await expect(page.getByLabel('Container')).toHaveValue('pulse');
|
|
await expect(page).toHaveURL(/sort=memory/);
|
|
if (testInfo.project.name === 'mobile-chromium') await expect(page.locator('.mobile-data-list > li:visible')).toHaveCount(25);
|
|
else await expect(page.locator('.desktop-data-view tbody tr:visible')).toHaveCount(25);
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true);
|
|
|
|
await page.goto('/inventory?q=Entity&type=container&status=operational&order=desc');
|
|
await expect(page.getByRole('heading', { name: 'Wat Pulse kan zien' })).toBeVisible();
|
|
await expect(page.getByLabel('Zoeken')).toHaveValue('Entity');
|
|
await expect(page.getByLabel('Type')).toHaveValue('container');
|
|
await expect(page.getByRole('textbox', { name: 'Status' })).toHaveValue('operational');
|
|
await expect(page.locator('.inventory-entity-list > li')).toHaveCount(25);
|
|
await page.getByRole('button', { name: 'Volgende pagina' }).click();
|
|
await expect(page.locator('.list-pager [role="status"]')).toBeFocused();
|
|
await expect(page).toHaveURL(/after=25/);
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true);
|
|
});
|
|
|
|
test('all 150 containers remain reachable with shareable filters and bounded mobile cards', async ({ page }, testInfo) => {
|
|
test.skip(testInfo.project.name === 'wallboard-chromium', 'Large lists target desktop and mobile routes.');
|
|
const started = Date.now();
|
|
await page.goto('/containers');
|
|
await expect(page.getByRole('heading', { name: 'Containers' })).toBeVisible();
|
|
const visibleRows = testInfo.project.name === 'mobile-chromium' ? page.locator('.mobile-data-list > li:visible') : page.locator('.desktop-data-view tbody tr:visible');
|
|
await expect(visibleRows).toHaveCount(25);
|
|
expect(Date.now() - started).toBeLessThan(3000);
|
|
const seen = new Set<string>();
|
|
for (let pageNumber = 1; pageNumber <= 6; pageNumber += 1) {
|
|
await expect(visibleRows).toHaveCount(25);
|
|
for (const value of await visibleRows.locator('a').allTextContents()) seen.add(value.trim());
|
|
if (pageNumber < 6) {
|
|
const next = page.getByRole('button', { name: 'Volgende pagina' });
|
|
await next.click();
|
|
await expect(page.locator('.list-pager [role="status"]')).toBeFocused();
|
|
}
|
|
}
|
|
expect(seen.size).toBe(150);
|
|
expect(seen.has('container-150')).toBe(true);
|
|
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth + 1)).toBe(true);
|
|
if (testInfo.project.name === 'mobile-chromium') {
|
|
const controls = await page.locator('.list-pager button').evaluateAll((buttons) => buttons.map((button) => button.getBoundingClientRect().height));
|
|
expect(controls.every((height) => height >= 44)).toBe(true);
|
|
}
|
|
|
|
await page.getByLabel('Zoeken').fill('container-150');
|
|
await expect(visibleRows).toHaveCount(1);
|
|
await expect(page).toHaveURL(/q=container-150/);
|
|
await page.reload();
|
|
await expect(visibleRows).toHaveCount(1);
|
|
await expect(visibleRows.getByText('container-150')).toBeVisible();
|
|
|
|
const axe = await new AxeBuilder({ page }).analyze();
|
|
expect(axe.violations.filter((item) => item.impact === 'serious' || item.impact === 'critical')).toEqual([]);
|
|
if (process.env.PULSE_CAPTURE_VISUALS) await page.screenshot({ path: path.resolve('../../artifacts/evidence/M11-08', `large-containers-${testInfo.project.name}.png`), fullPage: true });
|
|
});
|