from pathlib import Path import sys,json sys.path.insert(0,'/home/node/local/carbon-a-render/python') from playwright.sync_api import sync_playwright R=Path(__file__).parent with sync_playwright() as p: b=p.chromium.launch(executable_path='/opt/pw-browsers/chromium_headless_shell-1234/chrome-headless-shell-linux64/chrome-headless-shell',args=['--no-sandbox','--enable-webgl','--use-gl=angle','--use-angle=swiftshader','--enable-unsafe-swiftshader']) page=b.new_page(viewport={'width':1500,'height':1050},has_touch=True);errors=[];page.on('pageerror',lambda e:errors.append(str(e))) page.goto((R/'index.html').as_uri());page.wait_for_function('window.POC && !document.querySelector("#loading")') def point(i,r=62): return page.evaluate('''([i,radius])=>{const a=leaves[i],p=screen(polar(radius,a.angle)),box=stage.getBoundingClientRect();return {x:box.x+p[0],y:box.y+p[1],id:a.accession}}''',[i,r]) # Hit the same exact assembly from different radii inside all four dial sectors. targets=[1000,11000,21500,32000] for i in targets: for radius in [35,78]: q=point(i,radius);page.mouse.move(q['x'],q['y']);page.wait_for_function('(i)=>selected?.i===i',arg=i) assert page.locator('#selection').is_visible() assert page.locator('#sel-id').inner_text()==q['id'] assert page.locator('#tree').evaluate('e=>getComputedStyle(e).cursor')=='pointer' assert page.evaluate('pinnedAssembly===null') q=point(11000);page.mouse.click(q['x'],q['y']);page.mouse.move(100,100) assert page.evaluate('pinnedAssembly.i')==11000 assert page.locator('#selection').is_visible() # A dial drag changes angle/selection while preserving the camera, even if it # leaves the dial. Releasing keeps the chosen assembly pinned. page.mouse.click(100,130) # release the earlier frozen selection a=point(1000);z=point(21500,112);camera=page.evaluate('({zoom,pan:{...pan}})') page.mouse.move(a['x'],a['y']);page.mouse.down();page.mouse.move(z['x'],z['y'],steps=8);page.mouse.up() assert page.evaluate('pinnedAssembly.i')==21500 assert page.evaluate('({zoom,pan})')==camera page.screenshot(path=str(R/'dial-selection.png')) # The mapping stays in tree coordinates under zoom and pan. page.evaluate('''()=>{clearSelection();zoom=2.5;pan={x:-80,y:65};draw()}''') q=point(32000);page.mouse.move(q['x'],q['y']);page.wait_for_function('selected?.i===32000') page.mouse.click(q['x'],q['y']);assert page.evaluate('pinnedAssembly.i')==32000 # Touch taps can select through the centre as well. page.locator('#reset').click();page.locator('#theme-toggle').click();q=point(7000) page.touchscreen.tap(q['x'],q['y']);assert page.evaluate('pinnedAssembly.i')==7000 # The top seam resolves to a real neighbouring strand; the pivot stays stable. seam=page.evaluate('''()=>{const right=screen(polar(60,NOON+.01)),left=screen(polar(60,NOON-.01)),centre=screen([0,0]);return [hitDial(...right)?.i,hitDial(...left)?.i,hitDial(...centre)]}''') assert seam==[0,33587,None],seam # Taking over with the dial stops playback and keeps the partial sweep totals. page.locator('#animate-sweep').click();page.wait_for_function('sweep.count>100') q=point(17000);page.mouse.click(q['x'],q['y']);assert not page.evaluate('sweep.running');assert page.evaluate('pinnedAssembly.i')==17000 before=page.evaluate('sweep.count');page.wait_for_timeout(150);assert page.evaluate('sweep.count')==before assert page.evaluate('SWEEP_DURATION')==4000 assert not errors,errors print(json.dumps({'dial_hover':'8 positions passed','pin':'passed','dial_drag_without_pan':'passed','zoom_and_pan':'passed','touch':'passed','top_seam':seam,'sweep_takeover':'passed','errors':errors}),flush=True) b.close()