tinyrelayfile
tinyrelay/scripts/qa/ux-browser-edges.js/

scripts/qa/ux-browser-edges.js raw
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}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs