/** Actual GraphPage/React Flow + IndexedDB probe; fixture HTTP server, not production service. */ import assert from 'node:assert/strict'; import {writeFile} from 'node:fs/promises'; import {setTimeout as pause} from 'node:timers/promises'; export async function measureRenderer({call,evaluate,documents,root,partition,count,exceptions,reportPath}){ const initial=count+2,target=initial+count+1,started=Date.now(); const wait=async(expression,label)=>{const deadline=Date.now()+240000;while(Date.now()requestAnimationFrame(()=>requestAnimationFrame(resolve)))`); await evaluate(`(()=>{ const base=${initial},target=${target},start=performance.now(),tasks=[],gaps=[]; let raf,last=start,sawPreparing=false,firstDOM=null,firstFrame=null,allDOM=null,allFrame=null,prepared=null; const supported=PerformanceObserver.supportedEntryTypes.includes('longtask'),observer=supported?new PerformanceObserver(list=>tasks.push(...list.getEntries().map(entry=>({start:entry.startTime-start,duration:entry.duration})))):null; observer?.observe({type:'longtask'}); function tick(now){gaps.push(now-last);last=now;const size=document.querySelectorAll('.react-flow__node').length;if(size>base){if(firstDOM===null)firstDOM=now-start;else if(firstFrame===null)firstFrame=now-start;}if(size>=target){if(allDOM===null)allDOM=now-start;else if(allFrame===null)allFrame=now-start;}if(document.querySelector('[aria-label="正在准备复制"]'))sawPreparing=true;else if(sawPreparing&&prepared===null)prepared=now-start;raf=requestAnimationFrame(tick);} raf=requestAnimationFrame(tick); window.__canvasPerformance={read(){return{elapsedMs:performance.now()-start,firstDOMMs:firstDOM,firstPresentedFrameMs:firstFrame,allDOMMs:allDOM,allPresentedFrameMs:allFrame,prepareMs:prepared,nodeCount:document.querySelectorAll('.react-flow__node').length,longTasksSupported:supported,longTaskCount:tasks.length,maxLongTaskMs:Math.max(0,...tasks.map(task=>task.duration)),totalLongTaskMs:tasks.reduce((total,task)=>total+task.duration,0),maxFrameGapMs:Math.max(0,...gaps),longTasks:tasks};},stop(){cancelAnimationFrame(raf);if(observer){tasks.push(...observer.takeRecords().map(entry=>({start:entry.startTime-start,duration:entry.duration})));observer.disconnect();}return this.read();}}; document.querySelector('[aria-label="深复制选中内容"]').click(); })()`); await wait(`document.querySelectorAll('.react-flow__node').length===${target}&&document.querySelector('[aria-label="画布导入"]')?.textContent.includes('已加入画布')&&[...document.querySelectorAll('[role=status]')].some(item=>item.textContent.trim()==='已同步')`,'large copy did not finish rendering and synchronization'); await evaluate('new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)))'); const measurements=await evaluate('window.__canvasPerformance.stop()'),document=documents.get(root),copiedCanvas=[...document.canvases.values()].find(canvas=>canvas.parent&&canvas.id!==partition); assert.equal(document.elements.size,2*count+5);assert.ok(copiedCanvas);assert.ok([...document.elements.values()].some(element=>element.structure==='connection'&&element.source.elementId==='perf-source'&&element.target.elementId===copiedCanvas.parent.entryElementId));assert.ok(document.elements.has('perf-incoming'));assert.equal(measurements.nodeCount,target);assert.ok(measurements.firstPresentedFrameMs!==null&&measurements.allPresentedFrameMs!==null); report={scenario:'actual GraphPage deep copy of expanded partition',mode:'Next development webpack; isolated fixture HTTP; real Chrome/IndexedDB/Web Locks',count,initialReadyMs,measurements,browser:await call('Browser.getVersion'),serverElements:document.elements.size,serverCanvases:document.canvases.size,wallMs:Date.now()-started}; const screenshot=await call('Page.captureScreenshot',{format:'png'});await writeFile(reportPath.endsWith('.json')?reportPath.slice(0,-5)+'.png':reportPath+'.png',Buffer.from(screenshot.data,'base64')); }catch(error){let measurements;try{measurements=await evaluate('window.__canvasPerformance?.stop()');}catch{}report={scenario:'actual GraphPage deep copy of expanded partition',count,error:error.message,measurements,wallMs:Date.now()-started};await writeFile(reportPath,JSON.stringify(report,null,2));throw error;} await writeFile(reportPath,JSON.stringify(report,null,2));console.log('MEASURE '+JSON.stringify({...report,measurements:{...report.measurements,longTasks:undefined}}));console.log('REPORT '+reportPath); }