import assert from 'node:assert/strict'; export function assertChangedAssets(requests, expected) { const paths=requests.filter(r=>r.bytes>0).map(r=>new URL(r.url,'http://localhost').pathname).filter(p=>/\.(?:js|css|woff2?|png|svg)$/.test(p)&&!['/sw.js','/asset-sync-core.js'].includes(p)); assert.deepEqual([...new Set(paths)],[expected],'only the changed application asset transfers; worker checks and derived index/manifest are separate overhead'); } export async function waitForIdleAssets(page) { if(!await page.evaluate(()=>!!window.Lazy?.warm))return; await page.evaluate(()=>Lazy.warm()); const end=Date.now()+120000; while(true) { const ready=await page.evaluate(async()=>{ const c=await caches.open('ytplayer-assets'),r=await c.match('/__ytp_asset_state'); // Legacy rollback has no manifest state and installs its complete shell instead. if(!r)return (await caches.keys()).some(k=>/^ytplayer-(?!assets|thumbs|fonts)/.test(k)); const m=(await r.json()).current; return (await Promise.all(Object.values(m.groups).flatMap(g=>g.files).map(async p=>{ const response=await c.match(p+'?v='+m.files[p].h); return response?.headers.get('X-Asset-Hash')===m.files[p].h; }))).every(Boolean); }); if(ready)return; if(Date.now()>end)throw Error('Idle warm did not complete before benchmark'); await new Promise(r=>setTimeout(r,100)); } } export function assertWarmAssets(requests) { const assets=requests.filter(r=>r.bytes>0&&/\.(?:js|css|woff2?|png|svg)(?:[?#]|$)/.test(r.url)&&!new URL(r.url,'http://localhost').pathname.endsWith('/sw.js')); assert.deepEqual(assets,[],'warm reload does not transfer unfinished shell assets'); }