ouroboros / source /scripts /check_showcase_motion.py
REXWind's picture
Publish the Ouroboros scientific showcase to CladeTeam
03677d0 verified
Raw History Blame Contribute Delete
15.6 kB
"""Browser regression for presentation, motion preferences and existing research controls.
Run against a built exhibition preview; this check never submits inference jobs.
PREVIEW_URL and CHROMIUM_PATH select an existing server/browser.
"""
import json
import os
from pathlib import Path
from playwright.sync_api import sync_playwright, expect
OUT = Path('artifacts/refinements-20261110')
OUT.mkdir(parents=True, exist_ok=True)
BASE = os.environ.get('PREVIEW_URL', 'http://127.0.0.1:7860')
errors = []
requests_failed = []
checks = []
def scroll(page, selector):
page.locator(selector).evaluate("e=>window.scrollTo({top:e.getBoundingClientRect().top+scrollY-100,behavior:'instant'})")
page.wait_for_timeout(350)
def no_overflow(page, label):
assert page.evaluate('document.documentElement.scrollWidth <= innerWidth'), label + ' overflow'
checks.append(label)
print('PASS:', label, flush=True)
with sync_playwright() as p:
browser = p.chromium.launch(executable_path=os.environ.get('CHROMIUM_PATH'), args=[
'--no-sandbox', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'])
context = browser.new_context(viewport={'width': 1440, 'height': 1000})
page = context.new_page()
page.on('pageerror', lambda error: errors.append(str(error)))
page.on('response', lambda response: requests_failed.append(f'{response.status} {response.url}') if response.status >= 400 else None)
inference_requests = []
page.on('request', lambda request: inference_requests.append(request.url) if request.method == 'POST' and '/api/' in request.url else None)
page.goto(BASE, wait_until='networkidle')
page.evaluate('document.fonts.ready')
expect(page.locator('[data-render-state="ready"]')).to_have_count(2, timeout=30000)
expect(page.locator('.hero-scene')).to_have_attribute('data-running', 'true')
expect(page.locator('.site-header .ouroboros-mark')).to_be_visible()
expect(page.locator('.hero-brand-lockup .ouroboros-mark')).to_be_visible()
page.wait_for_timeout(1100)
page.screenshot(path=str(OUT / 'desktop-hero.png'))
for label, direction in [('Design →', 'design'), ('← Describe', 'describe'), ('Learn together', 'joint')]:
page.get_by_role('group', name='Hero learning direction', exact=True).get_by_role('button', name=label, exact=True).click()
expect(page.locator('.hero-scene')).to_have_attribute('data-direction', direction)
# Path animation actually advances, then freezes when paused.
path = page.locator('.design-path .path-stream')
before = path.evaluate('e=>getComputedStyle(e).strokeDashoffset')
page.wait_for_timeout(200)
assert path.evaluate('e=>getComputedStyle(e).strokeDashoffset') != before
page.get_by_role('button', name='Pause page animations', exact=True).click()
expect(page.locator('html')).to_have_attribute('data-motion', 'paused')
page.wait_for_timeout(150)
frozen = path.evaluate('e=>getComputedStyle(e).strokeDashoffset')
page.wait_for_timeout(200)
after_pause = path.evaluate('e=>getComputedStyle(e).strokeDashoffset')
assert after_pause == frozen, (frozen, after_pause, path.evaluate('e=>getComputedStyle(e).animationPlayState'))
expect(page.locator('.hero-scene')).to_have_attribute('data-running', 'false')
page.reload(wait_until='networkidle')
expect(page.get_by_role('button', name='Resume page animations', exact=True)).to_be_visible()
scroll(page, '#evidence')
assert page.locator('.bar-track>span').first.evaluate('e=>getComputedStyle(e).transform') == 'none'
checks.append('Animation pause, persisted preference and visible charts while paused')
page.get_by_role('button', name='Resume page animations', exact=True).click()
scroll(page, '#dual-learning')
expect(page.locator('.hero-scene')).to_have_attribute('data-running', 'false')
expect(page.locator('nav a[href="#dual-learning"]')).to_have_attribute('aria-current', 'location')
print('PASS: global motion and hero controls', flush=True)
dual = page.locator('#dual-learning')
# Every illustration is a function of scroll position, including on rewind.
expected = json.loads(Path('public/data/core-example.json').read_text())
stage = page.locator('.story-stage')
def at(value):
page.locator('.story-scroll').evaluate("""(e,p)=>{const stage=e.querySelector('.story-stage');
const offset=parseFloat(getComputedStyle(stage).top);
window.scrollTo({top:e.getBoundingClientRect().top+scrollY-offset+(e.offsetHeight-stage.offsetHeight)*p,behavior:'instant'});
}""", value)
page.wait_for_function('(p)=>Math.abs(Number(document.querySelector(".story-stage").dataset.progress)-p)<.0002', arg=value)
page.wait_for_timeout(220)
for name, value, beat in [('start',0,0),('encoding',.09,0),('projection',.23,1),('residues',.475,2),('reverse',.665,3),('words',.815,4),('joint',1,5)]:
at(value)
expect(stage).to_have_attribute('data-beat', str(beat))
no_overflow(page, 'story ' + name)
page.screenshot(path=str(OUT / f'desktop-{name}.png'))
at(.385)
halfway = page.locator('.dual-scene-svg').inner_html()
arrived = page.locator('[data-output="protein"] [data-arrived="true"]').count()
assert 0 < arrived < 72, arrived
at(.475)
assert page.locator('[data-output="protein"] [data-arrived="true"]').count() == 72
assert page.locator('[data-output="protein"] [data-letter]').evaluate_all('els=>els.map(e=>e.dataset.letter).join("")') == expected['generatedProtein'][:72]
at(.385)
assert page.locator('.dual-scene-svg').inner_html() == halfway, 'Rewinding must reproduce the same complete SVG state'
page.wait_for_timeout(400)
assert page.locator('.dual-scene-svg').inner_html() == halfway, 'An idle scene must not animate'
assert page.locator('[data-kind="hidden-state"] text').count() == 0
assert page.locator('.ds-count').all_text_contents() == ['×24'] * 4
stage.get_by_role('button', name='Pause scene', exact=True).click()
frozen = stage.get_attribute('data-progress')
page.mouse.wheel(0, 350)
page.wait_for_timeout(300)
assert stage.get_attribute('data-progress') == frozen
# Explicit navigation is useful even while paused, and uses the same timeline.
stage.get_by_role('button', name='06 Learn together', exact=False).click()
expect(stage).to_have_attribute('data-beat', '5')
stage.get_by_role('button', name='Resume scene', exact=True).click()
at(.7)
page.get_by_role('button', name='Pause page animations', exact=True).click()
frozen = stage.get_attribute('data-progress')
page.mouse.wheel(0, 200)
page.wait_for_timeout(300)
assert stage.get_attribute('data-progress') == frozen
page.get_by_role('button', name='Resume page animations', exact=True).click()
page.get_by_role('slider', name='Explanation progress').focus()
page.keyboard.press('End')
expect(stage).to_have_attribute('data-beat', '5')
page.keyboard.press('Home')
expect(stage).to_have_attribute('data-beat', '0')
scroll(page, '#dataset')
page.wait_for_timeout(300)
frozen = stage.get_attribute('data-progress')
page.mouse.wheel(0, 180)
page.wait_for_timeout(400)
assert stage.get_attribute('data-progress') == frozen, 'Offscreen scene must stop'
at(.815)
stage.get_by_role('button', name='Read all steps', exact=False).click()
expect(dual).to_have_attribute('data-story-mode', 'reading')
assert dual.locator('.reading-step').count() == 6
dual.get_by_role('button', name='Return to scroll experience', exact=False).click()
expect(dual).to_have_attribute('data-story-mode', 'scroll')
page.locator('.story-archive summary').click()
assert page.locator('[data-archive="generated-protein"]').inner_text() == expected['generatedProtein']
assert page.locator('[data-archive="reference-protein"]').inner_text() == expected['protein']
assert page.locator('[data-archive="generated-text"]').inner_text() == expected['generatedText']
page.locator('.story-archive summary').click()
checks.append('Six scroll beats, exact rewind, idle/offscreen freeze, local/global pause, keyboard/chapters, independent archived inputs and full outputs')
scroll(page, '#dataset')
assert page.locator('.key-rail button').count() == 29
page.locator('.key-rail button').last.click()
expect(page.locator('.key-rail button').last).to_have_attribute('aria-pressed', 'true')
scroll(page, '#atlas')
expect(page.locator('#atlas .atlas-case')).to_have_count(8)
page.locator('#atlas .atlas-case').first.click()
expect(page.locator('#atlas .atlas-preview-story')).to_contain_text('SHOWCASE 01')
page.screenshot(path=str(OUT / 'desktop-atlas.png'))
scroll(page, '#petase')
petase = page.locator('#petase')
expect(page.locator('.fold-story')).to_have_attribute('data-fold-state', 'playing', timeout=15000)
slider = page.get_by_role('slider', name='Sequence to predicted structure')
slider.fill('100')
expect(slider).to_have_value('100')
assert page.locator('.fold-canvas-wrap canvas').evaluate('e=>e.width>0&&e.height>0')
expect(page.locator('.fold-story')).to_have_attribute('data-fold-state', 'paused')
page.get_by_role('button', name='Replay structure transformation').click()
expect(page.locator('.fold-story')).to_have_attribute('data-fold-state', 'playing')
slider.fill('100')
page.screenshot(path=str(OUT / 'desktop-fold.png'))
checks.append('DE2 backbone loads, plays, scrubs, pauses and replays')
assert petase.locator('.case-tabs button').count() == 10
petase.locator('.case-tabs button').nth(1).click()
expect(petase.locator('[data-render-state="ready"]')).to_have_count(1, timeout=30000)
petase.locator('.case-tabs button').first.click()
expect(petase.locator('[data-render-state="ready"]')).to_have_count(1, timeout=30000)
petase.get_by_role('button', name='Color by confidence', exact=True).click()
expect(petase.locator('.confidence-legend')).to_be_visible()
petase.locator('.residue-list button').nth(42).click()
page.keyboard.press('ArrowRight')
expect(petase.locator('.residue-list button').nth(43)).to_be_focused()
petase.get_by_role('button', name='Try this function in the lab', exact=False).click()
expect(page.locator('#model-input')).not_to_have_value('')
expect(page.locator('#playground')).to_contain_text('Original LoRA generation prompt')
assert page.locator('#playground .generation-result').count() == 0
scroll(page, '#evidence')
evidence = page.locator('#evidence')
evidence.get_by_role('button', name='Keyword recovery', exact=True).click()
expect(evidence.locator('.chart-selection')).to_contain_text('n = 476')
evidence.get_by_role('button', name='Protein → Text', exact=True).click()
evidence.get_by_role('button', name='ROUGE-L', exact=True).click()
assert evidence.locator('#benchmark-set').count() == 0
expect(evidence.locator('.benchmark-dataset')).to_contain_text('Swiss-Prot time-cutoff')
assert '100 pairs' not in evidence.inner_text()
assert 'Hard' not in evidence.inner_text()
assert evidence.locator('.bar-row').count() > 1
page.wait_for_timeout(1100)
page.screenshot(path=str(OUT / 'desktop-evidence.png'))
checks.append('29 annotations, 8 Atlas cases, PETase structure/residues, prompt reuse, benchmark filters')
no_overflow(page, '1440px desktop')
# Ensure paused preferences on the homepage do not strand the standalone Atlas.
page.get_by_role('button', name='Pause page animations', exact=True).click()
page.goto(BASE + '/atlas?id=UniRef50_A0A9J7N2Q8', wait_until='networkidle')
page.get_by_role('button', name='Resume page animations', exact=True).click()
expect(page.locator('.atlas-structure [data-render-state="ready"]')).to_have_count(1, timeout=30000)
expect(page.locator('.atlas-description')).to_be_visible()
no_overflow(page, 'Standalone Atlas')
for width in [1280, 1024, 820, 768, 390, 320]:
page.set_viewport_size({'width': width, 'height': 844})
page.goto(BASE, wait_until='networkidle')
page.wait_for_timeout(900)
no_overflow(page, f'{width}px homepage')
if width in [390, 320]:
page.screenshot(path=str(OUT / f'mobile-{width}-hero.png'))
scroll(page, '.hero-scene')
page.screenshot(path=str(OUT / f'mobile-{width}-scene.png'))
for section in ['dual-learning', 'dataset', 'atlas', 'petase', 'evidence', 'playground']:
scroll(page, '#' + section)
no_overflow(page, f'{width}px {section}')
if width == 390 and section in ['dual-learning', 'atlas', 'petase']:
page.screenshot(path=str(OUT / f'mobile-{section}.png'))
if width <= 1100:
expect(page.locator('#dual-learning')).to_have_attribute('data-story-mode', 'reading')
assert page.locator('.reading-step').count() == 6
for step in range(1, 7):
scroll(page, f'#dual-step-{step}')
no_overflow(page, f'{width}px reading step {step}')
if width == 390:
page.screenshot(path=str(OUT / f'mobile-step-{step}.png'))
if width <= 780:
page.get_by_role('button', name='Toggle navigation', exact=True).click()
expect(page.locator('#main-navigation')).to_be_visible()
page.keyboard.press('Escape')
expect(page.locator('#main-navigation')).not_to_be_visible()
expect(page.get_by_role('button', name='Toggle navigation')).to_be_focused()
page.get_by_role('button', name='Toggle navigation', exact=True).click()
page.locator('nav a[href="#dataset"]').click()
expect(page.locator('#main-navigation')).not_to_be_visible()
# System preference is respected immediately and when changed at runtime.
page.emulate_media(reduced_motion='reduce')
page.goto(BASE, wait_until='networkidle')
expect(page.locator('html')).to_have_attribute('data-motion', 'paused')
expect(page.get_by_role('button', name='Animations off: reduced motion preference')).to_be_disabled()
expect(page.locator('.hero-scene')).to_have_attribute('data-running', 'false')
assert page.locator('.path-stream').first.evaluate('e=>getComputedStyle(e).animationName') == 'none'
scroll(page, '#dual-learning')
expect(page.locator('#dual-learning')).to_have_attribute('data-story-mode', 'reading')
assert page.locator('.reading-step').count() == 6
assert page.locator('.story-stage').count() == 0
page.set_viewport_size({'width':1440,'height':1000})
expect(page.locator('#dual-learning')).to_have_attribute('data-story-mode', 'reading')
scroll(page, '#evidence')
assert page.locator('.bar-track>span').first.evaluate('e=>getComputedStyle(e).transform') == 'none'
page.emulate_media(reduced_motion='no-preference')
expect(page.get_by_role('button', name='Pause page animations')).to_be_enabled()
checks.append('Reduced motion at load and runtime; all outputs and charts remain readable')
assert not errors, errors
assert not requests_failed, requests_failed
assert not inference_requests, inference_requests
browser.close()
report = {'checks': checks, 'page_errors': errors, 'http_errors': requests_failed,
'inference_jobs_submitted': len(inference_requests)}
(OUT / 'browser-report.json').write_text(json.dumps(report, indent=2) + '\n')
print(json.dumps(report, indent=2))