README shots wait on the map, not a clock: style + tiles loaded and our layers actually drawn (cap 60 s)
A fixed 7 s wait shot the upg-100-list counter over a bare basemap: that leaf paints 9.5-16.5 s after
load (rega, 3 cold loads at fc2fa2e4). Every map a shot's page or frames create is now recorded (an init
script on the engine global's Map.prototype.setStyle, as in rega's probe), and the capture waits until
every visible map has its style and tiles loaded and has drawn features on OUR layers (GeoJSON sources;
the basemap's are vector tiles), capped at 60 s with a WARN naming the unready map. Each shot's `wait`
becomes a 3 s floor, and the full fallback for a surface with no map. Not browser-run here (browsers are
rega's): the next shot run is the proof.
This commit is contained in:
parent
3113344e6a
commit
9c683f1310
1 changed files with 48 additions and 3 deletions
|
|
@ -164,6 +164,25 @@ async function shot(name, path, {
|
|||
// need upstream tiles and have no iframe), the iframe-host shot opts out (it needs its local
|
||||
// frame and loads no tiles of its own).
|
||||
if (mapboxReferer) await ctx.setExtraHTTPHeaders({ Referer: 'https://doxa.life/' })
|
||||
// ★ WAIT ON THE MAP ITSELF, NOT A CLOCK (2026-09-26). Every map a page (or any frame in it) creates is
|
||||
// recorded, so the capture can wait until each visible one has its style and tiles loaded AND has
|
||||
// drawn features on OUR layers (sources that are not the basemap's). A fixed 7 s wait shot the
|
||||
// upg-100-list counter over a bare basemap: that leaf paints 9.5-16.5 s in (rega, 3 cold loads at
|
||||
// fc2fa2e4). Same predicate rega measures with (_qa-cycle/rega/upglist-fc2fa2e4/upglist.mjs).
|
||||
await ctx.addInitScript(() => {
|
||||
window.__SHOT_MAPS = []
|
||||
const patch = (x) => {
|
||||
if (!x || !x.Map || x.Map.__shotPatched) return
|
||||
x.Map.__shotPatched = true
|
||||
const ss = x.Map.prototype.setStyle
|
||||
x.Map.prototype.setStyle = function (...a) { if (!window.__SHOT_MAPS.includes(this)) window.__SHOT_MAPS.push(this); return ss.apply(this, a) }
|
||||
}
|
||||
for (const g of ['maplibregl', 'mapboxgl']) {
|
||||
let v = window[g]
|
||||
try { patch(v) } catch (_) {}
|
||||
try { Object.defineProperty(window, g, { configurable: true, get: () => v, set: (x) => { v = x; try { patch(x) } catch (_) {} } }) } catch (_) {}
|
||||
}
|
||||
})
|
||||
const page = await ctx.newPage()
|
||||
// ★ WAIT FOR THE MAP TO FINISH LOADING, NOT JUST FOR THE PAGE TO EXIST. The element-count guard
|
||||
// below proves the UI rendered; it says nothing about map tiles and data, which arrive later. Shots
|
||||
|
|
@ -178,7 +197,33 @@ async function shot(name, path, {
|
|||
await page.goto(url, { waitUntil: 'load', timeout: 45000 })
|
||||
if (waitFor) await page.waitForSelector(waitFor, { timeout: 20000 })
|
||||
if (before) await before(page)
|
||||
await page.waitForTimeout(wait)
|
||||
// `wait` is now a FLOOR for the UI to settle (at most 3 s); the map readiness below does the rest.
|
||||
// A surface with no map (a card, a grid) waits its full `wait`, since there is nothing to ask.
|
||||
await page.waitForTimeout(Math.min(wait, 3000))
|
||||
const mapState = (frame) => frame.evaluate(() => (window.__SHOT_MAPS || []).map((m) => {
|
||||
try {
|
||||
const c = m.getContainer().getBoundingClientRect()
|
||||
if (c.width < 2 || c.height < 2) return { hidden: true }
|
||||
const st = m.getStyle() || {}
|
||||
// OUR data is GeoJSON; the basemap's sources are vector tiles. Type, not name: a source id can say anything.
|
||||
const own = Object.entries(st.sources || {}).filter(([, src]) => src && src.type === 'geojson').map(([k]) => k)
|
||||
const ids = (st.layers || []).filter((l) => own.includes(l.source) && (l.layout?.visibility ?? 'visible') !== 'none').map((l) => l.id)
|
||||
let drawn = 0
|
||||
try { drawn = ids.length ? m.queryRenderedFeatures({ layers: ids }).length : 0 } catch (_) {}
|
||||
return { style: m.isStyleLoaded(), tiles: m.areTilesLoaded(), ownLayers: ids.length, drawn }
|
||||
} catch (e) { return { err: String(e).slice(0, 80) } }
|
||||
})).catch(() => [])
|
||||
const MAP_CAP_MS = 60000, tMap = Date.now()
|
||||
let maps = []
|
||||
for (;;) {
|
||||
maps = []
|
||||
for (const f of page.frames()) maps.push(...(await mapState(f)))
|
||||
const live = maps.filter((m) => !m.hidden && !m.err)
|
||||
if (!live.length) { if (Date.now() - tMap < 4000) { await page.waitForTimeout(500); continue } await page.waitForTimeout(Math.max(0, wait - 3000)); break }
|
||||
if (live.every((m) => m.style && m.tiles && (m.ownLayers === 0 || m.drawn > 0))) break
|
||||
if (Date.now() - tMap > MAP_CAP_MS) { console.log(` WARN ${name}: a map was not ready in ${MAP_CAP_MS / 1000}s ${JSON.stringify(live)} — check the image`); break }
|
||||
await page.waitForTimeout(500)
|
||||
}
|
||||
const QUIET_MS = 4000, QUIET_MAX_MS = 60000, t0 = Date.now()
|
||||
while (!(inflight <= 0 && Date.now() - lastNet >= QUIET_MS)) {
|
||||
if (Date.now() - t0 > QUIET_MAX_MS) { console.log(` WARN ${name}: network never went quiet in ${QUIET_MAX_MS / 1000}s (${inflight} in flight) — check the image`); break }
|
||||
|
|
@ -257,8 +302,8 @@ await shot('cap3-monolithic-research', `/${TREE_NAME}/doxa-research-map/research
|
|||
await shot('cap4-upg100-mfe', `/${TREE_NAME}/my-upg-100-list/upg-100-mfe/index.html`, { wait: 8000 })
|
||||
// wait 20000: this leaf PAINTS 9.5-16.5 s after load under swiftshader (rega, 3 cold loads at fc2fa2e4,
|
||||
// judged by rendered features on its own layers), and its "195 polys · 27/27 zones · 2107 pins" counter
|
||||
// appears BEFORE the paint, so a 7 s wait shot the counter over a bare basemap. Stopgap until the shot
|
||||
// waits on the map itself rather than a timer.
|
||||
// appears BEFORE the paint, so a 7 s wait shot the counter over a bare basemap. The shot now waits on the
|
||||
// map itself (style + tiles + features drawn on our layers, see shot()); `wait` is only a fallback.
|
||||
await shot('cap4-upg100-list', `/${TREE_NAME}/my-upg-100-list/upg-100-list/index.html`, { wait: 20000 })
|
||||
|
||||
// ---- CAP4's THIRD profile: the poster wall ----
|
||||
|
|
|
|||
Loading…
Reference in a new issue