test: harden full-core-loop against Load/Fly precondition races
CI / build + unit + e2e (pull_request) Successful in 6m32s
CI / build + unit + e2e (pull_request) Successful in 6m32s
This commit is contained in:
+27
-6
@@ -100,15 +100,21 @@ test.describe.configure({ mode: 'serial' }); // shared live DB — order matters
|
||||
// (repositioning needs no package — v0.12.0 manual flight), and abandon any leftover cargo so
|
||||
// exact cash/rep assertions start from a clean baseline even after an interrupted previous run.
|
||||
async function flyHomeViaApi(page: Page): Promise<void> {
|
||||
const s0 = await getState(page);
|
||||
const plane = s0.planes.find((p) => p.id === 'plane-starter')!;
|
||||
let s = await getState(page);
|
||||
let plane = s.planes.find((p) => p.id === 'plane-starter')!;
|
||||
if (plane.status === 'flying' || plane.locationIcao !== HUB) {
|
||||
// Fast-forward any leftover leg / repositioning flight (~20 s real time worst case at 1000×).
|
||||
await setScale(page, SCALE);
|
||||
}
|
||||
if (plane.status === 'flying') {
|
||||
// Let the in-flight leg finish before repositioning.
|
||||
// Let the in-flight leg finish before repositioning. While flying, locationIcao stays at the
|
||||
// DEPARTURE airport, so the pre-landing snapshot can't tell us where the plane ends up — re-read
|
||||
// state after landing before deciding whether a repositioning flight is needed (run #41 retry: a
|
||||
// leftover EDDF→EDFH leg landed away from home but the helper still believed it was home and
|
||||
// skipped the fly-home).
|
||||
await expect.poll(async () => ((await getState(page)).planes.find((p) => p.id === 'plane-starter')!.status), { timeout: 240_000 }).toBe('available');
|
||||
s = await getState(page);
|
||||
plane = s.planes.find((p) => p.id === 'plane-starter')!;
|
||||
}
|
||||
if (plane.locationIcao !== HUB) {
|
||||
const res = await page.request.post(`/api/planes/${plane.id}/fly`, { data: { destIcao: HUB } });
|
||||
@@ -143,6 +149,14 @@ test('full core loop via UI: load → fly to destination → exact reward + rep
|
||||
expect(pkg.originIcao).toBe(HUB);
|
||||
expect(pkg.destIcao).toBe(nearest.icao);
|
||||
|
||||
// Precondition (server truth) before clicking Load: the starter must be grounded at the package's
|
||||
// origin — that is exactly what makes it load-eligible, and a stale pane snapshot can't prove it.
|
||||
await expect.poll(async () => {
|
||||
const b = await getState(page);
|
||||
const p = b.planes.find((x) => x.id === 'plane-starter')!;
|
||||
return p.status === 'available' && p.locationIcao === pkg.originIcao;
|
||||
}, { timeout: 30_000, message: 'waiting for plane-starter to be grounded at the package origin (self-heal incomplete?)' }).toBe(true);
|
||||
|
||||
// LOAD via the UI: exactly one eligible plane (the starter at EDDF) → direct button.
|
||||
await page.goto('/');
|
||||
const loadBtn = page.getByTestId(`load-${pkg.id}`);
|
||||
@@ -159,10 +173,17 @@ test('full core loop via UI: load → fly to destination → exact reward + rep
|
||||
await expect(flyBtn).toBeEnabled();
|
||||
await flyBtn.click();
|
||||
|
||||
let s = await getState(page);
|
||||
const planeId = 'plane-starter';
|
||||
expect(s.planes.find((p) => p.id === planeId)!.status).toBe('flying');
|
||||
expect(s.planes.find((p) => p.id === planeId)!.destIcao).toBe(nearest.icao);
|
||||
// The click fires the fly POST asynchronously (runAction is fire-and-forget from the test's view),
|
||||
// so a getState issued immediately can race ahead of it and still see 'available' — poll until the
|
||||
// fly actually took effect on the server (the run #36/#41 flake).
|
||||
await expect.poll(async () => {
|
||||
const b = await getState(page);
|
||||
const p = b.planes.find((x) => x.id === planeId)!;
|
||||
return p.status === 'flying' && p.destIcao === nearest.icao;
|
||||
}, { timeout: 15_000, message: 'waiting for the fly click to take effect (status "flying" + dest set)' }).toBe(true);
|
||||
|
||||
let s = await getState(page);
|
||||
|
||||
// Fast-forward to arrival (a few seconds of real time at 1000×).
|
||||
await setScale(page, SCALE);
|
||||
|
||||
Reference in New Issue
Block a user