Download source/scripts/check_showcase_motion.py from CladeTeam/ouroboros: direct link, hf CLI and curl.
- Browser
- Download file 15.6 kB
-
https://huggingface.co/spaces/CladeTeam/ouroboros/resolve/main/source/scripts/check_showcase_motion.py
- Command line
-
hf download hf://spaces/CladeTeam/ouroboros/source/scripts/check_showcase_motion.py
-
curl -L -o check_showcase_motion.py https://huggingface.co/spaces/CladeTeam/ouroboros/resolve/main/source/scripts/check_showcase_motion.py
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)) | |