diff --git a/scripts/qa-wildlife-switch.mjs b/scripts/qa-wildlife-switch.mjs index 387b11231..8c1f8da5c 100644 --- a/scripts/qa-wildlife-switch.mjs +++ b/scripts/qa-wildlife-switch.mjs @@ -247,11 +247,21 @@ await strict.run({}, async (b) => { box: box ? [box.x, box.y, box.width, box.height] : null, }; }); + // The picker sits in VISUAL PRESETS, out of the Tab order while that + // panel is collapsed: open it from the keyboard too. + if ((await picker()).panel !== 'true') { + await p.focus('#control-panel-toggle'); + await p.keyboard.press('Enter'); + } await p .waitForFunction( () => { const el = document.querySelector('#globe-engine'); - return el && !el.disabled && el.getBoundingClientRect().height > 0; + return ( + el && + !el.disabled && + el.checkVisibility({ visibilityProperty: true }) + ); }, { timeout: 30000 }, ) @@ -293,7 +303,20 @@ await strict.run({}, async (b) => { const tabs = path.length; const from = await p.$eval('#globe-engine', (el) => el.value); await p.keyboard.type(kind[0]); - await waitForEngine(p, kind); + await waitForEngine(p, kind).catch(async (error) => { + const state = await p.evaluate(() => ({ + engine: window.__godsEyeView?.engine?.kind ?? null, + picker: document.querySelector('#globe-engine')?.value ?? null, + focus: document.activeElement?.id ?? null, + busy: document.querySelector('#globe-engine')?.disabled ?? null, + frame: Boolean(document.querySelector('.engine-switch-frame')), + loading: document.querySelector('#loading-screen')?.className ?? null, + })); + throw Error( + `no ${kind} globe after typing ${kind[0]}: ${JSON.stringify({ from, tabs: path.length, ...state })}`, + { cause: error }, + ); + }); assert.equal( await p.evaluate(() => performance.timeOrigin), origin, diff --git a/src/globe/testing/contractSuite.mjs b/src/globe/testing/contractSuite.mjs index 2c2774a44..69ba2c4d3 100644 --- a/src/globe/testing/contractSuite.mjs +++ b/src/globe/testing/contractSuite.mjs @@ -795,23 +795,39 @@ export function createContractSuite( const shownStrength = strength(at(20.5, 9)); line.show(false); await until(() => magentaAt(at(20.5, 9)) === 0, 'the line hidden'); - // 30 km under the ground: only the depth-fail copy shows, dimmed. - const deep = (lon) => at(lon, 11, -30_000); - hidden.setPositions([deep(19), deep(21)]); + // Round the back of the Earth along the great circle west of the + // view centre, out to a visible end past the western horizon. The + // far side, behind the Earth's centre, shows only by its dimmed + // depth-fail copy. Cesium culls a line the globe hides whole, and + // draws in full what lies under the horizon but before the centre. + const west = (degrees) => { + const t = (degrees * Math.PI) / 180; + const [lon0, lat0] = [(20 * Math.PI) / 180, (10 * Math.PI) / 180]; + const [x, y, z] = [ + Math.cos(lat0) * Math.cos(lon0) * Math.cos(t) + + Math.sin(lon0) * Math.sin(t), + Math.cos(lat0) * Math.sin(lon0) * Math.cos(t) - + Math.cos(lon0) * Math.sin(t), + Math.sin(lat0) * Math.cos(t), + ]; + return at( + (Math.atan2(y, x) * 180) / Math.PI, + (Math.asin(z) * 180) / Math.PI, + ); + }; + const deep = () => west(150); + hidden.setPositions([200, 150, 100, 50, 20].map(west)); await until( - () => magentaAt(deep(20)) > 10, + () => magentaAt(deep()) > 10, 'the hidden line drawn by its depth-fail copy', ); - const hiddenStrength = strength(deep(20)); + const hiddenStrength = strength(deep()); ensure( hiddenStrength < shownStrength, `the depth-fail copy (${hiddenStrength}) is not dimmer than the line (${shownStrength})`, ); hidden.destroy(); - await until( - () => magentaAt(deep(20)) === 0, - 'the destroyed line gone', - ); + await until(() => magentaAt(deep()) === 0, 'the destroyed line gone'); } finally { line.destroy(); hidden.destroy();