71 lines
6.7 KiB
JavaScript
71 lines
6.7 KiB
JavaScript
#!/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()<end){if(await page.evaluate(fn,arg))return;await sleep(100);}throw Error('Timed out: '+fn.toString());}
|
|
const args=process.argv.slice(2), requested=args.includes('--browser')?args[args.indexOf('--browser')+1]:'all';
|
|
const results=[];
|
|
async function run(name){
|
|
const cwd=join(scratch,name);mkdirSync(cwd);cpSync(join(repo,'frontend'),join(cwd,'public'),{recursive:true});
|
|
const portServer=createServer();await new Promise(r=>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=[];page.on('pageerror',e=>errors.push(e.message));
|
|
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.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.evaluate(()=>closeModal());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('#modal').textContent(),/Cached smoke/);
|
|
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(()=>closeModal());checked.push('share');
|
|
}
|
|
if(groups.includes('feature:stats-view')) {
|
|
await page.evaluate(async()=>{view={type:'stats'};await renderStats();});await page.waitForSelector('.stats-empty');checked.push('stats');
|
|
}
|
|
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,[]);
|
|
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});}
|