126 lines
4.4 KiB
TypeScript
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);
|
|
});
|