From 0f480d23aff50bd33236d125aafbfd67b1a65fd9 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:42:25 +0100 Subject: [PATCH] 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. --- .../browser-tests/composed-disk-labels.cjs | 313 ++++++++++++++++++ .../browser-tests/composed-disk-labels.html | 12 + .../browser-tests/composed-disk-labels.tsx | 93 ++++++ .../e3b706f9d975b05a.json | 37 +++ .../__tests__/DockerNativeTables.test.tsx | 5 +- 5 files changed, 459 insertions(+), 1 deletion(-) create mode 100644 frontend-modern/browser-tests/composed-disk-labels.cjs create mode 100644 frontend-modern/browser-tests/composed-disk-labels.html create mode 100644 frontend-modern/browser-tests/composed-disk-labels.tsx create mode 100644 frontend-modern/browser-verification/e3b706f9d975b05a.json diff --git a/frontend-modern/browser-tests/composed-disk-labels.cjs b/frontend-modern/browser-tests/composed-disk-labels.cjs new file mode 100644 index 0000000000..fe629a89ee --- /dev/null +++ b/frontend-modern/browser-tests/composed-disk-labels.cjs @@ -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; +}); diff --git a/frontend-modern/browser-tests/composed-disk-labels.html b/frontend-modern/browser-tests/composed-disk-labels.html new file mode 100644 index 0000000000..1f5e9a26e6 --- /dev/null +++ b/frontend-modern/browser-tests/composed-disk-labels.html @@ -0,0 +1,12 @@ + + + + + + Composed disk label verification + + +
+ + + diff --git a/frontend-modern/browser-tests/composed-disk-labels.tsx b/frontend-modern/browser-tests/composed-disk-labels.tsx new file mode 100644 index 0000000000..aff9bbdfce --- /dev/null +++ b/frontend-modern/browser-tests/composed-disk-labels.tsx @@ -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 ( +
+

Composed disk evidence

+
+ + + +
+ + {(item) => ( +
+

{item.label}

+
+ +
+
+ )} +
+
+ ); +} + +render(() => , document.getElementById('root')!); diff --git a/frontend-modern/browser-verification/e3b706f9d975b05a.json b/frontend-modern/browser-verification/e3b706f9d975b05a.json new file mode 100644 index 0000000000..463b1880c3 --- /dev/null +++ b/frontend-modern/browser-verification/e3b706f9d975b05a.json @@ -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." + ] +} diff --git a/frontend-modern/src/features/docker/__tests__/DockerNativeTables.test.tsx b/frontend-modern/src/features/docker/__tests__/DockerNativeTables.test.tsx index b7f88cf332..15bb7087d2 100644 --- a/frontend-modern/src/features/docker/__tests__/DockerNativeTables.test.tsx +++ b/frontend-modern/src/features/docker/__tests__/DockerNativeTables.test.tsx @@ -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');