fix(e2e): renderedFeatures — evaluate maplibre geometry getter in-page before Playwright serialization drops it
CI / build + unit + e2e (pull_request) Successful in 7m28s
CI / build + unit + e2e (pull_request) Successful in 7m28s
This commit is contained in:
+29
-10
@@ -117,12 +117,30 @@ async function waitForAirportCount(page: Page): Promise<void> {
|
||||
}
|
||||
|
||||
// Rendered features of the given layers in the current viewport, [] until the map exists.
|
||||
function renderedFeatures(page: Page, layers: string[]) {
|
||||
function renderedFeatures(
|
||||
page: Page,
|
||||
layers: string[],
|
||||
): Promise<Array<{ type?: unknown; id?: unknown; properties?: unknown; geometry?: unknown }>> {
|
||||
return page.evaluate((ls: string[]) => {
|
||||
const m = (window as any).__flightMap;
|
||||
if (!m) return [];
|
||||
try {
|
||||
return m.queryRenderedFeatures(undefined, { layers: ls });
|
||||
// queryRenderedFeatures returns live GeoJSONFeature instances whose `geometry` is a lazy
|
||||
// PROTOTYPE getter — Playwright's evaluate serialization copies only own enumerable props,
|
||||
// so geometry silently vanishes across the page boundary (CI runs #69–#71: strict-equality
|
||||
// timeouts → TypeError on f.geometry.coordinates → filter-found-nothing timeouts). Map to
|
||||
// plain objects IN-PAGE so the getter is evaluated before serialization.
|
||||
return (m.queryRenderedFeatures(undefined, { layers: ls }) as Array<{
|
||||
type?: unknown;
|
||||
id?: unknown;
|
||||
properties?: unknown;
|
||||
geometry?: unknown;
|
||||
}>).map((f) => ({
|
||||
type: f.type,
|
||||
id: f.id,
|
||||
properties: f.properties,
|
||||
geometry: f.geometry,
|
||||
}));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
@@ -182,11 +200,12 @@ async function airportLonLat(page: Page, icao: string): Promise<[number, number]
|
||||
// exact-coords comparison can therefore never match even when the line is drawn correctly (CI run #69:
|
||||
// all three hover-route timeouts while the trace was visibly rendered), so we accept any number of
|
||||
// segments and require some segment to START within EPS of `from` and some to END within EPS of `to`.
|
||||
// The true endpoints survive clipping verbatim in whichever tile contains them. CI run #70
|
||||
// additionally showed queryRenderedFeatures can return per-tile artifact features WITHOUT a usable
|
||||
// geometry at all (f.geometry undefined), so the helper filters to real LineString segments and
|
||||
// treats "none yet" as keep-polling instead of throwing inside the poll callback. Call
|
||||
// fitToAirports([from, to]) first — rendered features are viewport-scoped.
|
||||
// The true endpoints survive clipping verbatim in whichever tile contains them. (CI runs #69–#71:
|
||||
// features could arrive WITHOUT a usable geometry because Playwright's evaluate serialization drops
|
||||
// the lazy prototype getter — not MapLibre artifacts; renderedFeatures now maps to plain objects
|
||||
// in-page.) The helper still filters to real LineString segments and treats "none yet" as keep-polling
|
||||
// instead of throwing inside the poll callback. Call fitToAirports([from, to]) first — rendered
|
||||
// features are viewport-scoped.
|
||||
const HOVER_ROUTE_EPS_DEG = 0.05; // ~5 km: above worst-case quantization at any zoom, below the distance between any two EU airports
|
||||
|
||||
async function expectHoverRoute(page: Page, fromIcao: string, toIcao: string): Promise<void> {
|
||||
@@ -195,9 +214,9 @@ async function expectHoverRoute(page: Page, fromIcao: string, toIcao: string): P
|
||||
Math.abs(a[0] - b[0]) <= HOVER_ROUTE_EPS_DEG && Math.abs(a[1] - b[1]) <= HOVER_ROUTE_EPS_DEG;
|
||||
await expect.poll(async () => {
|
||||
const feats = (await renderedFeatures(page, ['hover-route'])) as Array<{ geometry?: { type: string; coordinates: [number, number][] } }>;
|
||||
// CI run #70: MapLibre can return per-tile artifacts WITHOUT a usable LineString geometry
|
||||
// (f.geometry undefined). A throw inside an expect.poll callback fails the test instantly
|
||||
// instead of retrying — so keep only real line segments; "none yet" means keep polling.
|
||||
// Features are plain objects with in-page-evaluated geometry (see renderedFeatures). The filter
|
||||
// guards against degenerate/zero-length clipped segments and keeps this poll callback total — a
|
||||
// throw inside expect.poll fails the test instantly instead of retrying. "None yet" → keep polling.
|
||||
const segs = feats.filter(
|
||||
(f): f is { geometry: { type: 'LineString'; coordinates: [number, number][] } } =>
|
||||
!!f.geometry && f.geometry.type === 'LineString' && Array.isArray(f.geometry.coordinates) && f.geometry.coordinates.length >= 2,
|
||||
|
||||
Reference in New Issue
Block a user