e2e: reload before the crossing leg + freshness gate on sampled frames (run-177); queue refuel bound 1.4→1.3×required2
CI / build + unit + e2e (pull_request) Failing after 20m42s

This commit is contained in:
2026-09-23 00:32:36 +00:00
parent baa4b0fa7c
commit 7671c028b4
3 changed files with 23 additions and 7 deletions
+19 -6
View File
@@ -392,19 +392,20 @@ async function chainTo(page: Page, fromIcao: string, toIcao: string): Promise<st
return path;
}
// plane-starter's marker from the planes source: its longitude plus whether that frame is a FLYING one.
// plane-starter's marker from the planes source: its position plus whether that frame is a FLYING one.
// Extracted IN-PAGE because MapLibre feature geometry can be a lazy prototype getter that Playwright's
// evaluate serialization drops (same trap as e2e/hover.spec.ts). The `flying` flag matters for the
// sampling loop: a grounded frame snaps back to the raw airport coordinate (−175.1), which is the same
// screen point but a 360° jump in raw data — only flying frames may be recorded.
function starterMarker(page: Page): Promise<{ lon: number; flying: boolean } | null> {
// screen point but a 360° jump in raw data — only flying frames may be recorded. `lat` feeds the
// freshness gate below (a stale source frame sits far from where the SERVER says the plane is).
function starterMarker(page: Page): Promise<{ lat: number; lon: number; flying: boolean } | null> {
return page.evaluate(() => {
const m = (window as any).__flightMap;
if (!m || !m.getSource('planes')) return null;
try {
for (const f of m.querySourceFeatures('planes') as Array<{ properties?: Record<string, unknown>; geometry?: { coordinates: number[] } }>) {
if (f.properties?.id === 'plane-starter' && f.geometry) {
return { lon: f.geometry.coordinates[0]!, flying: f.properties.flying === true };
return { lat: f.geometry.coordinates[1]!, lon: f.geometry.coordinates[0]!, flying: f.properties.flying === true };
}
}
} catch {
@@ -478,6 +479,14 @@ test('plane marker stays continuous across the antimeridian (no 360° teleport)'
}
}
// Fresh page before the crossing leg: this test's page has been alive ~3 min (relocation chain) and
// under dind load the browser's state feed can go stale — a frozen planes source would then keep an
// old FLYING frame from a relocation hop, fooling the flying poll below and sampling stale lons.
// Server state persists across reloads; the plane is grounded at ORIGIN here, so on this fresh page
// ONLY the crossing leg can ever produce a flying frame.
await page.reload();
await waitForAirportCount(page);
// The crossing package + dispatch.
await setFuelFull(page, 'plane-starter');
const pkg = await createPackage(page, ORIGIN, CROSS_DEST);
@@ -496,16 +505,20 @@ test('plane marker stays continuous across the antimeridian (no 360° teleport)'
// Sample the marker's longitude while it crosses. Stop once the SERVER says landed; record only
// FLYING frames — a grounded frame snaps back to the raw airport coordinate (−175.1), the same screen
// point but a 360° jump in raw data, and could land between this loop's server read and source read.
// Freshness gate: the browser's source may lag server truth by a tick or two (SSE + tile re-render),
// but a FROZEN frame would otherwise pass continuity vacuously and never show the crossing — accept
// only frames within ~700 km of where the SERVER says the plane is (~5 s of flight at ×1000); haversine
// is ±360°-periodic in Δlon, so wrapped server lon vs unwrapped marker lon compare directly.
const lons: number[] = [];
for (;;) {
const p = (await getState(page)).planes.find((x) => x.id === 'plane-starter')!;
if (p.status !== 'flying') break;
const marker = await starterMarker(page);
if (marker?.flying) lons.push(marker.lon);
if (marker?.flying && haversineKm(p.lat, p.lon, marker.lat, marker.lon) < 700) lons.push(marker.lon);
await page.waitForTimeout(500);
}
expect(lons.length).toBeGreaterThan(3); // a real flight window, not a degenerate one
expect(lons.length).toBeGreaterThan(3); // a real flight window of FRESH frames (stale ones are gated out above), not a degenerate one
for (let i = 1; i < lons.length; i++) {
expect(Math.abs(lons[i]! - lons[i - 1]!), `marker jumped between samples ${i - 1} and ${i}`).toBeLessThan(180); // the ISS-124 bug
}
+3 -1
View File
@@ -572,7 +572,9 @@ test('a drained tank is topped up before a chained leg at exactly the rep-discou
// Exact fuel equality is impossible — this snapshot lands after leg 2's departure burn; the exact top-up level × rep-discounted price is pinned by the cash assertion below.
const fuel = s.planes.find((x) => x.id === 'plane-starter')!.fuel;
expect(fuel).toBeLessThanOrEqual(target); // can only have burned since departure…
expect(fuel).toBeGreaterThan(1.4 * required2); // …and a wrong top-up level (≤ required2, or none at all) lands below this bound
// 1.3× not 1.4×: under dind load the snapshot can land ~0.1×required2 into leg 2's departure burn;
// a wrong top-up level (≤ required2, or none at all) still lands far below this bound.
expect(fuel).toBeGreaterThan(1.3 * required2);
expect(s.account.cash).toBeCloseTo(cashBeforeChain - expectedCost, 6); // at exactly the rep-discounted cost
// Let leg 2 finish so the next test starts from a known airport.