// Own isolated renderer QA. Original observations come only from the pinned REA archive. const fs=require('node:fs/promises'),path=require('node:path'),crypto=require('node:crypto'),assert=require('node:assert/strict') const output=path.resolve('.tmp/artifact-link-ui-regression') const reference=path.resolve('.tmp/rea-assessment-initial/layout.json') const referenceId='ev_d6e6920ff4e47f73a182fac5312c5f02393366a035445d18c8a6733a8c8a11ed' const sha=bytes=>crypto.createHash('sha256').update(bytes).digest('hex') if(!process.versions.electron){ ;(async()=>{ await fs.mkdir(output,{recursive:true}) const entry=`import React,{useState} from 'react';import {createRoot} from 'react-dom/client'; import SourceArtifactLink from './src/renderer/src/components/SourceArtifactLink'; import './src/renderer/src/workbuddy/typography/tokens.css';import './src/renderer/src/styles/tokens.css';import './src/renderer/src/styles/app.css'; import './src/renderer/src/workbuddy/conversation/source-styles.css';import './src/renderer/src/workbuddy/conversation/source-adapters.css'; window.qa={clicks:0};function Fixture(){const [count,setCount]=useState(13);window.qa.setCount=setCount;return
window.qa.clicks++}/>
}createRoot(document.getElementById('root')).render();` await require('esbuild').build({stdin:{contents:entry,loader:'tsx',resolveDir:process.cwd()},outfile:path.join(output,'fixture.js'),bundle:true,platform:'browser',format:'iife',jsx:'automatic',logLevel:'silent'}) await fs.writeFile(path.join(output,'index.html'),'
') const env={...process.env};delete env.ELECTRON_RUN_AS_NODE const result=require('node:child_process').spawnSync(require('electron'),[__filename],{env,windowsHide:true,stdio:'inherit',timeout:30000}) process.exitCode=result.status??1 })().catch(error=>{console.error(error);process.exitCode=1}) }else{ const {app,BrowserWindow,nativeImage}=require('electron') app.setPath('userData',path.join(output,'user-data')) app.disableHardwareAcceleration() app.commandLine.appendSwitch('use-angle','swiftshader') app.commandLine.appendSwitch('disable-gpu-compositing') const delay=ms=>new Promise(resolve=>setTimeout(resolve,ms)) app.whenReady().then(async()=>{ const source=JSON.parse(await fs.readFile(reference,'utf8')) assert.equal(source.evidenceId,referenceId) const sourceNodes=source.layout.documents[0].nodes,sourceSvg=source.layout.documents[0].svg_nodes const errors=[],checks=[],win=new BrowserWindow({x:0,y:0,width:1336,height:901,useContentSize:true,frame:false,show:false,webPreferences:{contextIsolation:true,nodeIntegration:false,backgroundThrottling:false}}) win.webContents.on('console-message',event=>{if(event.level==='error')errors.push(event.message)}) const check=(label,actual,expected)=>checks.push({label,pass:JSON.stringify(actual)===JSON.stringify(expected),actual,expected}) const read=expression=>win.webContents.executeJavaScript(expression) await win.loadFile(path.join(output,'index.html'));await delay(250) win.showInactive();win.webContents.sendInputEvent({type:'mouseMove',x:0,y:0});await delay(200) const metrics=await read(`(()=>{const selectors={actions:'.artifact-slot-panel__actions',button:'.artifact-slot-panel__action-btn',chevron:'.artifact-slot-panel__action-chevron',svg:'.artifact-slot-panel__action-chevron svg'};const properties=${JSON.stringify(source.layout.properties)};return {nodes:Object.fromEntries(Object.entries(selectors).map(([key,selector])=>{const e=document.querySelector(selector),r=e.getBoundingClientRect(),c=getComputedStyle(e);return[key,{bounds:[r.x,r.y,r.width,r.height],styles:Object.fromEntries(properties.map(name=>[name,c.getPropertyValue(name)])),attributes:Object.fromEntries([...e.attributes].map(a=>[a.name,a.value]))}]})),slotDisplay:getComputedStyle(document.querySelector('[data-chat-search-skip]')).display,text:document.querySelector('.artifact-slot-panel__action-btn').textContent}})()`) for(const [name,index] of [['actions',1644],['button',1645],['chevron',1647],['svg',1648]]){ const expected=sourceNodes.find(node=>node.node_index===index) check(name+' bounds',metrics.nodes[name].bounds,expected.bounds) for(const [property,value] of Object.entries(metrics.nodes[name].styles))check(name+' '+property,value,expected.styles[property]) } check('display-contents slot',metrics.slotDisplay,'contents') check('exact count label',metrics.text,'查看所有产物 (13)') check('single direct label text node',await read("[...document.querySelector('.artifact-slot-panel__action-btn').childNodes].filter(n=>n.nodeType===Node.TEXT_NODE).map(n=>n.textContent)"),['查看所有产物 (13)']) check('SVG attributes',Object.fromEntries(['width','height','viewBox','fill'].map(key=>[key,metrics.nodes.svg.attributes[key]])),sourceSvg.find(node=>node.node_index===1648).attributes) check('SVG path',await read("document.querySelector('.artifact-slot-panel__action-chevron path').getAttribute('d')"),sourceSvg.find(node=>node.node_index===1649).attributes.d) win.webContents.debugger.attach('1.3') const capture=await Promise.race([win.webContents.debugger.sendCommand('Page.captureScreenshot',{format:'png',fromSurface:true,captureBeyondViewport:false}),delay(5000).then(()=>{throw Error('Own artifact-link screenshot timeout')})]) const screenshot=Buffer.from(capture.data,'base64');await fs.writeFile(path.join(output,'screen.png'),screenshot) const originalBytes=await fs.readFile('.tmp/rea-assessment-initial/workbuddy.png') assert.equal(sha(originalBytes),source.artifact.sha256,'reference PNG must match retained REA artifact') const original=nativeImage.createFromBuffer(originalBytes),candidate=nativeImage.createFromBuffer(screenshot),roi={x:324,y:614,width:139,height:26} const a=original.crop(roi),b=candidate.crop(roi),before=a.toBitmap(),after=b.toBitmap() await fs.writeFile(path.join(output,'reference-roi.png'),a.toPNG());await fs.writeFile(path.join(output,'candidate-roi.png'),b.toPNG()) let changed=0,max=0,sum=0,changedInSvg=0;const deltas=[] for(let i=0;i=450&&x<459&&y>=621&&y<633)changedInSvg++;if(deltas.length<12)deltas.push({x,y,reference:[...before.subarray(i,i+3)],candidate:[...after.subarray(i,i+3)]})}} const pixel={roi,pixels:roi.width*roi.height,changed,changedInSvg,max,meanRgbDelta:sum/(roi.width*roi.height*3),fullPixelParity:changed===0,deltas,referenceSha256:sha(a.toPNG()),candidateSha256:sha(b.toPNG())} const [x,y,w,h]=metrics.nodes.button.bounds,point={x:Math.floor(x+w/2),y:Math.floor(y+h/2)} win.webContents.sendInputEvent({type:'mouseMove',...point});await delay(200) const hover=await read("(()=>{const b=document.querySelector('.artifact-slot-panel__action-btn'),c=document.querySelector('.artifact-slot-panel__action-chevron');return{buttonColor:getComputedStyle(b).color,chevronColor:getComputedStyle(c).color,bounds:b.getBoundingClientRect().toJSON()}})()") check('own hover button color',hover.buttonColor,'rgb(0, 0, 0)');check('own hover chevron color',hover.chevronColor,'rgb(0, 0, 0)') win.webContents.sendInputEvent({type:'mouseDown',button:'left',clickCount:1,...point});win.webContents.sendInputEvent({type:'mouseUp',button:'left',clickCount:1,...point});await delay(50) check('native click callback once',await read('window.qa.clicks'),1) win.focus();win.webContents.focus();await delay(100) const keyboard={windowFocused:win.isFocused(),webContentsFocused:win.webContents.isFocused(),buttonFocused:await read("document.activeElement?.matches('.artifact-slot-panel__action-btn')===true"),tested:false} if(keyboard.windowFocused&&keyboard.webContentsFocused&&keyboard.buttonFocused){ win.webContents.sendInputEvent({type:'keyDown',keyCode:'Return'});win.webContents.sendInputEvent({type:'char',keyCode:'\r'});win.webContents.sendInputEvent({type:'keyUp',keyCode:'Return'});await delay(50) keyboard.tested=true check('native Enter callback once',await read('window.qa.clicks'),2) } await read('window.qa.setCount(0)');await delay(50) check('empty count omits entry',await read("document.querySelector('.artifact-slot-panel__action-btn')===null"),true) check('renderer errors',errors,[]) const integration=JSON.parse(await fs.readFile('.tmp/panel-integration-Capd62/report.json','utf8')) const gpuFeatureStatus=app.getGPUFeatureStatus(),gpu=await app.getGPUInfo('complete') check('archived GPU feature status',gpuFeatureStatus,source.environment_gpu.feature_status) const report={result:checks.every(check=>check.pass)?'PASS geometry/styles/own interactions':'FAIL geometry/styles/own interactions',checks,counts:{total:checks.length,pass:checks.filter(check=>check.pass).length,fail:checks.filter(check=>!check.pass).length},metrics,pixel,hover,keyboard,reference:{evidenceId:referenceId,archive:source.archive,screenshotSha256:sha(originalBytes),version:'WorkBuddy 5.8.2'},gpuFeatureStatus,sourceGpuFeatureStatus:source.environment_gpu.feature_status,gpu,productionHashes:{component:sha(await fs.readFile('src/renderer/src/components/SourceArtifactLink.tsx')),css:sha(await fs.readFile('src/renderer/src/components/source-artifact-link.css'))},integration:{result:integration.result,total:integration.checks.length,pass:integration.checks.filter(check=>check.pass).length,scope:integration.scope},limitations:['Original hover state is not archived; own hover and activation tests do not establish original hover pixel parity.','The callback is an isolated counter; actual overview/tab retention is covered by the separate production integration report.',...(!keyboard.tested?['Native Enter was not tested because a real focused window/webContents/button precondition was unavailable.']:[]),'No production GPU flags or UI source changed; no WorkBuddy runtime actions or new original evidence were collected.']} await fs.writeFile(path.join(output,'report.json'),JSON.stringify(report,null,2)) console.log(JSON.stringify({result:report.result,checks:report.counts,button:metrics.nodes.button.bounds,svg:metrics.nodes.svg.bounds,pixel,integration:report.integration.total})) win.destroy();app.exit(report.counts.fail?1:0) }).catch(error=>{console.error(error);app.exit(1)}) }