import { expect, test, type Page } from '@playwright/test'; import { loginAsAdmin, logout } from '../fixtures/auth'; // Phase B1 — Auth & Navigation. Every interaction with the login form // and the layout-level redirects, plus the obvious adversarial inputs. const VALID_USERNAME = process.env.E2E_ADMIN_USERNAME ?? 'admin'; const VALID_PASSWORD = process.env.E2E_ADMIN_PASSWORD ?? 'admin'; function failOnDialog(page: Page): void { page.on('dialog', async (dialog) => { await dialog.dismiss(); throw new Error(`Unexpected browser dialog fired: ${dialog.type()} — "${dialog.message()}"`); }); } test.describe('B1 auth — unauthenticated', () => { test.use({ storageState: { cookies: [], origins: [] } }); test('valid credentials land on the apps list', async ({ page }) => { await loginAsAdmin(page); await expect(page.getByRole('heading', { name: 'Apps', level: 1 })).toBeVisible(); }); test('wrong password shows an inline error and stays on /login', async ({ page }) => { await page.goto('/admin/login'); await page.getByLabel('Username').fill(VALID_USERNAME); await page.getByLabel('Password').fill('definitely-not-the-password'); await page.getByRole('button', { name: /sign in/i }).click(); const error = page.locator('.error'); await expect(error).toBeVisible(); await expect(error).not.toHaveText(''); await expect(page).toHaveURL(/\/admin\/login$/); // localStorage must remain empty — a failed login should not // leak a session token. const token = await page.evaluate(() => localStorage.getItem('picloud.admin.token')); expect(token).toBeNull(); }); test('empty submit is blocked by the browser and does not navigate', async ({ page }) => { await page.goto('/admin/login'); await page.getByRole('button', { name: /sign in/i }).click(); // HTML5 validation prevents submission; URL is unchanged and the // username input is reported invalid. await expect(page).toHaveURL(/\/admin\/login$/); const usernameInvalid = await page .getByLabel('Username') .evaluate((el: HTMLInputElement) => !el.validity.valid); expect(usernameInvalid).toBe(true); await expect(page.locator('.error')).toBeHidden(); }); test('visiting an authed route redirects to /login', async ({ page }) => { await page.goto('/admin/apps'); await expect(page).toHaveURL(/\/admin\/login$/); await expect(page.getByLabel('Username')).toBeVisible(); }); test('password field is type=password (no plaintext echo)', async ({ page }) => { await page.goto('/admin/login'); await expect(page.getByLabel('Password')).toHaveAttribute('type', 'password'); }); test('xss payload in username is escaped and does not execute', async ({ page }) => { failOnDialog(page); const payload = ''; await page.goto('/admin/login'); await page.getByLabel('Username').fill(payload); await page.getByLabel('Password').fill('whatever'); await page.getByRole('button', { name: /sign in/i }).click(); // Whatever the API does with that input, the page must remain // safe: no script tag injected into the DOM, no global side // effect, and a visible error (since the credentials don't // match any user). await expect(page.locator('.error')).toBeVisible(); const xssRan = await page.evaluate( () => (window as unknown as { __xss?: boolean }).__xss === true ); expect(xssRan).toBe(false); const injectedScript = await page.locator('script:has-text("__xss")').count(); expect(injectedScript).toBe(0); // The form must still be functional after the rejected attempt. await page.getByLabel('Username').fill(''); await page.getByLabel('Username').fill(VALID_USERNAME); await page.getByLabel('Password').fill(''); await page.getByLabel('Password').fill(VALID_PASSWORD); await page.getByRole('button', { name: /sign in/i }).click(); await expect(page).toHaveURL(/\/admin\/apps$/); }); }); test.describe('B1 auth — authenticated', () => { test('visiting /login while signed in bounces to /apps', async ({ page }) => { await page.goto('/admin/login'); await expect(page).toHaveURL(/\/admin\/apps$/); }); test('logout clears the session and lands on /login', async ({ page }) => { await page.goto('/admin/apps'); await expect(page.getByRole('heading', { name: 'Apps', level: 1 })).toBeVisible(); await logout(page); const token = await page.evaluate(() => localStorage.getItem('picloud.admin.token')); expect(token).toBeNull(); // And the authed area is now gated again. await page.goto('/admin/apps'); await expect(page).toHaveURL(/\/admin\/login$/); }); });