Files
Web_FreeCAD_Bitbybit/scripts/check-chrome-app-e2e-verification.mjs

44 lines
10 KiB
JavaScript

import { createHash } from 'node:crypto'
import { readFile } from 'node:fs/promises'
import { resolve } from 'node:path'
const root = resolve(new URL('..', import.meta.url).pathname)
const report = JSON.parse(await readFile(resolve(root, 'config/chrome-app-e2e-verification.json'), 'utf8'))
if (report.schemaVersion !== 1 || report.browserId !== 'chrome' || report.status !== 'pass') throw new Error('Chrome application E2E evidence is not passing.')
if (!/^HeadlessChrome\//.test(report.browser?.product || '') && !/^Chrome\//.test(report.browser?.product || '')) throw new Error('Chrome application E2E evidence has an unexpected browser product.')
if (report.headers?.coop !== 'same-origin' || report.headers?.coep !== 'require-corp') throw new Error('Chrome application E2E evidence is missing cross-origin isolation headers.')
if (!Array.isArray(report.pageErrors) || report.pageErrors.length !== 0) throw new Error('Chrome application E2E evidence contains page errors.')
if (report.desktop?.path !== '/workspace/pump-housing' || report.desktop?.workbench !== 'Part Design' || report.desktop?.bodyHorizontalOverflow > 1 || report.desktop?.overflowingControls !== 0 || report.desktop?.panelsSeparated !== true || report.desktop?.commandGroups < 4 || !report.desktop?.comboTabs?.some((label) => label.startsWith('Model')) || !report.desktop?.comboTabs?.some((label) => label.startsWith('Tasks')) || report.desktop?.selectionView !== true || report.desktop?.statusBar !== true) throw new Error('Chrome desktop workflow or layout evidence is invalid.')
if (report.uiParity?.fileMenu?.items < 7 || report.uiParity?.fileMenu?.expanded !== 'File' || !report.uiParity?.fileMenu?.labels?.some((label) => label.includes('Save'))) throw new Error('Chrome FreeCAD menu evidence is invalid.')
if (report.uiParity?.commandDialog?.modal !== true || report.uiParity?.commandDialog?.results < 10 || report.uiParity?.commandDialog?.title !== 'Command search' || report.uiParity?.commandDialog?.focused !== 'Command search') throw new Error('Chrome command search dialog evidence is invalid.')
if (report.uiParity?.preferencesDialog?.modal !== true || report.uiParity?.preferencesDialog?.selects !== 3 || report.uiParity?.preferencesDialog?.checks !== 2 || report.uiParity?.aboutDialog?.modal !== true || report.uiParity?.aboutDialog?.baseline !== true) throw new Error('Chrome Preferences/About dialog evidence is invalid.')
if (report.uiParity?.contextMenu?.items?.length !== 4 || !report.uiParity?.contextMenu?.items?.some((label) => label.includes('Toggle visibility')) || !report.uiParity?.contextMenu?.items?.some((label) => label.includes('Delete')) || report.uiParity?.contextMenu?.left < 0 || report.uiParity?.contextMenu?.top < 0) throw new Error('Chrome model tree context menu evidence is invalid.')
if (report.uiParity?.multiSelection?.count !== 2 || JSON.stringify(report.uiParity.multiSelection.labels) !== JSON.stringify(['Sketch', 'Pad'])) throw new Error('Chrome model tree multi-selection evidence is invalid.')
if (!report.workflows?.includes('viewport-multi-object-box-selection') || report.uiParity?.viewportBoxSelection?.objectCount < 2 || report.uiParity.viewportBoxSelection.topologySource !== 'occt-analytic' || report.uiParity.viewportBoxSelection.edgeCount < 4 || report.uiParity.viewportBoxSelection.vertexCount < 4 || report.uiParity.viewportBoxSelection.selectedCount < 2 || !report.uiParity.viewportBoxSelection.labels?.includes('Box') || !report.uiParity.viewportBoxSelection.labels?.includes('Cylinder')) throw new Error('Chrome viewport multi-object box-selection or analytic topology evidence is invalid.')
if (!report.workflows?.includes('cam-workbench-job-flow') || report.uiParity?.camMenu?.items !== 60 || JSON.stringify(report.uiParity?.camMenu?.groups) !== JSON.stringify(['Project Setup', 'Simulation and Tools', '2D Operations', 'Machining Operations', 'Path Modification'])) throw new Error('Chrome FreeCAD CAM menu evidence is invalid.')
if (report.cam?.jobTask?.stockModes !== 4 || report.cam.jobTask.fields < 5 || report.cam?.toolTask?.selects < 2 || report.cam.toolTask.inputs !== 13 || report.cam?.operationTask?.inputs < 5 || report.cam.operationTask.selects < 2) throw new Error('Chrome CAM Task panel evidence is invalid.')
if (report.cam?.undoRedo?.undo?.pathPoints !== 0 || report.cam.undoRedo.undo.redoEnabled !== true || report.cam?.undoRedo?.redo?.pathPoints < 5 || report.cam.undoRedo.redo.undoEnabled !== true) throw new Error('Chrome CAM Undo/Redo UI evidence is invalid.')
if (report.cam?.extendedTasks?.fixture?.inputs !== 7 || report.cam.extendedTasks.fixture.section !== true || !report.cam.extendedTasks.fixture.labels?.some((label) => label.includes('Fixture ID')) || !report.cam.extendedTasks.fixture.labels?.some((label) => label.includes('Max Z')) || report.cam?.extendedTasks?.toolBitLoad?.inputs !== 1 || report.cam.extendedTasks.toolBitLoad.textareas !== 1 || report.cam.extendedTasks.toolBitLoad.schemaVersion !== true || report.cam?.extendedTasks?.drilling?.inputs !== 5 || report.cam.extendedTasks.drilling.selects !== 2 || !report.cam.extendedTasks.drilling.labels?.some((label) => label.includes('Final Depth')) || report.cam?.extendedTasks?.dragKnife?.inputs !== 1 || report.cam.extendedTasks.dragKnife.selects !== 1 || report.cam.extendedTasks.dragKnife.knifeOffset !== true || report.cam?.extendedTasks?.post?.inputs !== 4 || report.cam.extendedTasks.post.selects !== 4 || !report.cam.extendedTasks.post.labels?.some((label) => label.includes('Rotary End')) || !report.cam.extendedTasks.post.labels?.some((label) => label.includes('Tilt End')) || !report.cam.extendedTasks.post.note?.includes('rotary limit checks')) throw new Error('Chrome extended CAM Task panel evidence is invalid.')
if (report.cam?.ui?.workbench !== 'CAM' || report.cam.ui.commandGroups !== 4 || report.cam.ui.toolbarCommands !== 60 || report.cam.ui.jobTree !== true || report.cam.ui.treeRows < 7 || report.cam.ui.operations !== 1 || report.cam.ui.generated !== true || report.cam.ui.toolpathPoints < 5 || report.cam.ui.toolpathOperations !== 1 || report.cam.ui.toolpathLegend !== true || report.cam.ui.horizontalOverflow > 1) throw new Error('Chrome CAM Job tree, toolbar or toolpath evidence is invalid.')
if (report.loftTask?.sectionCount < 1 || report.loftTask?.checkedSectionCount !== 1 || !report.loftTask?.labels?.includes('Sketch (sketch)') || report.loftTask?.panelHorizontalOverflow > 1) throw new Error('Chrome Loft task evidence is invalid.')
if (report.desktop?.kernelPreviewSource !== 'bitbybit-occt' || report.desktop?.canvas?.width < 100 || report.desktop?.canvas?.height < 100 || report.desktop?.canvas?.uniqueColors < 4 || report.desktop?.canvas?.glError !== 0) throw new Error('Chrome OCCT preview or WebGL canvas evidence is blank or invalid.')
if (report.persistenceRoundTrip?.path !== '/workspace/pump-housing' || !report.persistenceRoundTrip?.documentLabel?.includes('Pump Housing') || report.persistenceRoundTrip?.persistenceMode !== 'sqlite-opfs') throw new Error('Chrome OPFS save/reopen evidence is invalid.')
if (report.importFlow?.path !== '/import' || report.importFlow?.accept !== '.FCStd,.fcstd,.step,.stp,.iges,.igs,.brep,.brp' || report.importFlow?.exchangeHint !== true) throw new Error('Chrome import format workflow evidence is invalid.')
if (report.exportFlow?.path !== '/export' || report.exportFlow?.formats?.STEP?.selected !== true || report.exportFlow?.formats?.STEP?.disabled !== false || report.exportFlow?.formats?.STL?.disabled !== false || report.exportFlow?.formats?.IGES?.disabled !== false || report.exportFlow?.selectedAfterInteraction !== 'STL') throw new Error('Chrome export format workflow evidence is invalid.')
if (report.mobile?.path !== '/start' || report.mobile?.viewport?.width !== 390 || report.mobile?.bodyHorizontalOverflow > 1 || report.mobile?.visibleHeading !== true || report.mobile?.visibleActions !== true) throw new Error('Chrome mobile layout evidence is invalid.')
if (!Number.isSafeInteger(report.accessibility?.checked) || report.accessibility.checked < 1 || !Array.isArray(report.accessibility.missing) || report.accessibility.missing.length !== 0) throw new Error('Chrome accessibility naming evidence is invalid.')
if (!Number.isSafeInteger(report.screenReader?.nodes) || report.screenReader.nodes < 10 || report.screenReader.controls < 8 || report.screenReader.namedControls !== report.screenReader.controls || report.screenReader.unnamedControls !== 0) throw new Error('Chrome accessibility tree evidence is invalid.')
if (report.keyboard?.steps !== 8 || report.keyboard?.named !== 8 || report.keyboard?.unnamedVisible !== 0 || !Array.isArray(report.keyboard.sequence) || report.keyboard.sequence.length !== 8) throw new Error('Chrome keyboard focus evidence is invalid.')
if (report.pwa?.manifestLinked !== true || report.pwa?.manifestStatus !== 200 || report.pwa?.name !== 'BitBybit CAD Studio' || report.pwa?.startUrl !== '/start' || report.pwa?.scope !== '/' || report.pwa?.display !== 'standalone') throw new Error('Chrome PWA manifest evidence is invalid.')
if (report.offline?.path !== '/start' || report.offline?.shell !== true || report.offline?.heading !== true) throw new Error('Chrome offline shell evidence is invalid.')
if (Object.keys(report.screenshots || {}).length !== 3 || !report.screenshots?.cam?.path?.endsWith('cam-workbench.png')) throw new Error('Chrome CAM screenshot evidence is missing.')
for (const screenshot of Object.values(report.screenshots || {})) {
if (!screenshot?.path || screenshot.byteLength < 20_000 || !/^[a-f0-9]{64}$/.test(screenshot.sha256 || '')) throw new Error('Chrome screenshot metadata is invalid.')
const bytes = await readFile(resolve(root, screenshot.path))
if (bytes.byteLength !== screenshot.byteLength || bytes.subarray(0, 8).toString('hex') !== '89504e470d0a1a0a') throw new Error(`Chrome screenshot is missing or is not PNG: ${screenshot.path}`)
if (createHash('sha256').update(bytes).digest('hex') !== screenshot.sha256) throw new Error(`Chrome screenshot hash mismatch: ${screenshot.path}`)
}
console.log(JSON.stringify({ status: 'chrome-app-e2e-verification-pass', browserId: report.browserId, workflows: report.workflows, accessibility: report.screenReader, screenshots: report.screenshots }, null, 2))