Initial
This commit is contained in:
commit
b43955e0d9
162 changed files with 15425 additions and 0 deletions
75
tests/e2e/timeline.spec.ts
Normal file
75
tests/e2e/timeline.spec.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
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('the timeline activates after a run and scrubs the flight', async ({ page }) => {
|
||||
await login(page);
|
||||
|
||||
// No result yet → the scrubber is inert.
|
||||
const range = page.getByTestId('tl-range');
|
||||
await expect(range).toBeDisabled();
|
||||
|
||||
await page.getByTestId('run-btn').click();
|
||||
await expect(page.getByTestId('flight-time')).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// A result sets the global domain, enabling playback.
|
||||
await expect(range).toBeEnabled();
|
||||
await expect(page.getByTestId('tl-elapsed')).toHaveText('00:00:00');
|
||||
|
||||
// Seeking moves the clock (and with it every visible scenario's cursor).
|
||||
await range.fill('3600000'); // one hour into the flight
|
||||
await expect(page.getByTestId('tl-elapsed')).toHaveText('01:00:00');
|
||||
|
||||
// Play flips the control to pause, then pausing restores it.
|
||||
await page.getByTestId('tl-play').click();
|
||||
await expect(page.getByTestId('tl-pause')).toBeVisible();
|
||||
await page.getByTestId('tl-pause').click();
|
||||
await expect(page.getByTestId('tl-play')).toBeVisible();
|
||||
});
|
||||
|
||||
/**
|
||||
* Regression: durations are not whole seconds, so a stepped slider stopped just
|
||||
* short of max and the cursor stayed in its "in flight" state forever.
|
||||
*/
|
||||
test('scrubbing to the very end lands the cursor', async ({ page }) => {
|
||||
await login(page);
|
||||
// A rate that yields a duration which is NOT a whole number of seconds —
|
||||
// with a 1s-stepped slider the thumb stops short of max and never lands.
|
||||
await page.fill('#cp-ascent', '5.3');
|
||||
await page.getByTestId('run-btn').click();
|
||||
await expect(page.getByTestId('flight-time')).toBeVisible({ timeout: 15000 });
|
||||
|
||||
const cursorLayers = () =>
|
||||
page.evaluate(() => {
|
||||
// `_sfMap` is the raw MapLibre instance exposed by <Map /> in dev builds.
|
||||
const map = (window as unknown as { _sfMap: { getStyle(): { layers: { id: string }[] } } })
|
||||
._sfMap;
|
||||
return map
|
||||
.getStyle()
|
||||
.layers.map((l) => l.id)
|
||||
.filter((id) => id.startsWith('cursor/'))
|
||||
.map((id) => id.split('__')[1]);
|
||||
});
|
||||
|
||||
const range = page.getByTestId('tl-range');
|
||||
// The scenario card updates before the renderer publishes the clock domain,
|
||||
// so wait for the scrubber to go live or `max` still reads 0.
|
||||
await expect(range).toBeEnabled();
|
||||
const max = await range.getAttribute('max');
|
||||
expect(Number(max)).toBeGreaterThan(0);
|
||||
|
||||
// Rendering is effect-driven, so poll rather than asserting once.
|
||||
// Mid-flight: the animated (ring + core) marker.
|
||||
await range.fill(String(Number(max) / 2));
|
||||
await expect.poll(cursorLayers).toEqual(['marker-ring', 'marker-core']);
|
||||
|
||||
// At the end: swapped for the static landed marker.
|
||||
await range.fill(String(max));
|
||||
await expect.poll(cursorLayers).toEqual(['marker-core']);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue