// Reproduction: build and start apps/docsite, then run: // ASTRYX_ROOT=/path/to/astryx ARM=head PORT=62454 OUT=. node assets/pr-5548/cls-warm.cjs // The script primes exact-route assets, then measures at 370x700, 1.5Mbps, 40ms, and 6x CPU. const fs=require('fs');const path=require('path');const ROOT=process.env.ASTRYX_ROOT||process.cwd();const {chromium}=require(path.join(ROOT,'node_modules/playwright')); const ARM=process.env.ARM,PORT=Number(process.env.PORT),OUT=process.env.OUT;const routes=['/components/DateInput','/components/DateInput?tab=properties','/themes','/themes?theme=butter']; function maxWindow(es,all=false){let max=0,sum=0,start=0,prev=0;for(const e of es){if(e.hadRecentInput&&!all)continue;if(e.startTime-prev>1000||e.startTime-start>5000){start=e.startTime;sum=0}sum+=e.value;max=Math.max(max,sum);prev=e.startTime}return max} function expected(route){const u=new URL('http://x'+route);return route.startsWith('/themes')?{kind:'theme',value:u.searchParams.get('theme')==='butter'?'Butter':'Neutral'}:{kind:'tab',value:u.searchParams.get('tab')==='properties'?'properties':'overview'}} async function waitReady(page,want,timeout=60000){await page.waitForFunction(({kind,value})=>{const vis=e=>{const r=e.getBoundingClientRect(),s=getComputedStyle(e);return r.width>0&&r.height>0&&s.display!=='none'&&s.visibility!=='hidden'};if(kind==='theme'){const h=[...document.querySelectorAll('h1')].find(vis);const trigger=[...document.querySelectorAll('button[aria-expanded]')].find(e=>vis(e)&&e.textContent.trim()===value);return h&&getComputedStyle(h).fontSize==='35px'&&trigger}const tab=document.querySelector(`[data-tab-value="${value}"]`);return tab?.getAttribute('aria-current')==='page'&&Object.keys(tab).some(k=>k.startsWith('__reactProps$'))},want,{timeout})} (async()=>{const results=[];for(const route of routes){console.log('START',ARM,route);const browser=await chromium.launch({headless:true});try{const context=await browser.newContext({viewport:{width:370,height:700},deviceScaleFactor:1,isMobile:true,hasTouch:true,colorScheme:'light',reducedMotion:'no-preference'});const page=await context.newPage();const want=expected(route); // Prime exact route chunks and fonts in this context; the measurement navigation keeps that cache. await page.goto(`http://127.0.0.1:${PORT}${route}`,{waitUntil:'domcontentloaded',timeout:60000});await waitReady(page,want);await page.waitForTimeout(500);await page.goto('about:blank'); const errors=[],bad=[];page.on('pageerror',e=>errors.push(`pageerror: ${e.message}`));page.on('console',m=>{if(m.type()==='error')errors.push(`console: ${m.text()}`)});page.on('response',r=>{if(r.status()>=400)bad.push(`${r.status()} ${r.url()}`)}); await page.addInitScript(()=>{window.__r={shifts:[],snaps:[]};const label=n=>n?`${n.tagName}:${String(n.textContent||'').trim().slice(0,80)}`:null;new PerformanceObserver(list=>{for(const e of list.getEntries())window.__r.shifts.push({value:e.value,startTime:e.startTime,hadRecentInput:e.hadRecentInput,sources:(e.sources||[]).map(s=>({node:label(s.node),previousRect:s.previousRect,currentRect:s.currentRect}))})}).observe({type:'layout-shift',buffered:true});const snap=x=>window.__r.snaps.push({label:x,at:performance.now(),height:document.documentElement.scrollHeight,chars:document.body.innerText.length});new PerformanceObserver(list=>{for(const e of list.getEntries())if(e.name==='first-contentful-paint')requestAnimationFrame(()=>snap('fcp'))}).observe({type:'paint',buffered:true});document.addEventListener('DOMContentLoaded',()=>requestAnimationFrame(()=>snap('dcl')));addEventListener('load',()=>requestAnimationFrame(()=>snap('load')))}); const cdp=await context.newCDPSession(page);await cdp.send('Network.enable');await cdp.send('Network.setCacheDisabled',{cacheDisabled:false});await cdp.send('Network.emulateNetworkConditions',{offline:false,latency:40,downloadThroughput:187500,uploadThroughput:93750,connectionType:'cellular3g'});await cdp.send('Emulation.setCPUThrottlingRate',{rate:6}); const start=Date.now();await page.goto(`http://127.0.0.1:${PORT}${route}`,{waitUntil:'domcontentloaded',timeout:90000});await waitReady(page,want,90000);await page.waitForTimeout(1000);const d=await page.evaluate(()=>{const vis=e=>{const r=e.getBoundingClientRect(),s=getComputedStyle(e);return r.width>0&&r.height>0&&s.display!=='none'&&s.visibility!=='hidden'};const h=[...document.querySelectorAll('h1')].find(vis);const theme=[...document.querySelectorAll('button[aria-expanded]')].find(e=>vis(e)&&['Neutral','Butter'].includes(e.textContent.trim()));return{url:location.href,height:document.documentElement.scrollHeight,chars:document.body.innerText.length,h1:h?{text:h.textContent.trim(),size:getComputedStyle(h).fontSize}:null,currentTab:document.querySelector('[data-tab-value][aria-current="page"]')?.getAttribute('data-tab-value')??null,theme:theme?.textContent.trim()??null,shifts:window.__r.shifts,snaps:window.__r.snaps}});d.cls=maxWindow(d.shifts);d.clsIncludingRecentInput=maxWindow(d.shifts,true);d.clsSum=d.shifts.filter(x=>!x.hadRecentInput).reduce((a,b)=>a+b.value,0);d.clsSumIncludingRecentInput=d.shifts.reduce((a,b)=>a+b.value,0);d.elapsedMs=Date.now()-start;d.errors=errors;d.bad=bad;d.arm=ARM;d.route=route;results.push(d);console.log('DONE',ARM,route,'CLS',d.cls,'CLS_ALL',d.clsIncludingRecentInput,'ms',d.elapsedMs,'height',d.height,'tab',d.currentTab,'theme',d.theme,'h1',JSON.stringify(d.h1))}finally{await browser.close()}} fs.writeFileSync(`${OUT}/cls-370x700-warm-${ARM}.json`,JSON.stringify({arm:ARM,cache:'route assets primed; measured navigation uses cache',network:{downMbps:1.5,upMbps:.75,latencyMs:40},cpuThrottle:6,results},null,2)+'\n')})().catch(e=>{console.error(e);process.exit(1)});