test: hover spec — robust fleet-row wait + fit map to both endpoints before asserting
CI / build + unit + e2e (pull_request) Failing after 6m15s

This commit is contained in:
2026-09-19 10:06:48 +00:00
parent 3b6506832f
commit 3209dea425
2 changed files with 49 additions and 1 deletions
+48 -1
View File
@@ -126,6 +126,36 @@ async function unhover(page: Page): Promise<void> {
await page.getByTestId('wallet-cash').hover();
}
// Fit the map so all given airports are in-viewport — queryRenderedFeatures only returns
// features rendered inside the CURRENT viewport, and the default Frankfurt-centered view leaves
// far endpoints (LEMD) off-screen even when they match a layer filter.
async function fitToAirports(page: Page, icaos: string[]): Promise<void> {
const fc = (await (await page.request.get('/api/airports')).json()) as {
features: Array<{ properties: { icao: string; lat: number; lon: number } }>;
};
const points = icaos.map((icao) => {
const f = fc.features.find((x) => x.properties.icao === icao);
if (!f) throw new Error(`airport ${icao} not found in /api/airports`);
return [f.properties.lon, f.properties.lat] as [number, number];
});
await page.evaluate((pts: [number, number][]) => {
const m = (window as any).__flightMap;
if (!m) throw new Error('map not ready');
const lons = pts.map((p) => p[0]);
const lats = pts.map((p) => p[1]);
// duration 0 → instant camera jump, no animation to wait out.
m.fitBounds(
[
[Math.min(...lons), Math.min(...lats)],
[Math.max(...lons), Math.max(...lats)],
],
{ padding: 40, duration: 0 },
);
}, points);
// Give the renderer a beat after the camera jump before asserting on rendered features.
await page.waitForTimeout(300);
}
test('hovering a fleet row highlights the plane on the map', async ({ page }) => {
test.setTimeout(300_000); // self-heal can take minutes after an interrupted run
await flyHomeViaApi(page); // deterministic: the starter sits at EDDF, inside the initial view
@@ -134,7 +164,20 @@ test('hovering a fleet row highlights the plane on the map', async ({ page }) =>
await waitForAirportCount(page);
const row = page.locator('li[data-testid="plane-starter"]');
await expect(row).toBeVisible({ timeout: 15_000 });
// 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.)
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}`;
}, { timeout: 30_000 })
.toBe('visible');
// No highlight before hovering (the layers start filtered to match nothing).
expect(await renderedFeatures(page, ['planes-highlight'])).toHaveLength(0);
@@ -164,6 +207,10 @@ test('hovering an open package highlights its endpoint airports', async ({ page
const row = page.locator(`li[data-testid="open-package-${pkg.id}"]`);
await expect(row, 'the hook-created package appears in the list').toBeVisible({ timeout: 15_000 });
// Both endpoints must be in-viewport for queryRenderedFeatures to see them (LEMD is off-screen
// at the default Frankfurt-centered view).
await fitToAirports(page, [HUB, DEST]);
expect(await renderedFeatures(page, ['airports-highlight'])).toHaveLength(0);
await row.hover();