Spaces:
Running
Running
File size: 13,281 Bytes
e5675eb b3a9cc3 e5675eb 62181a5 e5675eb b3a9cc3 b084019 b3a9cc3 e5675eb c3828f6 fbc4cf6 c3828f6 fbc4cf6 c3828f6 fbc4cf6 c3828f6 fbc4cf6 e5675eb fbc4cf6 e5675eb fbc4cf6 e5675eb fbc4cf6 e5675eb fbc4cf6 e5675eb fbc4cf6 e5675eb fbc4cf6 e5675eb c3828f6 e5675eb fbc4cf6 e5675eb b3a9cc3 b084019 b3a9cc3 e5675eb b3a9cc3 fbc4cf6 e34c5a8 e5675eb c3828f6 e5675eb | 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 | """Browser verification for the presentation layout and counter-free sweep."""
from pathlib import Path
import sys,json,math
sys.path.insert(0,'/home/node/local/carbon-a-render/python')
from playwright.sync_api import sync_playwright
R=Path(__file__).parent
BROWSER='/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','--disable-renderer-backgrounding','--disable-background-timer-throttling','--disable-backgrounding-occluded-windows']
def check(url=None,headers=None):
with sync_playwright() as p:
browser=p.chromium.launch(executable_path=BROWSER,args=ARGS)
page=browser.new_page(viewport={'width':1500,'height':1050},extra_http_headers=headers or {})
errors=[];page.on('pageerror',lambda e:errors.append(str(e)))
page.goto(url or (R/'index.html').as_uri(),wait_until='domcontentloaded')
page.wait_for_function('window.POC && !document.querySelector("#loading")',timeout=60000)
assert page.evaluate('POC.assemblies')==33588
assert page.locator('#animate-sweep,.sweep-meter,#seen-species,#seen-assemblies,#seen-loci').count()==0
assert page.locator('.resource-links a').all_text_contents()==['Model↗','Database↗','Explorer↗','Paper↗','Blog↗']
assert page.locator('h1').inner_text()=='Carbon-A: Discovering Genes Across the Tree of Life'
assert page.locator('.wordmark em').evaluate('e=>getComputedStyle(e).color')=='rgb(41, 233, 154)'
assert page.locator('#view-dialog').is_hidden()
resources=page.locator('.resources').bounding_box();tree=page.locator('.stage-wrap').bounding_box()
assert resources['x']+resources['width']<=tree['x']
assert tree['height']>880 and abs(tree['y']+tree['height']-1050)<2
assert page.evaluate('Math.abs(stage.getBoundingClientRect().x+screen([0,0])[0]-innerWidth/2)')<1
assert page.locator('#links-title').inner_text()=='Links:'
buttons=[a.bounding_box() for a in page.locator('.resource-links a').all()]
assert max(b['width'] for b in buttons)-min(b['width'] for b in buttons)<1
assert all(a['y']+a['height']<b['y'] for a,b in zip(buttons,buttons[1:]))
assert page.locator('.strand-legend').bounding_box()['y']>buttons[-1]['y']+buttons[-1]['height']
page.screenshot(path=str(R/'presentation-desktop.png'))
# Hover previews a strand and its details; clicking pins that preview.
# The next click anywhere clears without selecting a replacement.
def point(i,radius=62):
return page.evaluate("""([i,radius])=>{const q=screen(polar(radius,leaves[i].angle)),b=stage.getBoundingClientRect();return [b.x+q[0],b.y+q[1]]}""",[i,radius])
page.mouse.move(*point(12345));page.wait_for_function('hoveredAssembly?.i===12345')
assert page.evaluate('selected.i===12345 && pinnedAssembly===null')
assert page.locator('#selection').is_visible()
assert page.locator('#sel-state').text_content()=='Preview · click strand to keep'
assert page.locator('#sel-id').inner_text()==page.evaluate('leaves[12345].accession')
page.mouse.move(*point(25000));page.wait_for_function('hoveredAssembly?.i===25000')
page.mouse.move(0,0);page.wait_for_function('selected===null')
assert page.locator('#selection').is_hidden()
page.mouse.move(*point(12345,730));page.wait_for_function('hoveredAssembly!==null')
assert page.evaluate('selected===hoveredAssembly && pinnedAssembly===null')
# Highlights use the sampled WebGL strand, including on hover.
assert page.evaluate('''()=>{const moves=[],lines=[],move=fx.moveTo,line=fx.lineTo;fx.moveTo=(...p)=>{moves.push(p);move.apply(fx,p)};fx.lineTo=(...p)=>{lines.push(p);line.apply(fx,p)};try{drawInteraction()}finally{fx.moveTo=move;fx.lineTo=line}const a=selected,expected=[];for(let k=a.geometryStart+4;k<a.geometryEnd;k+=8)expected.push(screen([hitData[k],hitData[k+1]]));return JSON.stringify(lines.slice(1))===JSON.stringify(expected)&&JSON.stringify(moves[1])===JSON.stringify(screen([hitData[a.geometryStart],hitData[a.geometryStart+1]]))}''')
page.mouse.move(*point(12345));page.wait_for_function('hoveredAssembly?.i===12345')
page.mouse.click(*point(12345));assert page.evaluate('pinnedAssembly.i')==12345
assert page.locator('#selection').is_visible() and page.evaluate('zoom')==1
page.mouse.move(*point(25000));page.wait_for_timeout(80)
assert page.evaluate('pinnedAssembly.i===12345 && selected===pinnedAssembly')
page.mouse.click(*point(25000));assert page.evaluate('selected===null')
assert page.locator('#selection').is_hidden()
page.wait_for_timeout(100);assert page.evaluate('selected===null')
page.mouse.click(*point(25000));assert page.evaluate('pinnedAssembly.i')==25000
page.locator('h1').click();assert page.evaluate('selected===null')
# Plain strands, icons and binned bars all use selection, without zoom or
# changing bar mode. Clicking info-box text also deselects.
targets=[point(12345,730)]
targets+=page.evaluate("""()=>{const r=stage.getBoundingClientRect(),ic=iconHits[0],bin=locusBins.find(b=>b.known&&b.value>10000),q=screen(polar(R+BAR_GAP+4,leaves[Math.floor(bin.mid)].angle));return [[r.x+ic.x,r.y+ic.y],[r.x+q[0],r.y+q[1]]]}""")
for xy in targets:
page.mouse.click(*xy);assert page.evaluate('selected!==null')
assert page.evaluate('zoom')==1 and page.evaluate('locusMode')=='5'
page.locator('#sel-name').click();assert page.evaluate('selected===null')
page.mouse.dblclick(*point(12345,730));assert page.evaluate('zoom')==1
assert page.evaluate('selected===null')
# Wheel zoom and pan have an always-accessible reset on the graphic.
page.mouse.move(*point(10000));page.mouse.wheel(0,-450)
page.wait_for_function('zoom>1 && !document.querySelector("#home").hidden')
page.locator('#home').click();page.wait_for_function('zoom===1 && pan.x===0 && pan.y===0')
page.locator('#home').wait_for(state='hidden')
box=page.locator('#stage').bounding_box()
page.mouse.move(box['x']+20,box['y']+30);page.mouse.down()
page.mouse.move(box['x']+75,box['y']+60,steps=4);page.mouse.up()
page.wait_for_function('!document.querySelector("#home").hidden')
assert page.evaluate('selected===null')
page.locator('#home').click();assert page.evaluate('pan.x===0 && pan.y===0')
# Search is accessible through the dialog; selecting a result closes it.
page.locator('#view-options').click();page.locator('#search').fill('GCA_000001405.29')
page.locator('#results button').first.click()
assert page.locator('#view-dialog').is_hidden()
assert page.locator('#sel-id').inner_text()=='GCA_000001405.29'
assert page.evaluate('zoom')==1
assert page.locator('#sel-files').get_attribute('href').endswith('/GCA_000001405.29')
page.locator('#sel-download').click();compile(page.locator('#data-code').inner_text(),'download_example','exec')
assert page.evaluate('selected===null') # the info action runs before clearing
page.locator('#data-close').click()
page.locator('#view-options').click();page.locator('#scope').select_option('1');page.locator('#locus-mode').select_option('20')
assert page.evaluate('mode')==1 and page.evaluate('locusMode')=='20'
page.locator('#scope').select_option('0');page.locator('#locus-mode').select_option('5');page.locator('#reset').click();page.locator('#view-close').click()
# Space inside a search input must never start animation.
page.locator('#view-options').click();page.locator('#search').focus();page.keyboard.press('Space')
assert not page.evaluate('sweep.running')
page.locator('#view-close').click();page.locator('#theme-toggle').click();assert page.evaluate('theme')=='light'
page.locator('#theme-toggle').click()
# Test capture from a fresh load, matching the recording entry point.
# Headless Chromium may stop compositor frames after repeated modal closures.
page.reload(wait_until='domcontentloaded')
page.wait_for_function('window.POC && !document.querySelector("#loading")',timeout=60000)
# Keyboard playback, smooth full turn, exact highlighted geometry and no UI counters.
page.evaluate('''()=>{document.activeElement.blur();window.framesSeen=[];window.treeRedraws=0;const oldDraw=gl.drawArrays.bind(gl);gl.drawArrays=(...a)=>{treeRedraws++;oldDraw(...a)};const oldUpdate=updateSweep;updateSweep=t=>{oldUpdate(t);framesSeen.push({t:performance.now(),p:sweep.progress,angle:sweep.angle,selected:selected?.i??null,info:document.querySelector('#sel-id').textContent,expected:selected?.accession})}}''')
page.keyboard.press('Space');page.wait_for_function('sweep.running && sweep.progress>.1')
assert page.locator('#selection').is_visible()
try: page.wait_for_function('sweep.status==="complete"',timeout=12000)
except Exception:
print('sweep failure',page.evaluate('({sweep,hidden:document.hidden,frames:window.framesSeen?.length,focus:document.activeElement.tagName,now:performance.now(),redraws:treeRedraws,pending,zoom})'),errors,flush=True);page.screenshot(path=str(R/'sweep-failure.png'));print('after screenshot',page.evaluate('sweep'),flush=True);raise
result=page.evaluate('({frames:framesSeen,redraws:treeRedraws,angle:sweep.angle,selected:selected?.i,pinned:pinnedAssembly?.i,zoom})')
frames=result['frames'];duration=frames[-1]['t']-frames[1]['t']
assert 3900<=duration<4700,duration
assert len(frames)>35,len(frames)
assert result['redraws']<6,result['redraws']
assert abs(result['angle']-1.5*math.pi)<1e-9
assert result['selected']==33587 and result['pinned']==33587 and result['zoom']==1
assert all(f['info']==f['expected'] for f in frames), 'Info must match the highlighted strand every frame'
assert page.locator('#selection').is_visible()
page.screenshot(path=str(R/'sweep-info.png'))
assert all(a['angle']<=b['angle'] for a,b in zip(frames,frames[1:]))
# Space toggles playback off and freezes the current strand and info.
# Repeats from holding the key do not toggle, scroll or activate a button.
page.keyboard.down('Space');page.wait_for_function('sweep.running && sweep.progress>.1')
page.keyboard.down('Space');assert page.evaluate('sweep.running')
page.keyboard.up('Space');page.keyboard.press('Space')
stopped=page.evaluate('({running:sweep.running,status:sweep.status,progress:sweep.progress,angle:sweep.angle,id:selected?.i,pin:pinnedAssembly?.i})')
assert not stopped['running'] and stopped['status']=='stopped'
assert .1<stopped['progress']<1 and stopped['id']==stopped['pin']
assert page.locator('#selection').is_visible()
page.wait_for_timeout(250)
assert page.evaluate('({running:sweep.running,status:sweep.status,progress:sweep.progress,angle:sweep.angle,id:selected?.i,pin:pinnedAssembly?.i})')==stopped
# Playback can be stopped while a button holds focus, without triggering it.
page.keyboard.press('Space');page.wait_for_function('sweep.running && sweep.progress>.1')
page.locator('#sel-download').focus();page.keyboard.press('Space')
assert not page.evaluate('sweep.running') and page.evaluate('pinnedAssembly===selected && selected!==null')
assert page.locator('#data-dialog').is_hidden()
assert page.locator('#selection').is_visible()
page.evaluate('document.activeElement.blur()')
page.keyboard.press('Space');page.wait_for_function('sweep.progress>.1 && sweep.running');page.keyboard.press('Escape')
assert not page.evaluate('sweep.running') and page.evaluate('selected===null')
# A phone layout has no horizontal overflow and retains all resource buttons.
page.evaluate('reset()');page.mouse.move(0,0);page.set_viewport_size({'width':390,'height':844})
page.wait_for_timeout(150)
assert page.evaluate('document.documentElement.scrollWidth')==390
resources=page.locator('.resources').bounding_box();tree=page.locator('.stage-wrap').bounding_box()
assert tree['width']==390 and tree['height']>=660
assert page.evaluate('Math.abs(stage.getBoundingClientRect().x+screen([0,0])[0]-innerWidth/2)')<1
assert resources['y']>=tree['y']+tree['height']
assert all(x.is_visible() for x in page.locator('.resource-links a').all())
page.screenshot(path=str(R/'presentation-mobile.png'),full_page=True)
page.mouse.click(*point(11000));assert page.locator('#selection').is_visible()
page.locator('h1').click();assert page.evaluate('selected===null')
# Capture mode must retain the info box too.
page.evaluate("document.documentElement.dataset.recording='true';POC.startSweep()")
page.wait_for_function('sweep.progress>.1 && sweep.running')
assert page.locator('#selection').is_visible()
page.locator('h1').click()
assert page.evaluate('selected===null && !sweep.running')
assert page.locator('#selection').is_hidden()
assert not errors,errors
browser.close()
return {'sweep_duration_ms':round(duration),'sweep_frames':len(frames),'background_redraws':result['redraws'],'layout':'desktop and mobile passed','hover_preview_and_exact_geometry':'passed','click_selection_and_anywhere_deselection':'passed','space_toggle_and_focused_button_stop':'passed','clicks_do_not_zoom':'passed','visible_home_reset':'passed','sweep_info_matches_strand':'passed','search_and_downloads':'passed','resource_links':5,'counter_and_button':'removed','errors':errors}
if __name__=='__main__':
result=check();(R/'presentation-verification.json').write_text(json.dumps(result,indent=2));print(json.dumps(result))
|