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