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});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")') assert page.evaluate('theme')=='dark' # Empty root has neither text nor a hit target. root=page.evaluate('''()=>{let texts=[];const original=ctx.fillText;ctx.fillText=function(t,x,y){if(Math.hypot(x-W/2,y-H/2)<30)texts.push(t);return original.apply(this,arguments)};draw();ctx.fillText=original;return {texts,hit:nearest(W/2,H/2)}}''') assert root=={'texts':[],'hit':None},root page.locator('#detail').click();page.locator('#sel-close').click() box=page.locator('#stage').bounding_box();cx=box['x']+box['width']/2;cy=box['y']+box['height']/2 page.mouse.move(cx,cy);page.wait_for_function('selected && selected.accession==="GCA_000001405.29"') assert page.locator('#selection').is_visible() assert page.evaluate('pinnedAssembly===null') assert page.locator('#sel-state').text_content().startswith('Hover preview') page.screenshot(path=str(R/'hover-detail.png')) # Hover preview clears on leaving the chart; clicking then pins the same record. page.mouse.move(100,100);assert page.locator('#selection').is_hidden() page.mouse.move(cx,cy);page.wait_for_function('selected!==null');page.mouse.click(cx,cy) assert page.locator('#sel-state').text_content().startswith('Frozen') page.mouse.move(100,100);assert page.locator('#selection').is_visible() assert page.locator('#sel-id').inner_text()=='GCA_000001405.29' # Hover another endpoint while keeping the frozen selection unchanged. q=page.evaluate('''()=>{const a=leaves[leaves.findIndex(a=>a.accession==='GCA_000001405.29')+12],q=screen(a.tip),r=stage.getBoundingClientRect();return {x:r.x+q[0],y:r.y+q[1],id:a.accession}}''') page.mouse.move(q['x'],q['y']);page.wait_for_timeout(100) assert page.locator('#sel-id').inner_text()=='GCA_000001405.29' page.mouse.move(100,100);assert page.locator('#sel-id').inner_text()=='GCA_000001405.29' # Both renderers and UI change theme, and the preference survives a reload. page.locator('#reset').click();page.locator('#theme-toggle').click();page.wait_for_function('theme==="light"') page.wait_for_timeout(150) assert page.locator('body').evaluate('e=>getComputedStyle(e).backgroundColor')=='rgb(255, 255, 255)' pixel=page.evaluate('''()=>{const p=new Uint8Array(4);gl.readPixels(0,0,1,1,gl.RGBA,gl.UNSIGNED_BYTE,p);return [...p]}''') assert pixel==[255,255,255,255],pixel page.screenshot(path=str(R/'light-overview.png')) page.reload();page.wait_for_function('window.POC && !document.querySelector("#loading")');assert page.evaluate('theme')=='light' page.locator('#theme-toggle').click();page.wait_for_timeout(150) pixel=page.evaluate('''()=>{const p=new Uint8Array(4);gl.readPixels(0,0,1,1,gl.RGBA,gl.UNSIGNED_BYTE,p);return [...p]}''') assert pixel==[8,14,19,255],pixel # Fine bar continuity: sum local alpha across successive slices through a # subpixel-wide diagonal bar. This catches the broken slivers from Canvas arcs. continuity=page.evaluate('''()=>{ zoom=4;const a=-2.38,r0=754,r1=928,s=baseScale*zoom,m=polar((r0+r1)/2,a);pan={x:-m[0]*s,y:-m[1]*s}; ctx.setTransform(1,0,0,1,0,0);ctx.clearRect(0,0,overlay.width,overlay.height);wedge(a-.00013,a+.00013,r0,r1,'#ffffff'); const data=ctx.getImageData(0,0,overlay.width,overlay.height).data;let empty=0,min=Infinity,n=0; for(let r=r0+4/s;r50 print('hover, pin, empty root, theme persistence and bar continuity passed',continuity,flush=True) assert not errors,errors b.close()