File size: 15,645 Bytes
03677d0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 | """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))
|