mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Verify composed disk labels without losing paused-read context
Add owning-Web browser proof for the exact combined model after preserving the reviewed backup-reader and original upstream commits. Exercise measured label fit, independent provenance and settled same-VM resumption in Chromium and phone WebKit, while explicitly retaining failed trusted row-tap evidence. Correct only Prettier layout in the additive test resolution; do not repeat the unchanged backup suite or claim native acceptance. Change-source: pulse-maintainer Contract-Neutral: Browser fixtures and own-parent receipt for already composed runtime; Prettier-only assertion layout, with no new contract behaviour.
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const crypto = require('node:crypto');
|
||||
const { chromium, webkit } = require('playwright');
|
||||
|
||||
const root = '/workspace/frontend-modern';
|
||||
const output = '/workspace/tmp/composed-disk-labels-settled';
|
||||
const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
|
||||
const runtime = [
|
||||
'frontend-modern/src/components/Workloads/stackedDiskBarModel.ts',
|
||||
'frontend-modern/src/components/Workloads/StackedDiskBar.tsx',
|
||||
'frontend-modern/src/components/Workloads/useStackedDiskBarState.ts',
|
||||
'frontend-modern/src/components/Workloads/GuestRow.tsx',
|
||||
'frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx',
|
||||
'frontend-modern/src/components/Workloads/DiskList.tsx',
|
||||
'frontend-modern/src/utils/workloadGuestPresentation.ts',
|
||||
'frontend-modern/src/utils/format.ts',
|
||||
'frontend-modern/src/index.css',
|
||||
];
|
||||
|
||||
(async () => {
|
||||
fs.mkdirSync(output, { recursive: true });
|
||||
const playwrightVersion = require('playwright/package.json').version;
|
||||
assert.equal(
|
||||
playwrightVersion,
|
||||
JSON.parse(fs.readFileSync('/workspace/tests/integration/package-lock.json')).packages[
|
||||
'node_modules/@playwright/test'
|
||||
].version,
|
||||
);
|
||||
const hashes = Object.fromEntries(
|
||||
runtime.map((file) => [file, sha256(fs.readFileSync(path.join('/workspace', file)))]),
|
||||
);
|
||||
assert.equal(
|
||||
hashes[runtime[0]],
|
||||
'9f50f630df8379673032052970e7cc6d287c02ecd212b3df5d16f1032be42413',
|
||||
);
|
||||
process.chdir(root);
|
||||
const { createServer } = await import(path.join(root, 'node_modules/vite/dist/node/index.js'));
|
||||
const server = await createServer({
|
||||
root,
|
||||
configFile: path.join(root, 'vite.config.ts'),
|
||||
cacheDir: path.join(output, 'vite-cache'),
|
||||
server: { host: '127.0.0.1', port: 5301, strictPort: true },
|
||||
});
|
||||
const results = [],
|
||||
screenshots = [];
|
||||
let browser;
|
||||
try {
|
||||
await server.listen();
|
||||
for (const [name, engine, width, dark] of [
|
||||
['chromium-desktop', chromium, 1365, false],
|
||||
['webkit-phone', webkit, 390, true],
|
||||
]) {
|
||||
browser = await engine.launch(
|
||||
engine === chromium
|
||||
? { headless: true, channel: 'chromium', args: ['--no-sandbox'] }
|
||||
: { headless: true },
|
||||
);
|
||||
const context = await browser.newContext({
|
||||
viewport: { width, height: width === 390 ? 844 : 900 },
|
||||
isMobile: width === 390,
|
||||
hasTouch: width === 390,
|
||||
locale: 'en-GB',
|
||||
timezoneId: 'UTC',
|
||||
});
|
||||
const page = await context.newPage();
|
||||
page.setDefaultTimeout(15000);
|
||||
page.setDefaultNavigationTimeout(60000);
|
||||
const errors = [],
|
||||
requests = [],
|
||||
checks = [],
|
||||
fits = [];
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
page.on('request', (request) =>
|
||||
requests.push({
|
||||
method: request.method(),
|
||||
pathname: new URL(request.url()).pathname,
|
||||
origin: new URL(request.url()).origin,
|
||||
}),
|
||||
);
|
||||
await page.route('http://127.0.0.1:5301/api/**', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
data: [],
|
||||
alerts: [],
|
||||
anomalies: [],
|
||||
config: null,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
await page.addInitScript((value) => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (value) document.documentElement.classList.add('dark');
|
||||
});
|
||||
}, dark);
|
||||
const capture = async (state) => {
|
||||
const file = path.join(output, `${name}-${state}.png`);
|
||||
await page.screenshot({ path: file, fullPage: true });
|
||||
screenshots.push({
|
||||
path: path.relative('/workspace', file),
|
||||
sha256: sha256(fs.readFileSync(file)),
|
||||
});
|
||||
};
|
||||
const settle = () =>
|
||||
page.evaluate(
|
||||
() =>
|
||||
new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))),
|
||||
);
|
||||
await page.goto('http://127.0.0.1:5301/browser-tests/composed-disk-labels.html');
|
||||
await page.waitForFunction(() => window.__composedDiskLabels);
|
||||
await settle();
|
||||
const checkFit = async (expectedWidth, roomy) => {
|
||||
await page.waitForFunction(
|
||||
(value) =>
|
||||
Math.abs(
|
||||
document.querySelector('[data-fit="single"]').getBoundingClientRect().width - value,
|
||||
) < 0.5,
|
||||
expectedWidth,
|
||||
);
|
||||
await settle();
|
||||
const state = await page.evaluate(() => {
|
||||
const boxes = [...document.querySelectorAll('[data-fit]')].map((box) => {
|
||||
const id = box.getAttribute('data-fit');
|
||||
const labels = [...box.querySelectorAll('span.whitespace-nowrap')];
|
||||
return {
|
||||
id,
|
||||
width: box.getBoundingClientRect().width,
|
||||
text: box.innerText,
|
||||
labels: labels.map((label) => ({
|
||||
text: label.textContent,
|
||||
scroll: label.scrollWidth,
|
||||
client: label.clientWidth,
|
||||
})),
|
||||
sublabels: box.querySelectorAll('.metric-sublabel').length,
|
||||
};
|
||||
});
|
||||
return { boxes, presentation: window.__composedDiskLabels.presentation() };
|
||||
});
|
||||
for (const box of state.boxes) {
|
||||
for (const label of box.labels)
|
||||
assert(
|
||||
label.scroll <= label.client + 1,
|
||||
`${name} ${box.id} ${expectedWidth}: rendered label must fit, not be ellipsised`,
|
||||
);
|
||||
}
|
||||
for (const id of ['single', 'anomaly', 'aggregate']) {
|
||||
const box = state.boxes.find((item) => item.id === id);
|
||||
assert.equal(box.sublabels, roomy ? 1 : 0, `${id}: optional detail matches actual width`);
|
||||
}
|
||||
assert.equal(state.presentation.single.displayLabel, '60%');
|
||||
assert.equal(state.presentation.anomaly.anomalyRatio, '2.5x');
|
||||
assert.equal(state.presentation.aggregate.showDiskCount, true);
|
||||
assert.equal(
|
||||
await page.locator('[data-fit="vertical"] [data-stacked-disk-max-label]').innerText(),
|
||||
'67%',
|
||||
);
|
||||
assert.equal(
|
||||
await page.locator('[data-fit="vertical"] [data-stacked-disk-fill="vertical"]').count(),
|
||||
2,
|
||||
);
|
||||
fits.push({ width: expectedWidth, boxes: state.boxes });
|
||||
};
|
||||
await checkFit(100, false);
|
||||
const compactInline = await page.locator('[data-fit="inline"]').innerText();
|
||||
assert(!compactInline.includes('MMMM'));
|
||||
assert(compactInline.includes('iiii 67%'));
|
||||
await capture('compact');
|
||||
const roomyButton = page.getByRole('button', { name: 'Use roomy bars', exact: true });
|
||||
if (engine === chromium) {
|
||||
await roomyButton.focus();
|
||||
await page.keyboard.press('Enter');
|
||||
} else await roomyButton.tap();
|
||||
await checkFit(320, true);
|
||||
assert((await page.locator('[data-fit="inline"]').innerText()).includes('MMMM 50%'));
|
||||
assert((await page.locator('[data-fit="inline"]').innerText()).includes('iiii 67%'));
|
||||
await capture('roomy');
|
||||
checks.push(
|
||||
'ResizeObserver drops optional detail at 100px and shows complete un-clipped single/anomaly/max/count and inline labels at 320px; vertical max remains truthful',
|
||||
);
|
||||
if (engine === chromium) {
|
||||
await page.locator('[data-fit="single"] [data-stacked-disk-trigger]').hover();
|
||||
const tip = page.locator('[data-tooltip-portal="true"]');
|
||||
await tip.waitFor({ state: 'visible' });
|
||||
assert((await tip.innerText()).includes('Guest reads paused while a backup is running.'));
|
||||
assert((await tip.innerText()).includes('/data'));
|
||||
await page.locator('h1').hover();
|
||||
await page.getByRole('button', { name: 'Mark observation fresh' }).click();
|
||||
await page.locator('[data-fit="single"] [data-stacked-disk-trigger]').hover();
|
||||
await tip.waitFor({ state: 'visible' });
|
||||
assert(!(await tip.innerText()).includes('Guest reads paused'));
|
||||
assert((await tip.innerText()).includes('/data'));
|
||||
await page.locator('h1').hover();
|
||||
checks.push(
|
||||
'Caller-owned paused-read tooltip survives measured model; fresh same-disk update removes only status',
|
||||
);
|
||||
}
|
||||
// Exercise the existing production guest callers, but only the combined
|
||||
// stale-to-fresh boundary, not an unchanged eight-reason acceptance run.
|
||||
await page.goto('http://127.0.0.1:5301/browser-tests/guest-disk-deferral.html');
|
||||
await page.waitForFunction(() => window.__guestDiskEvidence);
|
||||
const row = page.locator('[data-guest-id="fixture-pve:pve-a:101"]');
|
||||
const cell = row.locator('[data-workload-col="disk"]');
|
||||
if (engine === chromium) await row.click();
|
||||
else
|
||||
// WebKit's trusted tap emitted no click on this production row in the
|
||||
// separately retained input diagnostic. Check rendering via a mouse
|
||||
// click, without asserting touch acceptance or masking that failure.
|
||||
await row
|
||||
.locator('[data-workload-col="name"]')
|
||||
.getByText('backup-guest', { exact: true })
|
||||
.click();
|
||||
const drawer = page.getByRole('region', { name: 'Guest Overview' });
|
||||
await drawer.waitFor({ state: 'visible' });
|
||||
const paused = await cell.getAttribute('title');
|
||||
assert(paused.startsWith('Using last known disk stats. '));
|
||||
assert(paused.includes('VM operation lock, such as a backup.'));
|
||||
assert((await drawer.innerText()).includes(paused));
|
||||
assert(
|
||||
(await page.getByRole('region', { name: 'Guest disk list' }).innerText()).includes(paused),
|
||||
);
|
||||
assert((await drawer.innerText()).includes('/data'));
|
||||
if (engine === chromium) {
|
||||
await page.locator('h1').hover();
|
||||
await cell.locator('[data-stacked-disk-trigger]').hover();
|
||||
await page.locator('[data-tooltip-portal="true"]').waitFor({ state: 'visible' });
|
||||
assert((await page.locator('[data-tooltip-portal="true"]').innerText()).includes(paused));
|
||||
await page.locator('h1').hover();
|
||||
}
|
||||
await capture('guest-paused');
|
||||
await page.evaluate(() => {
|
||||
window.__composedOriginalRow = document.querySelector(
|
||||
'[data-guest-id="fixture-pve:pve-a:101"]',
|
||||
);
|
||||
window.__guestDiskEvidence.update({ reason: '', data: true, usage: 75 });
|
||||
});
|
||||
await page.waitForFunction(
|
||||
() => !document.querySelector('[data-workload-col="disk"]').hasAttribute('title'),
|
||||
);
|
||||
assert(!(await drawer.innerText()).includes('Using last known disk stats.'));
|
||||
assert.equal(await drawer.getByRole('progressbar').getAttribute('aria-valuenow'), '75');
|
||||
assert.equal(
|
||||
await page.evaluate(
|
||||
() =>
|
||||
window.__composedOriginalRow ===
|
||||
document.querySelector('[data-guest-id="fixture-pve:pve-a:101"]'),
|
||||
),
|
||||
true,
|
||||
);
|
||||
// AnimatedNumber must finish; do not capture an intermediate old percent
|
||||
// beside the new capacity and claim that it is a settled observation.
|
||||
await page.waitForFunction(() =>
|
||||
document.querySelector('[data-workload-col="disk"]').textContent.includes('75%'),
|
||||
);
|
||||
await capture('guest-fresh');
|
||||
checks.push(
|
||||
'Production GuestRow, Overview and disk list retain paused provenance; same-VM resumption clears notice and updates to 75% without remount',
|
||||
);
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth),
|
||||
true,
|
||||
);
|
||||
assert.deepEqual(errors, []);
|
||||
assert(
|
||||
requests.every(
|
||||
(request) => request.origin === 'http://127.0.0.1:5301' && request.method === 'GET',
|
||||
),
|
||||
);
|
||||
results.push({
|
||||
name,
|
||||
browserVersion: browser.version(),
|
||||
viewport: { width, height: width === 390 ? 844 : 900 },
|
||||
dark,
|
||||
checks,
|
||||
fits,
|
||||
errors,
|
||||
requestCount: requests.length,
|
||||
});
|
||||
await context.close();
|
||||
await browser.close();
|
||||
browser = null;
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(output, 'result.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
result: 'passed',
|
||||
verifiedAt: new Date().toISOString(),
|
||||
playwrightVersion,
|
||||
runtimeHashes: hashes,
|
||||
results,
|
||||
screenshots,
|
||||
limitations:
|
||||
'Synthetic component/CSS/browser composition only; not native QGA, thaw, appliance acceptance, installation, release qualification or availability.',
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
process.stdout.write(
|
||||
`COMPOSED_DISK_LABELS_PASSED ${results.length} browsers / ${screenshots.length} screenshots\n`,
|
||||
);
|
||||
} finally {
|
||||
if (browser) await browser.close();
|
||||
await server.close();
|
||||
}
|
||||
})().catch((error) => {
|
||||
process.stderr.write(`${error.stack}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Composed disk label verification</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./composed-disk-labels.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,93 @@
|
||||
// Production disk model, shell, ResizeObserver and CSS; synthetic observations.
|
||||
// This fixture verifies composition, not collection, thaw or native recovery.
|
||||
import { createSignal, For } from 'solid-js';
|
||||
import { render } from 'solid-js/web';
|
||||
import { StackedDiskBar } from '../src/components/Workloads/StackedDiskBar';
|
||||
import {
|
||||
buildStackedDiskBarPresentation,
|
||||
type StackedDiskBarProps,
|
||||
} from '../src/components/Workloads/stackedDiskBarModel';
|
||||
import type { Disk } from '../src/types/api';
|
||||
import type { AnomalyReport } from '../src/types/aiIntelligence';
|
||||
import '../src/index.css';
|
||||
|
||||
const GiB = 1024 ** 3;
|
||||
const disk = (mountpoint: string, total: number, used: number): Disk => ({
|
||||
mountpoint,
|
||||
total: total * GiB,
|
||||
used: used * GiB,
|
||||
free: (total - used) * GiB,
|
||||
usage: (used / total) * 100,
|
||||
type: 'ext4',
|
||||
});
|
||||
const single = [disk('/data', 440, 265)];
|
||||
const multiple = [disk('/MMMM', 180, 90), disk('/iiii', 260, 175)];
|
||||
const anomaly: AnomalyReport = {
|
||||
resource_id: 'fixture-pve:pve-a:101',
|
||||
resource_name: 'backup-guest',
|
||||
resource_type: 'vm',
|
||||
metric: 'disk',
|
||||
current_value: 60,
|
||||
baseline_mean: 24,
|
||||
baseline_std_dev: 4,
|
||||
z_score: 9,
|
||||
severity: 'high',
|
||||
description: 'Synthetic disk anomaly',
|
||||
};
|
||||
const cases: { id: string; label: string; props: StackedDiskBarProps }[] = [
|
||||
{ id: 'single', label: 'Single filesystem', props: { disks: single } },
|
||||
{ id: 'anomaly', label: 'Filesystem with anomaly', props: { disks: single, anomaly } },
|
||||
{
|
||||
id: 'aggregate',
|
||||
label: 'Aggregate with fullest disk and count',
|
||||
props: { disks: multiple, mode: 'aggregate', showDiskCount: true },
|
||||
},
|
||||
{ id: 'inline', label: 'Equal slots, different glyph widths', props: { disks: multiple } },
|
||||
{
|
||||
id: 'vertical',
|
||||
label: 'Host micro-bars and fullest disk',
|
||||
props: { disks: multiple, mode: 'vertical-bars' },
|
||||
},
|
||||
];
|
||||
|
||||
function Fixture() {
|
||||
const [width, setWidth] = createSignal(100);
|
||||
const [status, setStatus] = createSignal(
|
||||
'Using last known disk stats. Guest reads paused while a backup is running.',
|
||||
);
|
||||
(window as any).__composedDiskLabels = {
|
||||
width: setWidth,
|
||||
presentation: () =>
|
||||
Object.fromEntries(
|
||||
cases.map((item) => [item.id, buildStackedDiskBarPresentation(item.props, width())]),
|
||||
),
|
||||
};
|
||||
return (
|
||||
<main class="min-h-screen space-y-5 bg-surface p-3 text-base-content">
|
||||
<h1 class="text-base font-semibold">Composed disk evidence</h1>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button class="rounded-sm border border-border p-2" onClick={() => setWidth(100)}>
|
||||
Use compact bars
|
||||
</button>
|
||||
<button class="rounded-sm border border-border p-2" onClick={() => setWidth(320)}>
|
||||
Use roomy bars
|
||||
</button>
|
||||
<button class="rounded-sm border border-border p-2" onClick={() => setStatus('')}>
|
||||
Mark observation fresh
|
||||
</button>
|
||||
</div>
|
||||
<For each={cases}>
|
||||
{(item) => (
|
||||
<section aria-label={item.label} class="space-y-2">
|
||||
<h2 class="text-sm font-medium">{item.label}</h2>
|
||||
<div data-fit={item.id} style={{ width: `${width()}px` }}>
|
||||
<StackedDiskBar {...item.props} statusMessage={status()} />
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</For>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
render(() => <Fixture />, document.getElementById('root')!);
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"version": 1,
|
||||
"base_sha": "ffac59b6dcccaf03f52fb4ba747b6f99a79ba9b0",
|
||||
"verified_at": "2026-10-03T22:38:46.141Z",
|
||||
"result": "passed",
|
||||
"changed_paths": [
|
||||
"frontend-modern/src/components/Workloads/stackedDiskBarModel.ts"
|
||||
],
|
||||
"content_sha256": {
|
||||
"frontend-modern/src/components/Workloads/stackedDiskBarModel.ts": "9f50f630df8379673032052970e7cc6d287c02ecd212b3df5d16f1032be42413"
|
||||
},
|
||||
"routes": [
|
||||
"/browser-tests/composed-disk-labels.html",
|
||||
"/browser-tests/guest-disk-deferral.html"
|
||||
],
|
||||
"viewports": [
|
||||
{
|
||||
"width": 1365,
|
||||
"height": 900
|
||||
},
|
||||
{
|
||||
"width": 390,
|
||||
"height": 844
|
||||
}
|
||||
],
|
||||
"states": [
|
||||
"Exact combined deferred-read statusMessage and measured-glyph disk model; production StackedDiskBar, ResizeObserver, GuestRow, Overview, disk list and stylesheet, synthetic observations only. Playwright 1.56.1 matches the integration lock; Chromium 141.0.7390.37 light desktop and WebKit 26.0 dark 390px phone.",
|
||||
"100px bars retain percentages and omit optional capacity detail; equal inline slots retain iiii 67% but drop the wider MMMM label. 320px bars show whole capacity, anomaly, fullest-disk/count and inline labels without ellipsis. The micro-bars keep a truthful 67% highest-disk label.",
|
||||
"Paused guest evidence retains last-known disk values and caller-owned VM-operation-lock context in row, Overview, disk list and desktop tooltip. Same-VM fresh observations clear stale context without remount; row, Overview and disk list settle at 75%. All eight final screenshots inspected.",
|
||||
"Phone drawer rendering is verified using an emulated mouse click on the guest name. Separate trusted WebKit row/name taps emitted no click and did not open the drawer; this pass does not establish touch acceptance or clear that adverse observation. No native collection, QGA/thaw, installation or release claim."
|
||||
],
|
||||
"interactions": [
|
||||
"Chromium keyboard focus and Enter / WebKit tap on the native Roomy button drive ResizeObserver from 100px to 320px. Independent DOM scrollWidth/clientWidth checks verify rendered label fit.",
|
||||
"Desktop hover reads paused provenance beside unchanged /data evidence, then confirms fresh same-disk update removes only that status. Production GuestRow/Overview/list callers are exercised at both viewports; the phone guest is opened by mouse click, not a claimed tap success.",
|
||||
"Fresh source update preserves the same row node, clears stale status, updates the Overview progressbar and waits for AnimatedNumber to settle at 75% before capture. No page error, off-origin request, non-GET request or horizontal page overflow."
|
||||
]
|
||||
}
|
||||
@@ -1516,7 +1516,10 @@ describe('Docker native tables', () => {
|
||||
expect(screen.getByText('2')).toBeInTheDocument();
|
||||
// The snake_case rollout token reads as words; the raw token stays in the title.
|
||||
expect(screen.getByText('Rollback started')).toBeInTheDocument();
|
||||
expect(screen.getByText('Rollback started')).toHaveClass('whitespace-normal', 'wrap-break-word');
|
||||
expect(screen.getByText('Rollback started')).toHaveClass(
|
||||
'whitespace-normal',
|
||||
'wrap-break-word',
|
||||
);
|
||||
expect(
|
||||
screen.getByText('Rollback started').closest('[title]')?.getAttribute('title'),
|
||||
).toContain('rollback_started');
|
||||
|
||||
Reference in New Issue
Block a user