import { test, expect, openPredict, login, sceneObjectCount } from './fixtures'; test.beforeEach(async ({ context, page }) => { await login(context); await page.goto('/'); await page.evaluate(() => localStorage.removeItem('workspaces')); }); function workspacesPanel(page: import('@playwright/test').Page) { return page.locator('.panel-container-right'); } test('a workspace is auto-created on first visit to /predict', async ({ page }) => { await openPredict(page); const panel = workspacesPanel(page); await expect(panel.locator('.workspace-row').first()).toBeVisible({ timeout: 10_000 }); await expect(panel.getByRole('button', { name: /Рассчитать|Run/ }).first()).toBeVisible(); }); test('can add and remove workspaces', async ({ page }) => { await openPredict(page); const panel = workspacesPanel(page); await expect(panel.locator('.workspace-row')).toHaveCount(1, { timeout: 10_000 }); const addBtn = panel.getByRole('button', { name: /Добавить|Add/ }).first(); await addBtn.click(); await expect(panel.locator('.workspace-row')).toHaveCount(2); await addBtn.click(); await expect(panel.locator('.workspace-row')).toHaveCount(3); // Delete the last workspace via its row's delete button. const deleteBtn = panel.locator('.workspace-row').last().locator('button.btn-danger'); await deleteBtn.click(); // Confirm in the modal. await page .getByRole('dialog') .getByRole('button', { name: /^(Удалить|Delete)$/ }) .click(); await expect(panel.locator('.workspace-row')).toHaveCount(2); }); test('workspace render pipeline adds a map scene after a run', async ({ page }) => { test.setTimeout(90_000); await openPredict(page); // Wait for the Run button to be enabled before clicking. const runBtn = workspacesPanel(page) .locator('.workspace-row') .first() .getByRole('button', { name: /Рассчитать|Run/ }); await runBtn.click(); // Wait for the prediction request to complete and layers to be added. await expect .poll(() => sceneObjectCount(page, 'ws/'), { timeout: 75_000, intervals: [1000, 2000, 3000], }) .toBeGreaterThan(0); }); // Regression: a flight is only tens of km across, which is a few dozen pixels // at the default camera height — the track ends up hidden under its own launch/ // burst/landing markers and looks like a single dot. A fresh result must be // framed by the camera. test('camera frames the trajectory after a run', async ({ page }) => { test.setTimeout(120_000); await openPredict(page); const camera = () => page.evaluate(() => { /* eslint-disable @typescript-eslint/no-explicit-any */ const v: any = (window as any)._lsvMap; const c = v.camera.positionCartographic; return { lng: (c.longitude * 180) / Math.PI, lat: (c.latitude * 180) / Math.PI, height: c.height, }; /* eslint-enable @typescript-eslint/no-explicit-any */ }); const before = await camera(); await workspacesPanel(page) .locator('.workspace-row') .first() .getByRole('button', { name: /Рассчитать|Run/ }) .click(); await expect .poll(() => sceneObjectCount(page, 'ws/'), { timeout: 90_000, intervals: [1000, 2000, 3000], }) .toBeGreaterThan(0); // Give the framing flight time to finish. await expect .poll(async () => (await camera()).height, { timeout: 20_000, intervals: [500, 1000] }) .toBeLessThan(before.height / 2); const after = await camera(); // The camera must sit inside the track's own bounds, not at the default centre. const bounds = await page.evaluate(() => { /* eslint-disable @typescript-eslint/no-explicit-any */ const v: any = (window as any)._lsvMap; const C: any = v.camera.positionCartographic.constructor; const la: number[] = []; const lo: number[] = []; for (const e of v.entities.values) { if (!String(e.id).endsWith('__path')) continue; for (const p of e.polyline.positions.getValue(v.clock.currentTime)) { const c = C.fromCartesian(p); la.push((c.latitude * 180) / Math.PI); lo.push((c.longitude * 180) / Math.PI); } } return { latMin: Math.min(...la), latMax: Math.max(...la), lngMin: Math.min(...lo), lngMax: Math.max(...lo) }; /* eslint-enable @typescript-eslint/no-explicit-any */ }); expect(after.lat).toBeGreaterThanOrEqual(bounds.latMin - 0.5); expect(after.lat).toBeLessThanOrEqual(bounds.latMax + 0.5); expect(after.lng).toBeGreaterThanOrEqual(bounds.lngMin - 0.5); expect(after.lng).toBeLessThanOrEqual(bounds.lngMax + 0.5); });