Spaces:
Running
Running
Download test_browser.py from Hugdrigo/impactlab-latam: direct link, hf CLI and curl.
- Browser
- Download file 7.62 kB
-
https://huggingface.co/spaces/Hugdrigo/impactlab-latam/resolve/main/test_browser.py
- Command line
-
hf download hf://spaces/Hugdrigo/impactlab-latam/test_browser.py
-
curl -L -o test_browser.py https://huggingface.co/spaces/Hugdrigo/impactlab-latam/resolve/main/test_browser.py
7.62 kB
| """Browser smoke checks. Run with `python test_browser.py` and installed Chromium + playwright. | |
| Loads the current source files directly into Chromium; no HTTP server is needed. | |
| Uses system Chromium or an installed Playwright Chromium. | |
| """ | |
| from playwright.sync_api import sync_playwright | |
| import os,json,re,shutil | |
| from pathlib import Path | |
| CHROME=os.getenv('CHROMIUM_BINARY') or shutil.which('chromium') or shutil.which('google-chrome') | |
| checks=[] | |
| def assertion(cond): | |
| assert cond,'assertion failed' | |
| def check(name, fn): | |
| try:fn();checks.append((name,True)) | |
| except Exception as e:checks.append((name,False,str(e)));print('FAIL',name, str(e)[:220]);raise | |
| with sync_playwright() as p: | |
| browser=p.chromium.launch(**({'executable_path':CHROME} if CHROME else {}),headless=True,args=['--no-sandbox','--disable-dev-shm-usage']) | |
| context=browser.new_context(viewport={'width':1480,'height':1100},accept_downloads=True) | |
| page=context.new_page();errors=[] | |
| page.on('pageerror',lambda err:errors.append(str(err))) | |
| base=Path(__file__).resolve().parent | |
| html=(base/'index.html').read_text() | |
| html=re.sub(r'<link rel="stylesheet" href="styles.css"/>', '<style>'+ (base/'styles.css').read_text() + '</style>',html) | |
| html=re.sub(r'<script src="[^"]+"></script>', '', html) | |
| page.set_content(html,wait_until='load') | |
| for script in ('engine.js','demo-data.js','app.js'):page.add_script_tag(content=(base/script).read_text()) | |
| check('Title and layout',lambda: (assertion(page.title().startswith('ImpactLab')),assertion(page.locator('#indicator option').count()==8))) | |
| check('Synthetic source visible',lambda:assertion('SYNTHETIC DEMO' in page.locator('#sourceBanner').inner_text())) | |
| check('Overview metrics rendered',lambda:assertion('DiD contrast' in page.locator('#view').inner_text())) | |
| check('Default DiD is calculated',lambda:assertion(page.evaluate('window.__impactState.did.ok') is True)) | |
| check('Synthetic control has convex weights',lambda:assertion(page.evaluate('window.__impactState.synth.ok') is True)) | |
| check('Demo 3000 rows',lambda:assertion(page.evaluate('window.__impactState.rows.length')==3000)) | |
| page.locator('[data-tab="trends"]').click() | |
| check('Trends tab renders chart',lambda:assertion(page.locator('#view svg').count()>=1)) | |
| page.locator('[data-tab="did"]').click() | |
| check('DiD tab renders interval',lambda:assertion('Year-resampling range' in page.locator('#view').inner_text())) | |
| page.locator('[data-tab="synthetic"]').click() | |
| check('Synthetic tab visualizes donors',lambda:assertion('Donor weights' in page.locator('#view').inner_text())) | |
| page.locator('[data-tab="placebos"]').click() | |
| check('Placebo analyses show diagnostics',lambda:assertion('unit placebos' in page.locator('#view').inner_text())) | |
| page.locator('[data-tab="quality"]').click() | |
| check('Coverage audit reports countries',lambda:assertion('Panel completeness' in page.locator('#view').inner_text())) | |
| page.locator('[data-tab="protocol"]').click() | |
| page.locator('#policyName').fill('Hypothetical education reform (not verified)') | |
| page.locator('#policyUrl').fill('https://example.org/report') | |
| page.locator('#researchNotes').fill('This is an exploratory research note. No causal claim.') | |
| page.locator('[data-tab="export"]').click() | |
| check('Research report contains notes and provenance',lambda:assertion('This is an exploratory research note.' in page.locator('#reportPreview').inner_text() and 'SYNTHETIC DEMO' in page.locator('#reportPreview').inner_text())) | |
| with page.expect_download() as dl:page.locator('#exportCsv').click() | |
| check('CSV downloads',lambda:assertion(dl.value.suggested_filename.endswith('.csv'))) | |
| with page.expect_download() as dl2:page.locator('#exportMd').click() | |
| check('Markdown downloads',lambda:assertion(dl2.value.suggested_filename.endswith('.md'))) | |
| with page.expect_download() as dl3:page.locator('#exportJson').click() | |
| sess=json.loads(dl3.value.path().read_text()) | |
| check('JSON session saves exact provenance',lambda:assertion(sess['source']=='demo' and sess['notes'].startswith('This is an exploratory') and len(sess['rows'])==3000)) | |
| page.locator('#startYear').fill('2018');page.locator('#runBtn').click() | |
| check('Invalid short design yields visible warning',lambda:assertion(not page.locator('#errorPanel').is_hidden())) | |
| page.locator('#startYear').fill('2003');page.locator('#runBtn').click() | |
| check('Warning clears after valid design',lambda:assertion(page.locator('#errorPanel').is_hidden())) | |
| # Fake network errors must preserve earlier results, not mislabel as live. | |
| page.route('https://api.worldbank.org/**',lambda route:route.fulfill(status=503,body='unavailable')) | |
| page.locator('#liveBtn').click();page.wait_for_timeout(500) | |
| check('Live fetch failure preserves synthetic data',lambda:assertion(page.evaluate('window.__impactState.source')=='demo' and not page.locator('#errorPanel').is_hidden())) | |
| page.unroute('https://api.worldbank.org/**') | |
| def fake_response(route): | |
| u=route.request.url | |
| data=[{'countryiso3code':code,'date':str(y),'value':70+(i%4)+.1*(y-2000)} for i,code in enumerate(['CHL','ARG','PER','URY']) for y in range(2000,2025)] | |
| route.fulfill(status=200,headers={'Access-Control-Allow-Origin':'*','Content-Type':'application/json'},body=json.dumps([{'page':1,'pages':1,'total':len(data)},data])) | |
| page.route('https://api.worldbank.org/**',fake_response) | |
| page.locator('#liveBtn').click();page.wait_for_timeout(750) | |
| check('World Bank response adapter switches source to LIVE',lambda:assertion(page.evaluate('window.__impactState.source')=='worldbank')) | |
| check('Loaded record count matches mock response',lambda:assertion(page.evaluate('window.__impactState.rows.length')==100)) | |
| check('UI labels live data',lambda:assertion('WORLD BANK LIVE' in page.locator('#sourceBanner').inner_text())) | |
| page.locator('#demoBtn').click() | |
| check('Demo restores previous defaults',lambda:assertion(page.evaluate('window.__impactState.source')=='demo' and page.evaluate('window.__impactState.rows.length')==3000)) | |
| # Import JSON must be flagged imported; do not automatically trust source. | |
| f=page.locator('[data-tab="export"]');f.click();page.locator('#importJson').set_input_files({'name':'session.json','mimeType':'application/json','buffer':json.dumps(sess).encode()});page.wait_for_timeout(150) | |
| check('Session restores as UNVERIFIED IMPORTED',lambda:assertion(page.evaluate('window.__impactState.source')=='imported')) | |
| # malicious URL should never become link | |
| page.locator('#policyUrl').fill('javascript:alert(1)');page.locator('[data-tab="protocol"]').click() | |
| check('Unsafe URL excluded',lambda:assertion('javascript:alert' not in page.locator('#view').inner_html())) | |
| page.locator('#demoBtn').click() | |
| # Desktop and mobile screenshots | |
| page.screenshot(path=str(base/'ImpactLab_LATAM_DESKTOP_PREVIEW.png'),full_page=True) | |
| mobile=browser.new_context(viewport={'width':390,'height':844},device_scale_factor=1,accept_downloads=True) | |
| mob=mobile.new_page();mob.set_content(html,wait_until='load');[mob.add_script_tag(content=(base/script).read_text()) for script in ('engine.js','demo-data.js','app.js')] | |
| check('Mobile interface renders',lambda:assertion(mob.locator('#view .metric').count()>=4)) | |
| check('Mobile layout has no horizontal overflow',lambda:assertion(mob.evaluate('document.documentElement.scrollWidth <= innerWidth+2'))) | |
| mob.screenshot(path=str(base/'ImpactLab_LATAM_MOBILE_PREVIEW.png'),full_page=True) | |
| check('Desktop runtime errors zero',lambda:assertion(len(errors)==0)) | |
| browser.close() | |
| for item in checks:print('PASS' if item[1] else 'FAIL',item[0]) | |
| print('TOTAL',len(checks),'PASS',sum(1 for x in checks if x[1]),'FAIL',sum(1 for x in checks if not x[1])) | |