89 lines
3.6 KiB
TypeScript
89 lines
3.6 KiB
TypeScript
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();
|
|
}
|
|
|
|
/** The builder is desktop-only, so pin a wide viewport before revealing it. */
|
|
async function openBuilder(page: Page) {
|
|
await page.setViewportSize({ width: 1400, height: 900 });
|
|
await login(page);
|
|
await page.selectOption('#cp-profile', 'custom_profile');
|
|
await expect(page.getByTestId('profile-builder')).toBeVisible();
|
|
}
|
|
|
|
test('the builder is revealed only by the custom profile', async ({ page }) => {
|
|
await page.setViewportSize({ width: 1400, height: 900 });
|
|
await login(page);
|
|
// Progressive disclosure: the standard profile shows no stage list.
|
|
await expect(page.getByTestId('profile-builder')).toHaveCount(0);
|
|
|
|
await page.selectOption('#cp-profile', 'custom_profile');
|
|
await expect(page.getByTestId('profile-builder')).toBeVisible();
|
|
// It starts from the standard flight expressed as stages.
|
|
await expect(page.getByTestId('stage-card')).toHaveCount(2);
|
|
});
|
|
|
|
test('stages can be added, reordered and removed', async ({ page }) => {
|
|
await openBuilder(page);
|
|
const names = () => page.getByTestId('stage-name').evaluateAll((els) =>
|
|
els.map((e) => (e as HTMLInputElement).value)
|
|
);
|
|
expect(await names()).toEqual(['ascent', 'descent']);
|
|
|
|
await page.getByTestId('add-stage').click();
|
|
await expect(page.getByTestId('stage-card')).toHaveCount(3);
|
|
|
|
// Reordering is by buttons so it stays keyboard-reachable.
|
|
await page.getByTestId('stage-down').first().click();
|
|
expect(await names()).toEqual(['descent', 'ascent', 'stage-3']);
|
|
|
|
await page.getByTestId('stage-delete').last().click();
|
|
await expect(page.getByTestId('stage-card')).toHaveCount(2);
|
|
});
|
|
|
|
test('a stage with no exit is reported as invalid', async ({ page }) => {
|
|
await openBuilder(page);
|
|
await expect(page.getByTestId('profile-problems')).toHaveCount(0);
|
|
|
|
// Remove the ascent stage's only "advance when" condition.
|
|
await page.getByTestId('advance-row').first().getByRole('button').click();
|
|
await expect(page.getByTestId('profile-problems')).toContainText(/условия выхода|no exit/i);
|
|
});
|
|
|
|
test('an abort constraint must point at an existing stage', async ({ page }) => {
|
|
await openBuilder(page);
|
|
await page.getByTestId('add-abort').first().click();
|
|
// It defaults to falling back to the first stage, which is itself — pick another.
|
|
const fallback = page.getByTestId('abort-fallback').first();
|
|
await fallback.selectOption('descent');
|
|
await expect(page.getByTestId('profile-problems')).toHaveCount(0);
|
|
|
|
await fallback.selectOption('');
|
|
await expect(page.getByTestId('profile-problems')).toContainText(/Резервная|fallback/i);
|
|
});
|
|
|
|
test('a custom profile runs through the v2 endpoint and draws a trajectory', async ({ page }) => {
|
|
await openBuilder(page);
|
|
// Change the ascent rate through the stage, not the flat form.
|
|
await page.getByTestId('stage-rate').first().fill('7');
|
|
|
|
const v2 = page.waitForRequest(
|
|
(r) => r.url().includes('/api/predictions/v2/') && r.method() === 'POST'
|
|
);
|
|
await page.getByTestId('run-btn').click();
|
|
const request = await v2;
|
|
|
|
// The request carries the stage list in the predictor's own shape.
|
|
const body = request.postDataJSON();
|
|
expect(body.profile).toHaveLength(2);
|
|
expect(body.profile[0].model).toMatchObject({ type: 'constant_rate', rate: 7 });
|
|
expect(body.profile[1].model.type).toBe('parachute_descent');
|
|
expect(body.launch).toHaveProperty('time');
|
|
|
|
await expect(page.getByTestId('flight-time')).toBeVisible({ timeout: 15000 });
|
|
});
|