leaflet_svelte/tests/e2e/workspaces.spec.ts
2026-08-03 22:12:32 +09:00

126 lines
4.4 KiB
TypeScript

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);
});