63 lines
2.5 KiB
TypeScript
63 lines
2.5 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();
|
|
}
|
|
|
|
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();
|
|
});
|