clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/standalone-relay
tinyrelay / scripts / qa / ux-browser-edges.js
ux-browser-edges.js 4.9K
1// Run with playwright-cli run-code --filename after opening the disposable UX relay.2async page => {3 const base = await page.evaluate(()=>location.origin);4 const context = await page.context().browser().newContext({serviceWorkers:'block',viewport:{width:1280,height:900}});5 const probe=await context.newPage(), results=[], errors=[];6 probe.on('pageerror',error=>errors.push(error.message));7 const check=(ok,message)=>{if(!ok)throw Error(message);};8 const run=async(name,action)=>{try{results.push({name,ok:true,detail:await action()});}catch(error){results.push({name,ok:false,error:error.message});}};9 try {10 await probe.goto(base+'/');11 await probe.evaluate(()=>window.__uxMarker='same');12 await run('network and HTTP failures leave the current page usable',async()=>{13 await probe.route('**/articles',route=>route.fulfill({status:503,body:'Temporary failure'}));14 await probe.locator('#rail').getByRole('link',{name:'/articles',exact:true}).click();15 await probe.waitForFunction(()=>document.querySelector('#navigation-status')?.textContent.includes('Unable'));16 check(probe.url()===base+'/','HTTP error changed the URL');17 await probe.unroute('**/articles');18 await probe.route('**/articles',route=>route.abort());19 await probe.locator('#rail').getByRole('link',{name:'/articles',exact:true}).click();20 await probe.waitForFunction(()=>document.querySelector('#navigation-status')?.textContent.includes('Unable'));21 check(await probe.locator('#content').getAttribute('aria-busy')!=='true','network error leaves busy state');22 await probe.unroute('**/articles');23 });24 await run('slow earlier navigation cannot overwrite the latest click',async()=>{25 let finish;26 const gate=new Promise(resolve=>{finish=resolve;});27 await probe.route('**/articles',async route=>{await gate;await route.continue().catch(()=>{});});28 await probe.locator('#rail').getByRole('link',{name:'/articles',exact:true}).click();29 await probe.locator('#rail').getByRole('link',{name:'/repos',exact:true}).click();30 await probe.waitForURL(base+'/repos');31 finish();32 await probe.unroute('**/articles');33 check(await probe.locator('#content h1').textContent()==='Repositories','stale response replaced content');34 });35 await run('sign-in errors use current status after a shell swap',async()=>{36 await probe.locator('#who').getByRole('link',{name:'Sign in',exact:true}).click();37 await probe.waitForURL(base+'/signin');38 await probe.locator('#session-login').click();39 await probe.waitForFunction(()=>document.querySelector('#signer-status')?.textContent.includes('Connect a Nostr signer first'));40 await probe.locator('#bunker-url').fill('https://example.invalid');41 await probe.locator('#bunker button').click();42 await probe.waitForFunction(()=>document.querySelector('#signer-status')?.textContent.includes('valid bunker URL'));43 check(await probe.evaluate(()=>window.__uxMarker)==='same','sign-in navigation reloaded');44 });45 await run('modified clicks retain native new-tab behavior',async()=>{46 const newTab=context.waitForEvent('page');47 await probe.locator('#rail').getByRole('link',{name:'/repos',exact:true}).click({modifiers:['ControlOrMeta']});48 const opened=await newTab;49 await opened.waitForLoadState();50 check(probe.url()===base+'/signin','modified click navigated the current page');51 check(opened.url()===base+'/repos','modified click opened wrong URL');52 await opened.close();53 });54 await run('seeded sequence of 60 read-only navigation actions',async()=>{55 let seed=0x74696e79;56 const routes=['/home','/search','/repos','/files','/sites','/articles','/inbox','/outbox'];57 for(let i=0;i<60;i++){58 seed=(Math.imul(seed,1664525)+1013904223)>>>0;59 const name=routes[(seed>>>16)%routes.length], path=name==='/home'?'/':name;60 await probe.locator('#rail').getByRole('link',{name,exact:true}).click();61 await probe.waitForURL(base+path);62 await probe.waitForFunction(()=>document.querySelector('#content')?.getAttribute('aria-busy')!=='true');63 const state=await probe.evaluate(()=>({marker:window.__uxMarker,ids:[...document.querySelectorAll('[id]')].map(e=>e.id),classes:document.querySelectorAll('[class]').length,sections:document.querySelectorAll('#content>section').length}));64 check(state.marker==='same','document reloaded at '+i);65 check(state.classes===0,'class attributes introduced at '+i);66 check(new Set(state.ids).size===state.ids.length,'duplicate DOM IDs at '+i);67 check(state.sections===1,'content section missing at '+i);68 }69 return {seed:'0x74696e79',actions:60};70 });71 } finally { await context.close(); }72 results.push({name:'uncaught browser errors',ok:errors.length===0,errors});73 return {passed:results.filter(r=>r.ok).length,failed:results.filter(r=>!r.ok).length,results};74}