Repository Public
1import { expect, test } from '@playwright/test';2import { apiLogin, resetMock, SEED, 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 expect(page.getByRole('heading', { name: 'Set up Agent OS' })).toBeVisible();16 await page.getByTestId('setup-token').fill('mock-setup-token-0000');17 await page.getByTestId('setup-username').fill('owner');18 await page.getByTestId('setup-password').fill('short');19 await expect(page.getByTestId('setup-submit')).toBeDisabled(); // < 12 characters20 await page.getByTestId('setup-password').fill('a-long-passphrase-42');21 await page.getByTestId('setup-confirm').fill('a-long-passphrase-42');22 await page.getByTestId('setup-submit').click();2324 // the account is not usable yet: the app shell refuses to load before a second factor exists25 await expect(page.getByTestId('second-factor-enroll')).toBeVisible();26 expect((await (await page.request.get('/api/v1/auth/status')).json()).authenticated).toBe(false);27 await page.getByTestId('totp-start').click();28 await expect(page.locator('canvas[role="img"]')).toBeVisible();29 expect(await page.locator('canvas[role="img"]').evaluate((c: HTMLCanvasElement) => c.width)).toBeGreaterThan(100);30 const secret = (await page.getByTestId('totp-secret').getAttribute('data-secret'))!;31 await page.getByTestId('totp-enroll-code').fill(totp(secret));32 await page.getByRole('button', { name: 'Verify and enable' }).click();33 await expect(page).toHaveURL(/\/home\//);34 // a fresh instance: honest empty states, no invented data35 await expect(page.getByTestId('needs-you-empty')).toBeVisible();36 await expect(page.getByText('No mission is running')).toBeVisible();37 await expect(page.getByTestId('today-usd')).toHaveText('$0.00');38 expect(await page.evaluate(() => JSON.stringify({ ...localStorage }) + JSON.stringify({ ...sessionStorage }))).not.toMatch(/csrf|token|secret|passphrase/i);39 w.expectClean();40});4142test('sign in with password then TOTP; wrong code is refused; sign out', async ({ page }) => {43 const w = watch(page);44 await resetMock(page, true);45 await page.goto('/missions/');46 await expect(page).toHaveURL(/\/login\/\?next=/);47 await expect(page.getByText('Sign in to Agent OS')).toBeVisible();48 await page.getByTestId('login-username').fill(SEED.username);49 await page.getByTestId('login-password').fill(SEED.password);50 await page.getByTestId('login-submit').click();51 const code = page.getByLabel('Authentication code');52 await code.fill('000000');53 await page.getByTestId('totp-submit').click();54 await expect(page.getByTestId('login-error')).toBeVisible();55 await code.fill(totp(SEED.totpSecret));56 await page.getByTestId('totp-submit').click();57 await expect(page).toHaveURL(/\/missions\//); // back where the owner was going58 await expect(page.getByTestId('wordmark')).toHaveText('Agent OS');59 await expect(page).toHaveTitle('Agent OS');60 await page.getByRole('button', { name: 'Sign out' }).click();61 await expect(page).toHaveURL(/\/login\//);62 w.expectClean();63});6465test('offline: the cached shell loads and says missions keep running; /api is never cached', async ({ page, context }) => {66 await resetMock(page, true);67 await apiLogin(page);68 await page.goto('/home/');69 await expect(page.getByTestId('needs-you')).toBeVisible();70 await page.evaluate(async () => { await navigator.serviceWorker.ready; });71 await page.reload();72 await expect(page.getByTestId('needs-you')).toBeVisible();73 await expect.poll(() => page.evaluate(() => !!navigator.serviceWorker.controller)).toBe(true);74 const cached = await page.evaluate(async () => {75 const out: string[] = [];76 for (const name of await caches.keys()) for (const req of await (await caches.open(name)).keys()) out.push(`${name}:${new URL(req.url).pathname}`);77 return out;78 });79 expect(cached.every((p) => p.startsWith('aos-shell-'))).toBe(true);80 expect(cached.some((p) => p.includes('/_next/static/'))).toBe(true);81 expect(cached.some((p) => p.endsWith(':/home/'))).toBe(true);82 expect(cached.filter((p) => p.includes('/api'))).toEqual([]);8384 await context.setOffline(true);85 await page.reload();86 await expect(page.getByTestId('offline-state')).toHaveText('Offline — missions keep running on the server');87 await context.setOffline(false);88 await page.getByRole('button', { name: 'Retry' }).click();89 await expect(page.getByTestId('needs-you')).toBeVisible();90});91