This commit is contained in:
gili8420 2026-08-14 00:01:49 +09:00
commit b43955e0d9
162 changed files with 15425 additions and 0 deletions

63
tests/e2e/export.spec.ts Normal file
View file

@ -0,0 +1,63 @@
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('a computed scenario exports as GPX, KML and CSV', async ({ page }) => {
await login(page);
await page.getByTestId('run-btn').click();
await expect(page.getByTestId('flight-time')).toBeVisible({ timeout: 15000 });
for (const fmt of ['gpx', 'kml', 'csv'] as const) {
const download = page.waitForEvent('download');
await page.getByTestId(`export-${fmt}`).click();
const file = await download;
expect(file.suggestedFilename()).toMatch(new RegExp(`\\.${fmt}$`));
}
});
test('a shared link opens read-only without a session and can be revoked', async ({
page,
context
}) => {
await login(page);
await page.getByTestId('run-btn').click();
await expect(page.getByTestId('flight-time')).toBeVisible({ timeout: 15000 });
// Mint a share link from the history row.
await page.goto('/user/predictions/');
await context.grantPermissions(['clipboard-read', 'clipboard-write']);
await page.getByTestId('history-share').first().click();
await expect(page.locator('.toast')).toBeVisible();
const url = await page.evaluate(() => navigator.clipboard.readText());
expect(url).toMatch(/\/p\/[0-9a-f-]{36}\//);
// The link resolves for a visitor with no session at all.
const anon = await context.browser()!.newContext();
const anonPage = await anon.newPage();
await anonPage.goto(url);
await expect(anonPage.getByTestId('shared-summary')).toBeVisible({ timeout: 15000 });
// It is read-only: no run controls, but the data is still exportable.
await expect(anonPage.getByTestId('run-btn')).toHaveCount(0);
await expect(anonPage.getByTestId('shared-export-gpx')).toBeVisible();
// Revoking breaks the link. The share endpoint is keyed by the prediction id
// (the token only grants read access), so take the id from the shared page.
const predictionId = await anonPage.evaluate(async (u: string) => {
const token = u.split('/p/')[1].replace(/\/$/, '');
const res = await fetch(`/api/predictions/shared/${token}/`);
return (await res.json()).id as string;
}, url);
const revoke = await page.request.delete(`/api/predictions/${predictionId}/share/`);
expect(revoke.ok()).toBe(true);
await anonPage.reload();
await expect(anonPage.getByTestId('shared-error')).toBeVisible();
await anon.close();
});