import { test, expect, type Page } from '@playwright/test'; async function login(page: Page) { await page.goto('/login/'); await page.fill('#lg-username', 'demo'); await page.fill('#lg-password', 'demo'); await page.click('button[type=submit]'); await expect(page.getByTestId('nav-menu')).toBeVisible(); } test('an ensemble run draws a probabilistic landing footprint', async ({ page }) => { await login(page); // The run is asynchronous: it returns a job, then the UI polls until it settles. await page.getByTestId('run-ensemble-btn').click(); await expect(page.getByTestId('ensemble-summary')).toBeVisible({ timeout: 20000 }); await expect(page.getByTestId('ensemble-summary')).toContainText('21'); // The footprint is its own scene: member scatter + mean + 95% ellipse. const ensembleLayers = () => page.evaluate(() => { const map = (window as unknown as { _sfMap?: { getStyle(): { layers: { id: string }[] } } }) ._sfMap; if (!map) return [] as string[]; return map .getStyle() .layers.map((l) => l.id) .filter((id) => id.startsWith('ens/')) .map((id) => id.split('__')[1]); }); await expect.poll(ensembleLayers).toContain('ellipse'); await expect.poll(ensembleLayers).toContain('mean'); // One dot per member. await expect .poll(async () => (await ensembleLayers()).filter((id) => id.startsWith('m-')).length) .toBe(21); });