test: decisive hover diagnostics — browser snapshot vs node state + fleet HTML; CI fresh DB per run
CI / build + unit + e2e (pull_request) Failing after 6m3s
CI / build + unit + e2e (pull_request) Failing after 6m3s
This commit is contained in:
@@ -24,6 +24,9 @@ jobs:
|
||||
- name: Prepare e2e network
|
||||
run: |
|
||||
set -u
|
||||
# Interrupted runs skip the end-of-run cleanup, leaking a USED DB volume into later
|
||||
# runs (tests assume seeded state) — drop it so every run starts fresh.
|
||||
docker volume rm flight-ci_flight-db 2>/dev/null || true
|
||||
SUBNET=172.39.0.0/24
|
||||
if docker network inspect flight-ci-e2e >/dev/null 2>&1; then
|
||||
cur=$(docker network inspect flight-ci-e2e --format '{{(index .IPAM.Config 0).Subnet}}')
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
- e2e — new `e2e/hover.spec.ts` (3 tests, same SCALE/self-heal/`window.__flightMap` conventions as the other specs): fleet-row hover → `planes-highlight` renders exactly plane-starter, clears on mouseleave; open-package hover → `airports-highlight` shows both endpoint ICAOs (hook-created EDDF→LEMD package), clears on mouseleave; on-board block of a flying plane → `routes-highlight` shows the leg's planeId (sim paused mid-flight so the ~10 s leg can't land under the assertions, then resumed + fly-home cleanup)
|
||||
- Verified: `tsc --noEmit` clean; unit suite **238/238** green locally (no lib changes); e2e not run locally by design — CI verifies against an isolated stack
|
||||
- **E2e hover spec mechanics fixed after CI run #32** (`e2e/hover.spec.ts`, no app changes): the fleet-row wait now polls up to 30 s with a diagnostic that distinguishes "server has no plane-starter" from "DOM didn't render" (the browser's /api/state poll can lag node-side requests right after page load, leaving the pane in its loading state); the open-package test fits the map to both endpoint airports before asserting — `queryRenderedFeatures` only returns features inside the current viewport and the default Frankfurt-centered view leaves LEMD off-screen
|
||||
- **Decisive hover test-1 diagnostics (run #33 transient)**: the fleet-row poll now reports on every failed attempt what the NODE side sees (`/api/state` plane ids + cash via `page.request`) vs what the BROWSER last polled — new dev hook `window.__flightLastSnapshot` set in `lib/useGameState.ts` after each successful fetch (planeIds/cash/timeScale/timestamp, client-only) — plus the fleet section's HTML (`fleet-list` outerHTML or left-pane text), so a future failure can distinguish server divergence from a render bug
|
||||
- **CI removes the stale e2e DB volume at run start** (`.gitea/workflows/ci.yml`, "Prepare e2e network"): `docker volume rm flight-ci_flight-db 2>/dev/null || true` — an interrupted run skips the end-of-run cleanup and leaks its used game state into later runs (run #33's instance showed spent cash from a pre-#26 seed), while tests assume seeded state
|
||||
|
||||
## v0.14.0 — 2026-09-19 08:45
|
||||
- **Time-scale slider reworked to a 10-step ladder up to 1000× (ISS-006).** The old power-of-two ladder `1/2/4/8/16/32/64` is replaced by the discrete values `1, 2, 5, 10, 25, 50, 100, 250, 500, 1000` (each step roughly doubles the speed). The server clamp range widens from `[1, 64]` to `[1, 1000]` (`MAX_TIME_SCALE` in `lib/sim/engine.ts`, enforced by `clampTimeScale`; out-of-range values are still clamped, never rejected), and the OpenAPI spec's `timeScale` bounds + description follow. The slider mapping helpers in `lib/timeScale.ts` now place off-ladder scales at the nearest ladder step **by log distance** (the old `log2` formula assumed a pure power-of-two ladder); round-trip, monotonicity and endpoint-exactness are preserved
|
||||
|
||||
+16
-6
@@ -32,6 +32,7 @@ interface PackageState {
|
||||
}
|
||||
|
||||
interface StateBody {
|
||||
account: { cash: number; reputation: number };
|
||||
simState: { clockMs: number; timeScale: number; paused: boolean };
|
||||
planes: PlaneState[];
|
||||
packages: PackageState[];
|
||||
@@ -166,16 +167,25 @@ test('hovering a fleet row highlights the plane on the map', async ({ page }) =>
|
||||
const row = page.locator('li[data-testid="plane-starter"]');
|
||||
// The browser's /api/state polls can lag node-side requests right after a fresh page load
|
||||
// (useGameState keeps loading=true until the first success → no fleet rows yet), so poll with
|
||||
// diagnostics: on timeout the message distinguishes "server has no plane-starter" from
|
||||
// "DOM didn't render". (The value is returned, not thrown — expect.poll fails immediately
|
||||
// on a callback throw instead of retrying.)
|
||||
// diagnostics: on timeout the message compares what the NODE side sees vs what the BROWSER
|
||||
// last polled, plus the fleet section's HTML — distinguishing server divergence from a render
|
||||
// bug. (The value is returned, not thrown — expect.poll fails immediately on a callback throw
|
||||
// instead of retrying.)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
if ((await row.count()) > 0 && (await row.isVisible())) return 'visible';
|
||||
const body = await getState(page);
|
||||
const serverHasStarter = body.planes.some((p) => p.id === 'plane-starter');
|
||||
const paneHtml = (await page.locator('[data-testid="left-pane"]').first().innerHTML()).slice(0, 200);
|
||||
return `not visible — /api/state has plane-starter: ${serverHasStarter}; left-pane HTML: ${paneHtml}`;
|
||||
const browserSnap = await page.evaluate(() => (window as any).__flightLastSnapshot ?? null);
|
||||
const fleetHtml = await page.evaluate(() => {
|
||||
const el = document.querySelector('[data-testid="fleet-list"]');
|
||||
if (el) return `fleet-list HTML: ${(el.outerHTML ?? '').slice(0, 300)}`;
|
||||
const pane = document.querySelector<HTMLElement>('[data-testid="left-pane"]');
|
||||
return `no fleet-list — left-pane text: ${(pane?.innerText ?? '').slice(0, 200)}`;
|
||||
});
|
||||
return (
|
||||
`not visible — node /api/state planes=[${body.planes.map((p) => p.id).join(', ')}] cash=${body.account.cash}; ` +
|
||||
`browser __flightLastSnapshot=${JSON.stringify(browserSnap)}; ${fleetHtml}`
|
||||
);
|
||||
}, { timeout: 30_000 })
|
||||
.toBe('visible');
|
||||
|
||||
|
||||
+12
-1
@@ -21,7 +21,18 @@ export interface GameStateResult {
|
||||
export async function fetchState(): Promise<StateSnapshot> {
|
||||
const res = await fetch('/api/state', { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error(`GET /api/state → HTTP ${res.status}`);
|
||||
return (await res.json()) as StateSnapshot;
|
||||
const snapshot = (await res.json()) as StateSnapshot;
|
||||
// Dev/e2e hook (v0.15.0): the last successfully polled snapshot, so tests can compare what
|
||||
// the BROWSER received against a fresh node-side fetch. Client-only, harmless in prod.
|
||||
if (typeof window !== 'undefined') {
|
||||
(window as unknown as Record<string, unknown>).__flightLastSnapshot = {
|
||||
planeIds: snapshot.planes.map((p) => p.id),
|
||||
cash: snapshot.account.cash,
|
||||
timeScale: snapshot.simState.timeScale,
|
||||
at: Date.now(),
|
||||
};
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
// The shell's single polling source (v0.9.0): one GET /api/state loop for the whole page,
|
||||
|
||||
Reference in New Issue
Block a user