#!/usr/bin/env node /** Location UI acceptance; controlled search results isolate UI races from geocoder availability. */ import fs from 'node:fs'; import puppeteer from 'puppeteer'; const browser = await puppeteer.launch({headless:true,args:['--no-sandbox',...(process.platform === 'darwin' ? ['--use-angle=metal','--enable-gpu'] : ['--use-gl=angle','--use-angle=swiftshader'])]}); const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));let failures=0; const check=(name,ok)=>{console.log(`[${ok?'PASS':'FAIL'}] ${name}`);if(!ok)failures++}; try { await page.setViewport({width:1440,height:900}); await page.goto(`${process.env.QA_BASE_URL||'http://localhost:4173'}/?welcome=0`,{waitUntil:'domcontentloaded'}); await page.waitForFunction(()=>window.__godsEyeView?.styleManager?._locationControls&&document.getElementById('loading-screen')?.classList.contains('hidden'),{timeout:60000}); await page.click('#location-bar-toggle'); await page.click('[data-pin-target="location-bar"]'); const cityId=await page.$eval('.location-pill',node=>node.dataset.locationId); await page.click('.location-pill'); await page.waitForFunction(()=>document.getElementById('poi-row').classList.contains('expanded')); check('native city selection highlights and expands its POIs',await page.evaluate(id=>window.__godsEyeView.styleManager._activeLocationId===id&&document.querySelector('.location-pill.active')?.dataset.locationId===id,cityId)); await page.keyboard.press('w'); check('POI keyboard action selects the second landmark',await page.evaluate(()=>window.__godsEyeView.styleManager._activePoiIndex===1&&document.querySelector('.poi-pill.active')?.dataset.poiIndex==='1')); check('orbit controls and indicator agree',await page.evaluate(()=>{const ui=window.__godsEyeView.styleManager;ui.setOrbit(true);const active=ui.orbitController.active&&document.getElementById('orbit-indicator').classList.contains('active');ui.setOrbit(false);return active&&!ui.orbitController.active&&!document.getElementById('orbit-indicator').classList.contains('active')})); await page.click('#search-toggle'); await page.type('#location-search','qwerty'); check('typing in search leaves POI selection unchanged',await page.evaluate(()=>window.__godsEyeView.styleManager._activePoiIndex===1)); await page.evaluate(()=>{ const lookup=window.__godsEyeView.styleManager._locationLookup; window.__qaSearchRequests=[]; lookup.search=(query,options)=>new Promise(resolve=>window.__qaSearchRequests.push({query,options,resolve})); document.getElementById('location-search').value='First'; }); await page.keyboard.press('Enter'); await page.waitForFunction(()=>window.__qaSearchRequests.length===1); await page.evaluate(()=>{document.getElementById('location-search').value='Second';document.getElementById('location-search').focus()}); await page.keyboard.press('Enter'); await page.waitForFunction(()=>window.__qaSearchRequests.length===2); check('second native submit aborts the first request and keeps current busy state',await page.evaluate(()=>window.__qaSearchRequests[0].options.signal.aborted&&document.getElementById('location-search').classList.contains('searching'))); await page.evaluate(()=>window.__qaSearchRequests[1].resolve({label:'Second landmark, Test city'})); await page.waitForFunction(()=>window.__godsEyeView.styleManager._searchedLocationLabel==='Second landmark, Test city'); await page.evaluate(()=>window.__qaSearchRequests[0].resolve({label:'First landmark, Test city'})); check('late result cannot overwrite the current location readout or style',await page.evaluate(()=>{const ui=window.__godsEyeView.styleManager;return ui._searchedLocationLabel==='Second landmark, Test city'&&ui._locationMiniPoi.textContent.includes('Second landmark')&&ui.activeStyle==='normal'&&!ui._locationSearch.classList.contains('searching')})); check('cancelled POI expansion stays closed after a frame',await page.evaluate(async id=>{const ui=window.__godsEyeView.styleManager;ui._expandPOIRow(id);ui._collapsePOIRow();await new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)));return !ui._poiRow.classList.contains('expanded')&&!ui._locationBarDivider.classList.contains('visible')},cityId)); await page.evaluate(id=>window.__godsEyeView.styleManager._onCityPillClick(id),cityId); await page.waitForFunction(()=>document.getElementById('poi-row').classList.contains('expanded')); // Finish the camera's existing city flight before inspecting both layouts. await page.waitForFunction(()=>!window.__godsEyeView.viewer.camera._currentFlight,{timeout:10000}); fs.mkdirSync('qa-shots/location-controls',{recursive:true}); await page.screenshot({path:'qa-shots/location-controls/desktop.png'}); await page.setViewport({width:620,height:900}); await page.waitForFunction(()=>document.getElementById('left-panel-stack')?.dataset.layoutMode==='mobile'); await page.screenshot({path:'qa-shots/location-controls/narrow.png'}); check('native reset returns through the existing camera handoff',await page.evaluate(async()=>{const ui=window.__godsEyeView.styleManager;ui._resetGlobeBtn.click();const pending=ui._globeResetPromise;if(!pending)return false;await pending;return !ui.orbitController.active&&!window.__godsEyeView.viewer.trackedEntity})); check('destroyed controls cannot navigate or submit another lookup',await page.evaluate(()=>{const ui=window.__godsEyeView.styleManager;const count=window.__qaSearchRequests.length;ui._locationControls.destroy();ui._locationLookup.destroy();const before=ui._navigationGeneration;ui._locationPills.querySelector('button').click();ui._resetGlobeBtn.click();ui._locationSearch.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',bubbles:true}));return before===ui._navigationGeneration&&count===window.__qaSearchRequests.length})); check('no uncaught browser errors',errors.length===0); }finally{await browser.close()} console.log(`RESULT: ${failures} failures`);process.exitCode=failures?1:0;