"""Targeted scroll-scene layout, rewind, and visibility-state regression.""" from playwright.sync_api import sync_playwright,expect from pathlib import Path import os,json out=Path('artifacts/scroll-story-20261110') with sync_playwright() as p: b=p.chromium.launch(executable_path=os.environ.get('CHROMIUM_PATH'),args=['--no-sandbox','--use-gl=angle','--use-angle=swiftshader','--enable-unsafe-swiftshader']) page=b.new_page(viewport={'width':1280,'height':720}) errors=[] page.on('pageerror',lambda e:errors.append(str(e))) page.goto(os.environ.get('PREVIEW_URL','http://127.0.0.1:7860'),wait_until='networkidle') def at(p): page.locator('.story-scroll').evaluate('(e,p)=>{const s=e.querySelector(".story-stage");window.scrollTo({top:scrollY+e.getBoundingClientRect().top-82+(e.offsetHeight-s.offsetHeight)*p,behavior:"instant"})}',p) page.wait_for_timeout(1100) at(.23) expect(page.locator('.story-stage')).to_have_attribute('data-beat','1') # Moving packets stay clear of the projection title/equation band. at(.385);frame=page.locator('.dual-scene-svg').inner_html() at(.96);at(.385) assert page.locator('.dual-scene-svg').inner_html()==frame at(1) page.screenshot(path=str(out/'desktop-1280x720-joint.png')) assert page.locator('.story-scrubber').bounding_box()['y'] < 720 svg=page.locator('.dual-scene-svg').bounding_box() chapters=page.locator('.story-chapters').bounding_box() assert svg['y']+svg['height'] <= chapters['y'], (svg, chapters) assert page.locator('.ds-objective').bounding_box()['y'] < chapters['y'] page.locator('.story-chapters button').first.click() expect(page.locator('.story-stage')).to_have_attribute('data-beat','0') at(1) assert page.evaluate('document.documentElement.scrollWidth<=innerWidth') page.set_viewport_size({'width':1920,'height':1080});at(.475) page.screenshot(path=str(out/'desktop-1920-residues.png')) assert page.evaluate('document.documentElement.scrollWidth<=innerWidth') page.set_viewport_size({'width':1440,'height':620}) expect(page.locator('#dual-learning')).to_have_attribute('data-story-mode','reading') page.set_viewport_size({'width':1440,'height':1000}) expect(page.locator('#dual-learning')).to_have_attribute('data-story-mode','scroll') # Runtime reduction yields all steps, and is reversible. page.emulate_media(reduced_motion='reduce') expect(page.locator('.reading-step')).to_have_count(6) page.emulate_media(reduced_motion='no-preference') at(.23) # Dispatch the browser visibility event with its hidden state to exercise cleanup. page.evaluate('Object.defineProperty(document,"hidden",{configurable:true,get:()=>true});document.dispatchEvent(new Event("visibilitychange"))') page.wait_for_timeout(100) frozen=page.locator('.story-stage').get_attribute('data-progress') page.mouse.wheel(0,250);page.wait_for_timeout(400) assert page.locator('.story-stage').get_attribute('data-progress')==frozen page.evaluate('delete document.hidden;document.dispatchEvent(new Event("visibilitychange"))') page.wait_for_timeout(1200) assert page.locator('.story-stage').get_attribute('data-progress')!=frozen assert not errors,errors b.close() (out/'final-layout-report.json').write_text(json.dumps({'checks':['1280x720 complete sticky stage','1920x1080 display','short viewport reading fallback','rewind after final projection-layout change','runtime reduced-motion switching','simulated hidden-tab visibility event freeze and resume'],'errors':errors},indent=2)) print('Final layout and timeline checks passed')