Repository Public
1import { expect, test } from '@playwright/test';2import { apiLogin, resetMock, totp, watch } from './helpers';34test.afterAll(async ({ browser }) => {5 const page = await browser.newPage();6 await resetMock(page, true);7 await page.close();8});910test('first-run setup: token + account, mandatory TOTP enrolment with a locally rendered QR', async ({ page }) => {11 const w = watch(page);12 await resetMock(page, false);13 await page.goto('/');14 await expect(page).toHaveURL(/\/setup\//);15 await page.getByTestId('setup-token').fill('mock-setup-token-0000');16 await page.getByTestId('setup-username').fill('owner');17 await page.getByTestId('setup-password').fill('short');18 await expect(page.getByTestId('setup-submit')).toBeDisabled(); // < 12 characters19 await page.getByTestId('setup-password').fill('a-long-passphrase-42');20 await page.getByTestId('setup-confirm').fill('a-long-passphrase-42');21 await page.getByTestId('setup-submit').click();2223 // the account is not usable yet: the app shell refuses to load before a second factor exists24 await expect(page.getByTestId('second-factor-enroll')).toBeVisible();25 expect((await (await page.request.get('/api/v1/auth/status')).json()).authenticated).toBe(false);26 await page.getByTestId('totp-start').click();27 await expect(page.locator('canvas[role="img"]')).toBeVisible();28 expect(await page.locator('canvas[role="img"]').evaluate((c: HTMLCanvasElement) => c.width)).toBeGreaterThan(100);29 const secret = (await page.getByTestId('totp-secret').getAttribute('data-secret'))!;30 await page.getByTestId('totp-enroll-code').fill(totp(secret));31 await page.getByRole('button', { name: 'Verify and enable' }).click();32 await expect(page).toHaveURL(/\/terminal\//);33 await expect(page.getByTestId('no-session')).toBeVisible();34 expect(await page.evaluate(() => JSON.stringify({ ...localStorage }) + JSON.stringify({ ...sessionStorage }))).not.toMatch(/csrf|token|secret|passphrase/i);35 w.expectClean();36});3738test('offline: the cached shell loads and says the remote session keeps running; /api is never cached', async ({ page, context }) => {39 await resetMock(page, true);40 await apiLogin(page);41 await page.goto('/terminal/');42 await expect(page.getByTestId('no-session')).toBeVisible();43 // wait for the service worker to control the page and finish caching the shell44 await page.evaluate(async () => { await navigator.serviceWorker.ready; });45 await page.reload();46 await expect(page.getByTestId('no-session')).toBeVisible();47 await expect.poll(() => page.evaluate(() => !!navigator.serviceWorker.controller)).toBe(true);48 const cached = await page.evaluate(async () => {49 const out: string[] = [];50 for (const name of await caches.keys()) for (const req of await (await caches.open(name)).keys()) out.push(new URL(req.url).pathname);51 return out;52 });53 expect(cached.some((p) => p.startsWith('/_next/static/'))).toBe(true);54 expect(cached).toContain('/terminal/');55 expect(cached.filter((p) => p.startsWith('/api'))).toEqual([]);5657 await context.setOffline(true);58 await page.reload();59 await expect(page.getByTestId('offline-state')).toHaveText('Offline — the remote session keeps running on the server');60 await context.setOffline(false);61 await page.getByRole('button', { name: 'Retry' }).click();62 await expect(page.getByTestId('no-session')).toBeVisible();63});64