mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
ac8172c42c
Reconcile coverage rows by logical keys before windowing. Isolated Chromium checks at desktop and narrow widths preserve keyboard focus, expanded evidence, route and scroll across replacement HTTP snapshots. Include subsystem completion obligations and a content-bound browser receipt. Does not qualify the full application scroll-jump report in #1869 or reporter resolution. Change-source: pulse-maintainer
143 lines
4.8 KiB
JavaScript
143 lines
4.8 KiB
JavaScript
// Run with pulse-heavy-run -- node scripts/check-backup-browser-polling.mjs
|
|
// Isolated real-browser component regression; no running Pulse instance needed.
|
|
import { createServer } from "../frontend-modern/node_modules/vite/dist/node/index.js";
|
|
import { chromium, expect } from "@playwright/test";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = fileURLToPath(new URL("../frontend-modern", import.meta.url));
|
|
process.chdir(root); // PostCSS/Tailwind resolve their config from the frontend root.
|
|
const server = await createServer({
|
|
root,
|
|
configFile: `${root}/vite.config.ts`,
|
|
server: { host: "127.0.0.1", port: 5198, strictPort: true },
|
|
plugins: [
|
|
{
|
|
name: "backup-browser-fixture",
|
|
configureServer(server) {
|
|
server.middlewares.use((req, _res, next) => {
|
|
if (req.url?.startsWith("/proxmox/backups/"))
|
|
req.url = "/browser-tests/backups.html";
|
|
next();
|
|
});
|
|
},
|
|
},
|
|
],
|
|
});
|
|
let browser;
|
|
try {
|
|
await server.listen();
|
|
browser = await chromium.launch({ headless: true });
|
|
const page = await browser.newPage({
|
|
viewport: { width: Number(process.env.PULSE_BROWSER_WIDTH || 1440), height: 900 },
|
|
});
|
|
const errors = [];
|
|
page.on("pageerror", (error) => errors.push(error.message));
|
|
page.on("console", (message) => {
|
|
if (message.type() === "error") console.error(message.text());
|
|
});
|
|
page.on("requestfailed", (request) =>
|
|
console.error(request.url(), request.failure()),
|
|
);
|
|
let generation = 0;
|
|
const workloads = () =>
|
|
Array.from({ length: 40 }, (_, index) => ({
|
|
id: `ct-${112 + index}`,
|
|
type: "system-container",
|
|
name: `guest-${String(index).padStart(2, "0")}-snapshot-${generation}`,
|
|
displayName: `guest-${String(index).padStart(2, "0")}-snapshot-${generation}`,
|
|
platformId: "pve-a",
|
|
platformType: "proxmox-pve",
|
|
sourceType: "api",
|
|
status: "running",
|
|
lastSeen: Date.now(),
|
|
proxmox: { vmid: 112 + index, node: "pve-a", instance: "pve-a" },
|
|
}));
|
|
await page.route("**/fixture/workloads", (route) =>
|
|
route.fulfill({ json: workloads() }),
|
|
);
|
|
await page.route(
|
|
(url) => url.pathname.startsWith("/api/"),
|
|
(route) => {
|
|
const path = new URL(route.request().url()).pathname;
|
|
if (path === "/api/backups/pve")
|
|
return route.fulfill({
|
|
json: {
|
|
data: {
|
|
guestSnapshots: workloads().map((w) => ({
|
|
id: `snap-${w.id}`,
|
|
name: "pre-upgrade",
|
|
node: "pve-a",
|
|
instance: "pve-a",
|
|
type: "ct",
|
|
vmid: w.proxmox.vmid,
|
|
time: new Date().toISOString(),
|
|
vmstate: false,
|
|
})),
|
|
storageBackups: [],
|
|
backupTasks: [],
|
|
},
|
|
},
|
|
});
|
|
if (path === "/api/backups/pbs")
|
|
return route.fulfill({ json: { data: { backups: [] } } });
|
|
return route.fulfill({ json: { data: [], policy: {} } });
|
|
},
|
|
);
|
|
await page.goto("http://127.0.0.1:5198/proxmox/backups/coverage");
|
|
const toggle = page.getByRole("button", {
|
|
name: /expand details for guest-20-/i,
|
|
});
|
|
// The narrow layout exposes this control on keyboard focus, not pointer hover.
|
|
await toggle.waitFor({ timeout: 20_000 });
|
|
await toggle.focus();
|
|
await toggle.press("Enter").catch(async (error) => {
|
|
console.error(errors, await page.locator("body").innerText());
|
|
throw error;
|
|
});
|
|
const expanded = page.getByRole("button", {
|
|
name: /collapse details for guest-20-/i,
|
|
});
|
|
await expanded.focus();
|
|
await expect(
|
|
page.getByText("Restore evidence", { exact: true }),
|
|
).toBeVisible();
|
|
const baseline = await page.evaluate(() => ({
|
|
y: document.querySelector("main").scrollTop,
|
|
url: location.href,
|
|
}));
|
|
expect(baseline.y).toBeGreaterThan(100);
|
|
const samples = [];
|
|
for (generation = 1; generation <= 3; generation++) {
|
|
await expect(
|
|
page
|
|
.getByText(`guest-20-snapshot-${generation}`, { exact: true })
|
|
.first(),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByText("Restore evidence", { exact: true }),
|
|
).toBeVisible();
|
|
const sample = await page.evaluate(() => ({
|
|
y: document.querySelector("main").scrollTop,
|
|
url: location.href,
|
|
}));
|
|
console.log(
|
|
JSON.stringify({
|
|
generation,
|
|
...sample,
|
|
focused: await expanded.evaluate((el) => el === document.activeElement),
|
|
}),
|
|
);
|
|
await expect(expanded).toBeFocused();
|
|
expect(sample.url).toBe(baseline.url);
|
|
expect(Math.abs(sample.y - baseline.y)).toBeLessThanOrEqual(1);
|
|
samples.push({ generation, ...sample });
|
|
}
|
|
expect(errors).toEqual([]);
|
|
console.log(
|
|
JSON.stringify({ baseline, samples, pageErrors: errors }, null, 2),
|
|
);
|
|
} finally {
|
|
await browser?.close();
|
|
await server.close();
|
|
}
|