e2e: per-test timescale speedup — FAST/CHAIN rates for pure arrival waits (v1.8.0)
CI / build + unit + e2e (pull_request) Successful in 6m3s
CI / build + unit + e2e (pull_request) Successful in 6m3s
- actions/hover/two-planes: FAST=10_000 for expiry fast-forwards, landing waits and fly-home cleanups; queue: + CHAIN=4_000 for auto-chain observation, refuel test at explicit 2_000 with relaxed fuel bound - map-interactions: FAST=50_000 chain hops (~3 min -> ~15 s per direction); crossing leg stays at SCALE and now sets it BEFORE dispatch (a leftover fast rate would let the dispatch tick consume most of the leg) - hover fleet-row test asserts flying from the fly endpoint's own snapshot and freezes immediately instead of speeding up then freezing mid-leg - two-planes parallel-flight: both-grounded poll after cleanup dispatches so afterEach heal no longer pays the range-edge return leg at SCALE
This commit is contained in:
@@ -20,6 +20,9 @@ const HUB = 'EDDF'; // must match HUB_ICAO in lib/game/seed.ts
|
||||
// the 6300 L tank, so a full-tank departure always makes it home.
|
||||
const DEST = 'LEMD';
|
||||
const SCALE = 1000; // test rate for fast-forwarding legs
|
||||
// Pure arrival waits and relocation-chain hops (no mid-leg observation): a lagged tick can only finish
|
||||
// the leg sooner, never skip an observed state. Max hop ≤3400 km lands in <1 s at this rate.
|
||||
const FAST = 50_000;
|
||||
const WORLD = 24; // the fixed world rate (ISS-117) — must match lib/worldClock.ts
|
||||
|
||||
interface PlaneState {
|
||||
@@ -106,15 +109,15 @@ async function setFuelFull(page: Page, planeId: string): Promise<void> {
|
||||
expect((await page.request.post('/api/test/set-fuel', { data: { planeId, fuel: p.maxFuel } })).status()).toBe(200);
|
||||
}
|
||||
|
||||
// Load a package onto plane-starter and fly it from → to via the API, then poll at
|
||||
// scale 1000 until the plane lands at `to`. Same pattern as e2e/actions.spec.ts.
|
||||
// Load a package onto plane-starter and fly it from → to via the API, then fast-forward at FAST
|
||||
// (pure arrival wait — no mid-leg observation) until the plane lands at `to`. Same pattern as e2e/actions.spec.ts.
|
||||
async function loadAndFly(page: Page, fromIcao: string, toIcao: string): Promise<PackageState> {
|
||||
const pkg = await createPackage(page, fromIcao, toIcao);
|
||||
const load = await page.request.post(`/api/packages/${pkg.id}/load`, { data: { planeId: 'plane-starter' } });
|
||||
expect(load.status()).toBe(200);
|
||||
const fly = await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: toIcao } });
|
||||
expect(fly.status()).toBe(200);
|
||||
await setScale(page, SCALE);
|
||||
await setScale(page, FAST);
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const body = await getState(page);
|
||||
@@ -204,7 +207,7 @@ test('airport popup shows name, ICAO, local time and generated weather', async (
|
||||
});
|
||||
|
||||
test('plane markers move while flying and the plane popup shows status + route', async ({ page }) => {
|
||||
test.setTimeout(600_000); // self-heal from a far airport (~20 s worst case at 1000×) + main flight + cleanup
|
||||
test.setTimeout(600_000); // self-heal from a far airport (~3 s worst case at 50_000×) + main flight + cleanup
|
||||
|
||||
await page.goto('/');
|
||||
await waitForAirportCount(page);
|
||||
@@ -215,7 +218,7 @@ test('plane markers move while flying and the plane popup shows status + route',
|
||||
const foundStarter = (await getState(page)).planes.find((p) => p.id === 'plane-starter');
|
||||
expect(foundStarter).toBeDefined();
|
||||
if (foundStarter!.status === 'flying') {
|
||||
await setScale(page, SCALE);
|
||||
await setScale(page, FAST); // pure arrival wait — no mid-leg observation needed here
|
||||
await expect
|
||||
.poll(async () => (await getState(page)).planes.find((p) => p.id === 'plane-starter')?.status === 'available', {
|
||||
timeout: 240_000,
|
||||
@@ -348,8 +351,8 @@ test('plane markers move while flying and the plane popup shows status + route',
|
||||
// The popup's route line is the plane's own leg: origin name → destination name.
|
||||
await expect(page.getByTestId('plane-route')).toContainText(`${live.locationName} → ${live.destName}`);
|
||||
|
||||
// Restore SCALE before step 5 — the landing wait needs a fast clock.
|
||||
await setScale(page, SCALE);
|
||||
// Fast-forward to arrival (pure wait — no mid-leg observation) at FAST: ~0.2 s instead of ~4 s.
|
||||
await setScale(page, FAST);
|
||||
|
||||
// 5) Let the flight finish, then fly home so the live instance stays in its
|
||||
// onboarding state (starter grounded at EDDF) for the other specs. The loaded
|
||||
@@ -478,7 +481,7 @@ async function fitToAirports(page: Page, icaos: string[]): Promise<void> {
|
||||
}
|
||||
|
||||
test('plane marker stays continuous across the antimeridian (no 360° teleport)', async ({ page }) => {
|
||||
test.setTimeout(900_000); // relocation chain (~3 min at 1000×) + crossing leg + return chain + margin
|
||||
test.setTimeout(900_000); // relocation chain (~15 s per direction at 50_000×) + crossing leg + return chain + margin
|
||||
|
||||
await page.goto('/');
|
||||
await waitForAirportCount(page);
|
||||
@@ -489,7 +492,7 @@ test('plane marker stays continuous across the antimeridian (no 360° teleport)'
|
||||
// Self-heal: full health, let any in-flight leg finish, then relocate to ORIGIN via the chain.
|
||||
expect((await page.request.post('/api/test/set-health', { data: { planeId: 'plane-starter', health: 100, repairUntilMs: 0 } })).status()).toBe(200);
|
||||
if ((await getState(page)).planes.find((p) => p.id === 'plane-starter')!.status === 'flying') {
|
||||
await setScale(page, SCALE);
|
||||
await setScale(page, FAST); // pure arrival wait — no mid-leg observation needed here
|
||||
await expect.poll(async () => (await getState(page)).planes.find((p) => p.id === 'plane-starter')?.status ?? '', { timeout: 240_000 }).toBe('available');
|
||||
}
|
||||
// Relocate + crossing leg. The finally ALWAYS runs — even when a poll below fails, the plane must end
|
||||
@@ -500,7 +503,7 @@ test('plane marker stays continuous across the antimeridian (no 360° teleport)'
|
||||
for (const next of await chainTo(page, loc, ORIGIN)) {
|
||||
await setFuelFull(page, 'plane-starter'); // full tank → auto-refuel tops up 0 L and costs nothing
|
||||
expect((await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: next } })).status()).toBe(200);
|
||||
await setScale(page, SCALE);
|
||||
await setScale(page, FAST); // pure arrival wait per hop — a lagged tick can only land it sooner
|
||||
await expect.poll(async () => (await getState(page)).planes.find((p) => p.id === 'plane-starter')?.locationIcao ?? '', { timeout: 240_000 }).toBe(next);
|
||||
}
|
||||
}
|
||||
@@ -521,9 +524,12 @@ test('plane marker stays continuous across the antimeridian (no 360° teleport)'
|
||||
const pkg = await createPackage(page, ORIGIN, CROSS_DEST);
|
||||
expect((await page.request.post(`/api/packages/${pkg.id}/load`, { data: { planeId: 'plane-starter' } })).status()).toBe(200);
|
||||
console.log(`[antimeridian] pkg=${pkg.id} leg ${ORIGIN}->${CROSS_DEST}; BEFORE dispatch: ${fmtPlane((await getState(page)).planes.find((x) => x.id === 'plane-starter')!)}`);
|
||||
// The crossing leg must run at SCALE (1000×): the freshness gate (~700 km ≈ 5 s of flight) and the
|
||||
// >3 fresh samples both assume ~14 s of real-time flight. Set it BEFORE dispatch — a leftover FAST
|
||||
// rate would let the dispatch tick itself consume most of the leg before this setScale lands.
|
||||
await setScale(page, SCALE);
|
||||
const fly = await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: CROSS_DEST } });
|
||||
if (fly.status() !== 200) throw new Error(`dispatch POST /api/planes/plane-starter/fly → ${fly.status()} (expected 200)`);
|
||||
await setScale(page, SCALE);
|
||||
console.log(`[antimeridian] AFTER dispatch: ${fmtPlane((await getState(page)).planes.find((x) => x.id === 'plane-starter')!)}`);
|
||||
|
||||
// querySourceFeatures only sees LOADED tiles — the v0.70.0 default view ([0,0] z2) doesn't cover this
|
||||
@@ -591,16 +597,16 @@ test('plane marker stays continuous across the antimeridian (no 360° teleport)'
|
||||
expect(lons.some((lon) => Math.abs(lon) > 180), `sampled lons (n=${lons.length}): ${JSON.stringify(lons)}`).toBe(true); // crossed onto a world copy, either side of ±180
|
||||
expect(Math.max(...lons) - Math.min(...lons), `lon span: min=${Math.min(...lons).toFixed(4)} max=${Math.max(...lons).toFixed(4)} over ${JSON.stringify(lons)}`).toBeGreaterThan(4); // traversed the crossing region, not a static window
|
||||
} finally {
|
||||
// Cleanup: let any in-flight leg land, then chain back home so the other specs start from the canonical
|
||||
// state. The package was delivered on arrival (generous deadline).
|
||||
await setScale(page, SCALE);
|
||||
// Cleanup: let any in-flight leg land (pure arrival wait → FAST), then chain back home so the other
|
||||
// specs start from the canonical state. The package was delivered on arrival (generous deadline).
|
||||
await setScale(page, FAST);
|
||||
await expect.poll(async () => (await getState(page)).planes.find((p) => p.id === 'plane-starter')?.status ?? '', { timeout: 240_000 }).toBe('available');
|
||||
const endLoc = (await getState(page)).planes.find((p) => p.id === 'plane-starter')!.locationIcao;
|
||||
if (endLoc !== HUB) {
|
||||
for (const next of await chainTo(page, endLoc, HUB)) {
|
||||
await setFuelFull(page, 'plane-starter');
|
||||
expect((await page.request.post('/api/planes/plane-starter/fly', { data: { destIcao: next } })).status()).toBe(200);
|
||||
await setScale(page, SCALE);
|
||||
await setScale(page, FAST); // pure arrival wait per hop — a lagged tick can only land it sooner
|
||||
await expect.poll(async () => (await getState(page)).planes.find((p) => p.id === 'plane-starter')?.locationIcao ?? '', { timeout: 240_000 }).toBe(next);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user