"""Chromium fixture evidence QA. ASGI GET with simulated EventSource, no native tasks. Default CSS is the explicitly identified Bootstrap 5.3.6 fixture substitute. No fixture stylesheet is copied into the release. HTMX is deliberately unavailable unless the operator supplies the pinned asset; this is not proxy/HTTPS acceptance. """ from pathlib import Path import argparse,json,os,socket,sys,tempfile,time,re sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'src')) from dataclasses import replace from fastapi.testclient import TestClient from playwright.sync_api import sync_playwright from aim_webgui.app import create_app from aim_webgui.auth.service import Auth from aim_webgui.config import Settings from aim_webgui.journal import Journal,project from aim_webgui.reports import persist, TITLES from evidence_fixtures import declared,sample,plan,result,job,ev def main(): p=argparse.ArgumentParser();p.add_argument('--out',type=Path,required=True);p.add_argument('--bootstrap',type=Path,default=Path('/opt/imagemagick/share/doc/ImageMagick-7/www/assets/bootstrap.min.css'));args=p.parse_args();args.out.mkdir(parents=True,exist_ok=True) with tempfile.TemporaryDirectory(prefix='aim-evidence-browser-')as td: state=Path(td)/'state' sock=socket.socket();sock.bind(('127.0.0.1',0));port=sock.getsockname()[1];sock.close() url=f'http://127.0.0.1:{port}' s=Settings(state_dir=state,public_url=url,core_transport='stdio');a=Auth(s);a.bootstrap() with a.store.transaction()as db: db.execute('UPDATE users SET must_change_password=0');uid=db.execute('SELECT id FROM users').fetchone()[0] paths={};jobs={} for name in TITLES: d=declared(name);data=sample(d['data_schema']) if name=='host_capabilities_v1':data['is_unifi_controller']=True if name=='filesystem_usage_v1': data={'platform':'linux','filesystems':[{'name':'/dev/synthetic1','mount':'/','filesystem_type':'ext4','used_bytes':42949672960,'total_bytes':128849018880,'available_bytes':85899345920,'used_percent':33.3,'status':'available'},{'name':'/dev/synthetic2','mount':'/srv/archive','filesystem_type':None,'used_bytes':None,'total_bytes':None,'available_bytes':None,'used_percent':None,'status':'unavailable'}]} ident=job(a,uid,plan(d),status='successful');r=result(d,report_data=data) with a.store.transaction()as db: small=persist(db,s,ident,plan(d),r) db.execute('UPDATE jobs SET core_result=?,finished_at=? WHERE id=?',(json.dumps(small),time.time(),ident)) paths[name]='/jobs/'+ident+'/reports';jobs[name]=ident running=job(a,uid,plan(declared()),status='running');j=Journal(s);j.begin(running) def add(start,end):j.append(running,[(*project(ev(i),{'test01.example'}),time.time())for i in range(start,end+1)]) add(1,300) paths['progress']='/jobs/'+running client=TestClient(create_app(s),base_url=url) token,_=a.new_session(uid);client.cookies.set(s.cookie_name,token);records=[] static=Path(__file__).resolve().parents[1]/'src/aim_webgui/static' def html(path): response=client.get(path);assert response.status_code==200,(path,response.text[:400]) text=response.text def css(m): name=m.group(1).split('?')[0] source=args.bootstrap if 'bootstrap.min.css' in name else static/name.removeprefix('/static/') return '' def js(m): name=m.group(1).split('?')[0] if 'htmx.min.js'in name:return '' source=static/name.removeprefix('/static/') return '' text=re.sub(r']+href="(/static/[^"]+\.css(?:\?[^"]*)?)"[^>]*>',css,text) text=re.sub(r'',js,text) return text def bridge_read(source,path): response=client.get(path) return {'status':response.status_code,'text':response.text} fixture_js=""" window.__sources=[]; window.fetch=async function(url,opts){const r=await window.fixtureRead(String(url));return {ok:r.status>=200&&r.status<300,status:r.status,json:async()=>JSON.parse(r.text),text:async()=>r.text};}; window.EventSource=class{constructor(url){this.url=url;this.listeners={};window.__sources.push(this);setTimeout(()=>this.emit('open',{}),0);}addEventListener(k,f){this.listeners[k]=f;}close(){this.closed=true;}emit(k,d){if(this.listeners[k]&&!this.closed)this.listeners[k]({data:JSON.stringify(d)});}}; window.__emit=function(kind,data){window.__sources.forEach(s=>s.emit(kind,data));}; """ def mount(page,path): content=html(path) page.set_content(content.replace('
',''),wait_until='domcontentloaded') def emit(page,start): data=j.snapshot(uid,running,after=start) page.evaluate('(d)=>window.__emit("snapshot",d)',data) for row in data['events']:page.evaluate('(r)=>window.__emit("line",{record:r,text:r.text})',row) try: with sync_playwright()as p: browser=p.chromium.launch(executable_path=os.environ.get('AIM_BROWSER_EXECUTABLE','/usr/bin/chromium'),args=['--no-sandbox']) for w,h in ((320,750),(390,844),(760,900),(1440,1000),(740,390)): for theme in ('light','dark'): ctx=browser.new_context(viewport={'width':w,'height':h},reduced_motion='reduce') ctx.add_cookies([{'name':s.cookie_name,'value':token,'url':url}]) ctx.add_init_script("localStorage.setItem('aim-web-display-theme',"+json.dumps(theme)+");") page=ctx.new_page();page.expose_binding("fixtureRead",bridge_read);errors=[];page.on('pageerror',lambda e:errors.append(str(e))) for name,path in paths.items(): print('Browser',name,w,h,theme,flush=True) mount(page,path);page.evaluate("(t)=>document.querySelectorAll('[data-theme-option]').forEach(b=>{if(b.dataset.themeOption===t)b.click()})",theme);page.wait_for_timeout(120) assert page.locator('h1').count()==1,(name,page.content()[:300]) assert page.evaluate('document.documentElement.scrollWidth<=innerWidth+1'),(name,w,h,'horizontal overflow') if name=='progress': page.wait_for_function("document.querySelector('[data-journal-output]').textContent.includes('Synthetic task 300')") box=page.locator('[data-journal-output]');height=box.evaluate('(e)=>e.clientHeight') assert height