import asyncio, json, pathlib, csv from playwright.async_api import async_playwright ROOT=pathlib.Path(__file__).parent async def main(): async with async_playwright() as p: browser=await p.chromium.launch(headless=True,executable_path='/usr/bin/chromium',args=['--no-sandbox']) page=await browser.new_page(viewport={'width':1512,'height':1060},accept_downloads=True) errors=[] page.on('pageerror', lambda exc: errors.append(str(exc))) source=(ROOT/'index.html').read_text() # Set content, then add source scripts, intercept JSON responses using a fetch shim. await page.set_content(source, wait_until='domcontentloaded') await page.add_style_tag(content=(ROOT/'styles.css').read_text()) for script in (ROOT/'engine.js',): await page.add_script_tag(content=script.read_text()) source_json=(ROOT/'evaluation_snapshots.json').read_text() manifest=(ROOT/'benchmark_manifest.json').read_text() await page.evaluate('''({data,manifest}) => { const f = window.fetch; window.fetch = async (url) => { if(String(url).includes('evaluation_snapshots.json')) return {ok:true,json:async()=>JSON.parse(data)}; if(String(url).includes('benchmark_manifest.json')) return {ok:true,json:async()=>JSON.parse(manifest)}; return f(url); }}''',{'data':source_json,'manifest':manifest}) await page.add_script_tag(content=(ROOT/'app.js').read_text()) await page.locator('.metric').first.wait_for(timeout=10000) checks=[] async def ck(label,cond): if not cond: raise AssertionError(label) checks.append(label) print('PASS',label) await ck('initial overview page', await page.locator('h2').first.inner_text()=='Evaluation overview') await ck('736 prediction records', '736' in await page.locator('#heroN').inner_text()) await ck('initial gate warning visible', 'release check' in (await page.locator('#panel').inner_text()).lower()) await ck('hybrid initial metrics 77.8', '77.8%' in await page.locator('#panel').inner_text()) await page.locator('#methodSelect').select_option('nb') await ck('model selector updates to naive bayes', '74.1%' in await page.locator('#panel').inner_text()) await page.locator('#splitSelect').select_option('iid') await ck('split selector 100 percent baseline', '100.0%' in await page.locator('#panel').inner_text()) await page.locator('[data-view="drift"]').click() await ck('drift section shows PSI and JSD', 'Text-length PSI' in await page.locator('#panel').inner_text() and 'Language JSD' in await page.locator('#panel').inner_text()) await page.locator('[data-view="slices"]').click() await ck('slice view has language controls', await page.locator('#sliceBy').count()==1) await page.locator('#sliceBy').select_option('truth') await ck('slice view label option', 'Six rhetorical classes' not in await page.locator('#panel').inner_text() and 'background' in await page.locator('#panel').inner_text()) await page.locator('[data-view="errors"]').click() await ck('confusion matrix visible', 'CONFUSION MATRIX' in await page.locator('#panel').inner_text()) await page.locator('#splitSelect').select_option('challenge') await ck('challenge examples show errors', await page.locator('span.tag.bad').count()>0) await page.locator('#errorBy').select_option('limitation') await ck('error filter changes view', 'Misclassifications' in await page.locator('#panel').inner_text()) await page.locator('[data-view="regression"]').click() await ck('paired model compare n54', 'Matched example IDs' in await page.locator('#panel').inner_text()) await page.locator('#compareMethod').select_option('word') await ck('paired method updates', 'Word TF-IDF' in await page.locator('#panel').inner_text()) await page.locator('[data-view="gates"]').click() await ck('default release gates fail', 'CONFIGURED CHECKS FAIL' in await page.locator('#panel').inner_text()) await page.locator('#tPSI').fill('1.0') await page.locator('#tGap').fill('30') await page.locator('#tAccuracy').fill('70') await page.locator('#applyGates').click() await ck('thresholds change gate view without modifying data', 'CONFIGURED CHECKS PASS' in await page.locator('#panel').inner_text()) await page.locator('#resetGates').click() await ck('reset restores research defaults', 'CONFIGURED CHECKS FAIL' in await page.locator('#panel').inner_text()) await page.locator('[data-view="registry"]').click() await ck('registry lists SHA256', 'evaluationSha256' in await page.locator('#panel').inner_text()) await ck('registry shows lineage', 'Academic Rhetorical Intent Mini' in await page.locator('#panel').inner_text()) await page.locator('[data-view="export"]').click() async with page.expect_download() as d: await page.locator('[data-download="rows"]').click() dl=await d.value await ck('CSV export generated',dl.suggested_filename.endswith('.csv')) async with page.expect_download() as d: await page.locator('[data-download="session"]').click() dl=await d.value await ck('full JSON session downloaded',dl.suggested_filename.endswith('.json')) async with page.expect_download() as d: await page.locator('[data-download="report"]').click() dl=await d.value await ck('Markdown audit report downloaded',dl.suggested_filename.endswith('.md')) async with page.expect_download() as d: await page.locator('[data-download="template"]').click() dl=await d.value await ck('CSV import template downloaded', 'template' in dl.suggested_filename) await page.locator('#inputFile').set_input_files(files={'name':'custom.json','mimeType':'application/json','buffer':json.dumps({'rows':[dict(id='custom1',split='challenge',method='nb',language='en',domain='test',truth='background',pred='background',tokenCount=4)]}).encode()}) await page.locator('#importBtn').click() await page.wait_for_timeout(150);await ck('user import applied', '1' in await page.locator('#heroN').inner_text() and 'Imported 1 rows' in await page.locator('#notice').inner_text()) await page.locator('[data-view="export"]').click() await page.locator('#restoreBtn').click() await ck('restores original dataset', '736' in await page.locator('#heroN').inner_text()) await page.locator('#methodSelect').select_option('hybrid') await page.locator('#notice').evaluate('(el)=>el.hidden=true') await page.set_viewport_size({'width':390,'height':844}) await page.screenshot(path='/mnt/data/ModelGuard_Observatory_MOBILE_PREVIEW.png',full_page=True) await ck('mobile viewport does not overflow', await page.evaluate('document.documentElement.scrollWidth <= window.innerWidth + 3')) await ck('navigation persists on mobile', await page.locator('[data-view="drift"]').is_visible()) await page.set_viewport_size({'width':1512,'height':1050}) await page.locator('[data-view="overview"]').click() await page.screenshot(path='/mnt/data/ModelGuard_Observatory_DESKTOP_PREVIEW.png',full_page=True) await ck('no browser JavaScript errors', not errors) print('BROWSER TESTS',len(checks),'PASS') await browser.close() asyncio.run(main())