#!/usr/bin/env node // Phase 3 checks against the real Bun server, a same-origin restart and real workers. import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { mkdtempSync, cpSync, mkdirSync, symlinkSync, readFileSync, writeFileSync, rmSync } from 'node:fs'; import { resolve, join } from 'node:path'; import { createServer } from 'node:net'; import { chromium, webkit } from 'playwright'; import { createThrottleProxy } from './proxy.mjs'; const repo=resolve(new URL('..',import.meta.url).pathname), scratch=mkdtempSync(join(repo,'perf/.tmp/seams-')); const sleep=ms=>new Promise(r=>setTimeout(r,ms)); async function wait(page,fn,arg,timeout=90000){const end=Date.now()+timeout;while(Date.now()portServer.listen(0,'127.0.0.1',r));const port=portServer.address().port;await new Promise(r=>portServer.close(r)); let child,browser,proxy,log=''; async function stop(){if(!child)return;const p=child;child=null;if(p.exitCode===null){p.kill('SIGTERM');await new Promise(r=>p.once('exit',r));}} async function start(){log='';child=spawn('bun',[join(repo,'server/server.js')],{cwd,env:{...process.env,PORT:String(port),ASSET_HASHING:'1',ASSET_SYNC:'1',BUILD_TAG:'',DB_PATH:join(cwd,'db.sqlite'),DATA_DIR:join(cwd,'data'),MEDIA_DIR:join(cwd,'media'),UPLOAD_DIR:join(cwd,'uploads')},stdio:['ignore','pipe','pipe']});for(const stream of [child.stdout,child.stderr])stream.on('data',c=>log=(log+c).slice(-15000));for(let i=0;i<200;i++){if(child.exitCode!==null)throw Error(log);try{if((await fetch('http://127.0.0.1:'+port+'/api/version')).ok)return;}catch{}await sleep(100);}throw Error(log);} try { await start();proxy=createThrottleProxy({targetPort:port,profile:'unthrottled'});const origin='http://127.0.0.1:'+await proxy.listen();proxy.startTracking(); browser=await ({chromium,webkit}[name]).launch({headless:true});const context=await browser.newContext(); await context.route('**/*',r=>r.request().url().startsWith(origin)?r.continue():r.abort()); await context.addInitScript(()=>{Object.defineProperty(navigator,'onLine',{configurable:true,get:()=>!window.__offline});}); const page=await context.newPage(),errors=[],failedRequests=[];page.on('pageerror',e=>errors.push(e.message));page.on('requestfailed',r=>failedRequests.push(r.url())); await page.goto(origin);await page.waitForFunction(()=>typeof data!=='undefined'&&document.querySelector('#carModeBtn')); await wait(page,async()=>!!(await navigator.serviceWorker.getRegistration())?.active); await page.evaluate(()=>Lazy.warm()); await wait(page,async()=>{const c=await caches.open('ytplayer-assets'),r=await c.match('/__ytp_asset_state');if(!r)return false;const s=await r.json();return !(await Promise.all(Object.values(s.current.groups).flatMap(g=>g.files).map(async p=>!!await c.match(p+'?v='+s.current.files[p].h)))).includes(false);}); await page.reload();await page.waitForFunction(()=>typeof data!=='undefined'&&document.querySelector('#carModeBtn')); await page.evaluate(()=>{window.__offline=true;window.__shellBuild=document.querySelector('meta[name="ytp-build"]').content;}); await stop();await proxy.setOffline(true);const before=proxy.getStats().requestCount; await page.evaluate(()=>{data.playlists=[{id:'smoke',name:'Cached playlist',videos:[]}];render();}); assert.equal(await page.locator('#homePlGrid .home-pl-name').textContent(),'Cached playlist'); await page.click('[data-view="settings"]');await page.waitForSelector('.settings'); await page.evaluate(()=>{view={type:'search'};render();}); const groups=await page.evaluate(()=>Object.keys(Lazy.manifest.groups)); const checked=['shell']; if(groups.includes('feature:presenter')) { await page.evaluate(async()=>{history.replaceState(null,'','/?present=');await Presenter.boot();}); await page.waitForSelector('#presenterView:not(.hidden)'); await page.locator('#pvCode').fill('x');await page.locator('#pvPairBtn').click();assert.match(await page.locator('#pvMsg').textContent(),/6-digit/); await page.evaluate(()=>document.activeElement?.blur());await page.keyboard.press('b'); assert.equal(await page.locator('#pvBlank').evaluate(n=>!n.classList.contains('hidden')),true); await page.evaluate(()=>{document.querySelector('#presenterView').classList.add('hidden');document.body.classList.remove('presenter-open');});checked.push('presenter'); } if(groups.includes('feature:remote-client')) { await page.evaluate(()=>document.querySelector('#navRemoteBtn').click()); await page.waitForSelector('#modal:not(.hidden)');assert.match(await page.locator('#modal').textContent(),/remote|screen|phone/i); await page.getByRole('button',{name:'Use this phone as a remote',exact:true}).click(); await page.waitForSelector('#remoteView:not(.hidden)');await page.locator('#rvCode').fill('x');await page.locator('#rvPairBtn').click(); assert.match(await page.locator('#rvPairMsg').textContent(),/6-digit/);await page.locator('#rvClose').click();checked.push('remote'); } if(groups.includes('feature:watch-party')) { await page.evaluate(()=>document.querySelector('#navPartyBtn').click());await page.waitForSelector('#partyPanel:not(.hidden)'); await page.locator('.pt-name').fill('Cached guest');assert.equal(await page.locator('.pt-name').inputValue(),'Cached guest');checked.push('party'); } if(groups.includes('feature:share-external')) { await page.evaluate(async()=>{current={meta:{id:'benchmedia01',title:'Cached smoke',channel:'Fixture'}};await Share.open();}); await page.waitForSelector('.share-moment');assert.match(await page.locator('.sm-app').textContent(),/v=benchmedia01/); await page.locator('.sm-time').fill('0:30');await page.locator('.sm-time').dispatchEvent('input'); assert.match(await page.locator('.sm-app').textContent(),/t=30/); const m3u=await page.evaluate(async()=>{await Lazy.load('feature:share-external');return window.YT.External.m3u([{id:'benchmedia01',title:'Cached smoke'}]);});assert.match(m3u,/#EXTM3U/);assert.match(m3u,/benchmedia01/); await page.evaluate(async()=>{closeModal();current={meta:{id:'upl_0123456789ab',title:'Cached upload'}};await External.open();}); await page.waitForSelector('.ext-panel');assert.match(await page.locator('.ext-panel').textContent(),/api\/uploads\/upl_0123456789ab/); await page.evaluate(()=>closeModal());checked.push('share'); } if(groups.includes('feature:stats-view')) { await page.evaluate(async()=>{view={type:'stats'};render();});await page.waitForSelector('.stats-empty'); await page.evaluate(async()=>{data.stats=StatsCore.record(null,{id:'benchmedia01',title:'Cached stats',channel:'Fixture'},600,true);await renderStats();}); await page.waitForSelector('.stats .st-tiles');assert.match(await page.locator('.st-song-text').first().textContent(),/Cached stats/);checked.push('stats'); } if(groups.includes('feature:share-external')) { await page.evaluate(()=>{Share.open=()=>Promise.reject(new Error('Unavailable fixture chunk'));document.querySelector('#shareMomentBtn').click();}); await page.getByText('Unavailable fixture chunk',{exact:true}).waitFor(); } assert.equal(proxy.getStats().requestCount,before,'all warmed seam code loads from SW without network'); // Ignore only verified disconnected version/recommendation API transports in WebKit. const scriptErrors=errors.filter(e=>!(name==='webkit'&&(e==='TypeError: Load failed'||/\/api\/(?:recommendations\?[^\s]*|version)\.$/.test(e))));assert.deepEqual(scriptErrors,[]); if(errors.length>scriptErrors.length)assert.ok(failedRequests.some(url=>/\/api\/(?:recommendations|version)/.test(url)),'ignored WebKit transport errors require a recorded disconnected API request'); results.push({browser:name,checked,offlineNetworkRequests:proxy.getStats().requestCount-before,errors:scriptErrors});console.log(name,JSON.stringify(results.at(-1))); }finally{await browser?.close();await stop();await proxy?.close();} } try{for(const name of requested==='all'?['chromium','webkit']:[requested])await run(name);writeFileSync(join(repo,'perf/results/phase4-seams-2026-10-08.json'),JSON.stringify(results,null,2)+'\n');}finally{rmSync(scratch,{recursive:true,force:true});}