reprochain-ai / test_browser.py
Hugdrigo's picture
Upload 35 files
c04328a verified
Raw History Blame Contribute Delete
8.54 kB
import asyncio, pathlib, json
from playwright.async_api import async_playwright
ROOT=pathlib.Path(__file__).resolve().parent
OUT=pathlib.Path('/mnt/data')
async def main():
async with async_playwright() as p:
browser=await p.chromium.launch(headless=True, executable_path='/usr/bin/chromium', args=['--no-sandbox','--disable-dev-shm-usage'])
ctx=await browser.new_context(viewport={'width':1512,'height':980}, accept_downloads=True)
page=await ctx.new_page();errors=[];page.on('pageerror', lambda e:errors.append(str(e)))
import base64,hashlib
await page.set_content((ROOT/'index.html').read_text(),wait_until='domcontentloaded')
await page.add_style_tag(content=(ROOT/'styles.css').read_text())
await page.expose_function('__shahex',lambda b64:hashlib.sha256(base64.b64decode(b64)).hexdigest())
await page.evaluate('''() => {
Object.defineProperty(window.crypto,'subtle',{configurable:true,value:{digest:async (_alg,buffer)=>{
let arr=new Uint8Array(buffer), bin='';
for(let i=0;i<arr.length;i+=8192)bin+=String.fromCharCode(...arr.subarray(i,i+8192));
const hex=await window.__shahex(btoa(bin));
return Uint8Array.from(hex.match(/../g),x=>parseInt(x,16)).buffer;
}}});
}''')
assets={f.name:base64.b64encode(f.read_bytes()).decode() for f in ROOT.iterdir() if f.is_file() and f.suffix in ('.json','.js','.css','.html','.md')}
await page.evaluate('''(assets)=>{
window.__ASSETS=assets;
window.fetch=async uri=>{
const id=String(uri).split('/').pop().split('?')[0],base=window.__ASSETS[id];
if(!base)return {ok:false,status:404};
const bin=atob(base),arr=Uint8Array.from(bin,c=>c.charCodeAt(0));
return {ok:true,status:200,arrayBuffer:async()=>arr.buffer,json:async()=>JSON.parse(new TextDecoder().decode(arr))};
};
}''',assets)
await page.add_script_tag(content=(ROOT/'evalflow_engine.js').read_text())
await page.add_script_tag(content=(ROOT/'reprochain_engine.js').read_text())
await page.add_script_tag(content=(ROOT/'app.js').read_text())
await page.wait_for_function("document.querySelector('#statusBadge').textContent.includes('PASS')",timeout=45000)
checks=[]
def ck(name,condition):
if not condition: raise AssertionError(name+' errors='+repr(errors))
checks.append(name);print('PASS',name)
ck('hero ReproChain AI', 'ReproChain' in await page.locator('h1').inner_text())
ck('22 hashed artifact nodes',await page.locator('#kArtifact').inner_text()=='22')
ck('4416 prediction records',await page.locator('#kRecords').inner_text()=='4,416')
ck('manifest root verified',await page.locator('#kRoot').inner_text()=='VALID')
ck('default research gate PASS',await page.locator('#statusBadge').inner_text()=='RESEARCH GATES PASS')
ck('candidate 77.8% Accuracy', '77.8%' in await page.locator('#content').inner_text())
ck('nine navigation modules',await page.locator('.nav-btn').count()==9)
await page.locator('[data-view=lineage]').click()
ck('lineage artifact nodes shown',await page.locator('[data-node]').count()==22)
await page.locator('[data-node=predictions]').click()
ck('predictions upstream text', 'train' in (await page.locator('#content').inner_text()).lower())
ck('sha prefix shown', 'SHA-256' in await page.locator('#content').inner_text())
async with page.expect_download() as d: await page.locator('[data-export=graph]').click()
download=await d.value; ck('graphml export',download.suggested_filename.endswith('.graphml'))
await page.locator('[data-view=vault]').click()
ck('vault 22 verified', '22' in await page.locator('#content').inner_text())
await page.locator('#tamper').click()
ck('simulated tamper BLOCK',await page.locator('#statusBadge').inner_text()=='RESEARCH GATES BLOCK')
ck('synthetic tamper warning', 'SIMULATION ONLY' in await page.locator('#content').inner_text())
await page.locator('#tamper').click()
ck('recover gate after simulation',await page.locator('#statusBadge').inner_text()=='RESEARCH GATES PASS')
await page.locator('#rescan').click()
await page.wait_for_function("document.querySelector('#statusLine').textContent.startsWith('22/22')")
ck('rescan matches 22 hashes',(await page.locator('#statusLine').inner_text()).startswith('22/22'))
await page.locator('#baseline').select_option('hybrid')
await page.locator('#candidate').select_option('nb')
await page.locator('#replay').click()
await page.wait_for_function("document.querySelector('#statusBadge').textContent.includes('BLOCK')")
ck('reversed classifier comparison BLOCK',await page.locator('#statusBadge').inner_text()=='RESEARCH GATES BLOCK')
await page.locator('#baseline').select_option('nb')
await page.locator('#candidate').select_option('hybrid')
await page.locator('#replay').click()
await page.wait_for_function("document.querySelector('#statusBadge').textContent.includes('PASS')")
ck('restore candidate PASS',await page.locator('#statusBadge').inner_text()=='RESEARCH GATES PASS')
await page.locator('[data-view=governance]').click()
ck('editable policy eight entries',await page.locator('[id^=p_]').count()==8)
await page.locator('#p_minAccuracy').fill('0.95')
await page.locator('#apply').click()
await page.wait_for_function("document.querySelector('#statusBadge').textContent.includes('BLOCK')")
ck('strict policy BLOCK',await page.locator('#statusBadge').inner_text()=='RESEARCH GATES BLOCK')
await page.locator('#defaults').click()
await page.wait_for_function("document.querySelector('#statusBadge').textContent.includes('PASS')")
ck('policy reset restores PASS',await page.locator('#statusBadge').inner_text()=='RESEARCH GATES PASS')
await page.locator('[data-view=replay]').click()
ck('paired bootstrap interval displayed','bootstrap' in (await page.locator('#content').inner_text()).lower())
await page.locator('[data-view=runs]').click()
ck('run registry contains >= 4 runs',await page.locator('table tbody tr').count()>=4)
await page.locator('#regA').select_option('1')
await page.locator('#regB').select_option('3')
ck('run selector compares snapshots', 'Difference (B' in await page.locator('#content').inner_text())
await page.locator('[data-view=audit]').click()
await page.locator('#auditVerify').click()
await page.wait_for_function("document.querySelector('#auditMessage').textContent.startsWith('PASS:') || document.querySelector('#auditMessage').textContent.startsWith('FAILED:')")
ck('audit chain verify successful','PASS:' in await page.locator('#auditMessage').inner_text())
await page.locator('[data-view=export]').click()
for kind,ext in [('report','.md'),('run','.json'),('inventory','.csv'),('runs','.csv'),('graph','.graphml'),('bundle','.json'),('manifest','.json')]:
async with page.expect_download() as d:await page.locator(f'[data-export={kind}]').click()
item=await d.value;ck('export '+kind,item.suggested_filename.endswith(ext))
snap={'schema':'reprochain.run.v1','baseline':'nb','candidate':'hybrid','split':'challenge','accuracy':.7777,'bundleRoot':'f'*64}
await page.locator('#importRun').set_input_files({'name':'test-run.json','mimeType':'application/json','buffer':json.dumps(snap).encode()})
await page.wait_for_function("document.querySelector('#importMsg').textContent.includes('UNVERIFIED')")
ck('import unverified run warning', 'UNVERIFIED' in await page.locator('#importMsg').inner_text())
await page.locator('[data-view=runs]').click()
ck('import run shown unverified', 'UNVERIFIED' in await page.locator('#content').inner_text())
await page.locator('[data-view=methods]').click()
ck('limitations visible','not a trusted signature' in (await page.locator('#content').inner_text()).lower())
await page.locator('[data-view=overview]').click()
await page.evaluate('window.scrollTo(0,0)')
await page.screenshot(path=str(OUT/'ReproChain_AI_DESKTOP_PREVIEW.png'),full_page=True)
ck('desktop no sideways scroll',await page.evaluate('document.documentElement.scrollWidth<=window.innerWidth+3'))
await page.set_viewport_size({'width':390,'height':844});await page.evaluate('window.scrollTo(0,0)');await page.screenshot(path=str(OUT/'ReproChain_AI_MOBILE_PREVIEW.png'),full_page=True)
ck('mobile no sideways scroll',await page.evaluate('document.documentElement.scrollWidth<=window.innerWidth+3'))
ck('mobile navigation accessible',await page.locator('[data-view=export]').count()==1)
ck('no browser JavaScript errors',len(errors)==0)
print('BROWSER CHECKS PASSED',len(checks));await browser.close()
asyncio.run(main())