fix(e2e): pass STARTER into evaluate; verify self-heal end state on fresh snapshots
CI / build + unit + e2e (pull_request) Failing after 10m42s

This commit is contained in:
2026-09-28 09:53:07 +00:00
parent 82e4c64206
commit b683c4e546
2 changed files with 21 additions and 31 deletions
+3 -3
View File
@@ -105,7 +105,7 @@ async function fitToAirports(page: Page, icaos: string[]): Promise<void> {
// fetch can be stale by tens of px) and wait for the popup. `flyingOnly` skips grounded frames —
// used right after dispatch, when the last rendered frame may predate it.
async function clickPlane(page: Page, flyingOnly: boolean): Promise<void> {
const pt = await page.evaluate(async (onlyFlying: boolean) => {
const pt = await page.evaluate(async ({ onlyFlying, starterId }: { onlyFlying: boolean; starterId: string }) => {
const m = (window as any).__flightMap;
if (!m) return null;
const deadline = Date.now() + 10_000;
@@ -116,7 +116,7 @@ async function clickPlane(page: Page, flyingOnly: boolean): Promise<void> {
} catch {
// map not ready yet — keep polling
}
const f = feats.find((x) => x.properties?.id === STARTER && (!onlyFlying || x.properties?.flying === true));
const f = feats.find((x) => x.properties?.id === starterId && (!onlyFlying || x.properties?.flying === true));
if (f && f.geometry) {
const projected = m.project(f.geometry.coordinates);
const rect = m.getContainer().getBoundingClientRect();
@@ -125,7 +125,7 @@ async function clickPlane(page: Page, flyingOnly: boolean): Promise<void> {
if (Date.now() >= deadline) return null;
await new Promise((r) => setTimeout(r, 100));
}
}, flyingOnly);
}, { onlyFlying: flyingOnly, starterId: STARTER });
expect(pt, 'plane marker not rendered').not.toBeNull();
// Popups are created per click and never auto-close — remove any stale ones so the testids below
+18 -28
View File
@@ -154,39 +154,29 @@ async function createPackage(page: Page, fromIcao: string, toIcao: string, deadl
return (await created.json()) as PackageState;
}
// Self-heal: let any in-flight leg finish, then reposition plane-starter home via the API.
// Self-heal: let any in-flight leg finish, then reposition plane-starter home via the API. The end
// state is verified by ONE poll on FRESH snapshots (v1.12.0): legs now include takeoff/landing holds,
// so a leftover leg can land at its destination after an earlier snapshot was taken — deciding from a
// stale pre-flight snapshot let tests load cargo onto a plane that wasn't actually at the hub (409).
async function flyHomeViaApi(page: Page): Promise<void> {
// v0.39.0 (ISS-069): wear accumulates across the shared CI DB — restore full health and clear any
// active repair gate from an interrupted run before the steps below act on the starter, so the
// farthest-flyable picker and fuel pins never see a worn or broken plane.
expect((await page.request.post('/api/test/set-health', { data: { planeId: 'plane-starter', health: 100, repairUntilMs: 0 } })).status()).toBe(200);
const s0 = await getState(page); // ticks — a shortened gate flips the plane back to available here
const starter = s0.planes.find((p) => p.id === 'plane-starter')!;
if (starter.status === 'flying' || starter.locationIcao !== HUB) {
// Fast-forward any leftover leg / repositioning flight (~20 s real time worst case at 1000×).
await setScale(page, SCALE);
}
if (starter.status === 'flying') {
await expect
.poll(async () => (await getState(page)).planes.find((p) => p.id === 'plane-starter')?.status === 'available', {
timeout: 240_000,
message: 'waiting for an in-flight plane-starter to land',
})
.toBe(true);
}
if (starter.locationIcao !== HUB) {
// Make sure the world rate is back at SCALE so the return leg fast-forwards. ISS-082: dispatch
// auto-refuels, so a grounded plane can always start any full-tank-flyable leg when cash allows — no top-up retry needed.
await setScale(page, SCALE);
const flyHome = await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: HUB } });
expect(flyHome.status()).toBe(200); // repositioning needs no package (v0.12.0 manual flight)
await expect
.poll(async () => (await getState(page)).planes.find((p) => p.id === 'plane-starter')?.locationIcao === HUB, {
timeout: 240_000,
message: 'waiting for plane-starter to land back at the hub',
})
.toBe(true);
}
// Fast-forward any leftover leg / repositioning flight (a few seconds real time worst case at 1000×).
await setScale(page, SCALE);
await expect.poll(async () => {
const p = (await getState(page)).planes.find((x) => x.id === 'plane-starter')!;
if (p.status === 'available' && p.locationIcao === HUB) return 'ok';
if (p.status === 'available') {
// ISS-082: dispatch auto-refuels, so a grounded plane can always start any full-tank-flyable leg
// when cash allows — no top-up retry needed. Fail loud with the server's reason otherwise.
const flyHome = await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: HUB } });
if (flyHome.status() !== 200) throw new Error(`reposition home failed (${flyHome.status()}): ${await flyHome.text()}`);
return `flying home from ${p.locationIcao}`;
}
return p.status; // flying or repairing — keep waiting (SCALE makes both finish quickly)
}, { timeout: 240_000, message: 'waiting for plane-starter to be grounded at the hub' }).toBe('ok');
// Abandon leftover loaded/expired packages (the baseline is captured AFTER this cleanup).
const s1 = await getState(page);
for (const pkg of s1.packages) {