import { expect, test, type Page } from '@playwright/test'; const PASSWORD = 'Sup3rSecure!123'; test.describe.configure({ mode: 'serial' }); function createDeferred() { let resolve: (() => void) | null = null; const promise = new Promise((done) => { resolve = done; }); return { promise, resolve: () => resolve?.() }; } function uniqueEmail(prefix: string) { return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}@example.com`; } async function gotoAuthPage(page: Page, path: string) { await page.goto(path, { waitUntil: 'domcontentloaded' }); await page.waitForLoadState('networkidle'); } async function signUp(page: Page, email: string, path = '/auth/signup') { await gotoAuthPage(page, path); await page.locator('input[autocomplete="name"]').fill('Playwright User'); await page.locator('input[autocomplete="email"]').fill(email); await page.locator('input[autocomplete="new-password"]').first().fill(PASSWORD); await page.locator('input[autocomplete="new-password"]').nth(1).fill(PASSWORD); await page.getByRole('button', { name: 'Create account' }).click(); } async function signIn(page: Page, email: string, path = '/auth/signin') { await gotoAuthPage(page, path); await page.locator('input[autocomplete="email"]').fill(email); await page.locator('input[autocomplete="current-password"]').fill(PASSWORD); await page.getByRole('button', { name: 'Sign in with password' }).click(); } async function expectStableDashboard(page: Page) { await expect(page.getByRole('heading', { name: 'Command Center' })).toBeVisible({ timeout: 30_000 }); await expect(page).toHaveURL(/\/$/, { timeout: 30_000 }); await page.waitForTimeout(1_000); expect(page.url()).not.toContain('/auth/signin'); } async function expectStableProtectedRoute(page: Page, pattern: RegExp) { await expect(page).toHaveURL(pattern, { timeout: 30_000 }); await page.waitForTimeout(1_000); expect(page.url()).not.toContain('/auth/signin'); } async function signOut(page: Page) { await page.getByRole('button', { name: 'Sign out' }).first().click(); await expect(page).toHaveURL(/\/auth\/signin/, { timeout: 30_000 }); } test('preserves the return path while switching between auth screens and shows the expected controls', async ({ page }) => { await gotoAuthPage(page, '/auth/signin?next=%2Fanalysis%3Fticker%3DNVDA'); await expect(page.getByRole('heading', { name: 'Secure Sign In' })).toBeVisible(); await expect(page.getByText('Use email/password or request a magic link.')).toBeVisible(); await expect(page.locator('input[autocomplete="email"]')).toBeVisible(); await expect(page.locator('input[autocomplete="current-password"]')).toBeVisible(); await expect(page.getByRole('button', { name: 'Sign in with password' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Send magic link' })).toBeVisible(); await expect(page.getByRole('link', { name: 'Create one' })).toHaveAttribute('href', '/auth/signup?next=%2Fanalysis%3Fticker%3DNVDA'); await page.getByRole('link', { name: 'Create one' }).click(); await expect(page).toHaveURL(/\/auth\/signup\?next=%2Fanalysis%3Fticker%3DNVDA$/); await expect(page.getByRole('heading', { name: 'Create Account' })).toBeVisible(); await expect(page.getByText('Set up your operator profile to access portfolio and filings intelligence.')).toBeVisible(); await expect(page.locator('input[autocomplete="name"]')).toBeVisible(); await expect(page.locator('input[autocomplete="email"]')).toBeVisible(); await expect(page.locator('input[autocomplete="new-password"]').first()).toBeVisible(); await expect(page.getByRole('button', { name: 'Create account' })).toBeVisible(); await expect(page.getByRole('link', { name: 'Sign in' })).toHaveAttribute('href', '/auth/signin?next=%2Fanalysis%3Fticker%3DNVDA'); }); test('shows client-side validation when signup passwords do not match', async ({ page }) => { await gotoAuthPage(page, '/auth/signup'); await page.locator('input[autocomplete="name"]').fill('Playwright User'); await page.locator('input[autocomplete="email"]').fill('mismatch@example.com'); await page.locator('input[autocomplete="new-password"]').first().fill(PASSWORD); await page.locator('input[autocomplete="new-password"]').nth(1).fill('NotTheSame123!'); await page.getByRole('button', { name: 'Create account' }).click(); await expect(page.getByText('Passwords do not match.')).toBeVisible(); }); test('shows loading affordances while sign-in is in flight', async ({ page }) => { const gate = createDeferred(); await page.route('**/api/auth/sign-in/email', async (route) => { await gate.promise; await route.fulfill({ status: 401, contentType: 'application/json', body: JSON.stringify({ message: 'Invalid credentials' }) }); }); await gotoAuthPage(page, '/auth/signin'); await page.locator('input[autocomplete="email"]').fill('playwright@example.com'); await page.locator('input[autocomplete="current-password"]').fill(PASSWORD); const submitButton = page.getByRole('button', { name: 'Sign in with password' }); const magicLinkButton = page.getByRole('button', { name: 'Send magic link' }); await submitButton.click(); await expect(page.getByRole('button', { name: 'Signing in...' })).toBeDisabled(); await expect(magicLinkButton).toBeDisabled(); gate.resolve(); await expect(page.getByText('Invalid credentials')).toBeVisible(); }); test('shows loading affordances while sign-up is in flight', async ({ page }) => { const gate = createDeferred(); await page.route('**/api/auth/sign-up/email', async (route) => { await gate.promise; await route.fulfill({ status: 409, contentType: 'application/json', body: JSON.stringify({ message: 'Email already exists' }) }); }); await gotoAuthPage(page, '/auth/signup'); await page.locator('input[autocomplete="name"]').fill('Playwright User'); await page.locator('input[autocomplete="email"]').fill(uniqueEmail('playwright-loading')); await page.locator('input[autocomplete="new-password"]').first().fill(PASSWORD); await page.locator('input[autocomplete="new-password"]').nth(1).fill(PASSWORD); await page.getByRole('button', { name: 'Create account' }).click(); await expect(page.getByRole('button', { name: 'Creating account...' })).toBeDisabled(); gate.resolve(); await expect(page.getByText('Email already exists')).toBeVisible(); }); test('successful signup reaches the authenticated shell and stays there', async ({ page }) => { await signUp(page, uniqueEmail('playwright-signup-success')); await expectStableDashboard(page); }); test('successful signup preserves the requested next path', async ({ page }) => { await signUp(page, uniqueEmail('playwright-signup-next'), '/auth/signup?next=%2Fanalysis%3Fticker%3DNVDA'); await expectStableProtectedRoute(page, /\/analysis\?ticker=NVDA$/); }); test('successful sign-in reaches the authenticated shell and stays there', async ({ page }) => { const email = uniqueEmail('playwright-signin-success'); await signUp(page, email); await expectStableDashboard(page); await signOut(page); await signIn(page, email); await expectStableDashboard(page); }); test('authenticated users are redirected away from auth pages with hard navigation', async ({ page }) => { await signUp(page, uniqueEmail('playwright-authenticated-redirect')); await expectStableDashboard(page); await page.goto('/auth/signin', { waitUntil: 'domcontentloaded' }); await expectStableDashboard(page); await page.goto('/auth/signup?next=%2Fanalysis%3Fticker%3DNVDA', { waitUntil: 'domcontentloaded' }); await expectStableProtectedRoute(page, /\/analysis\?ticker=NVDA$/); }); test('shows the handoff state while waiting for the session to become visible', async ({ page }) => { const gate = createDeferred(); let holdSession = false; await page.route('**/api/auth/get-session**', async (route) => { if (holdSession) { await gate.promise; } await route.continue(); }); await gotoAuthPage(page, '/auth/signup'); holdSession = true; await page.locator('input[autocomplete="name"]').fill('Playwright User'); await page.locator('input[autocomplete="email"]').fill(uniqueEmail('playwright-handoff')); await page.locator('input[autocomplete="new-password"]').first().fill(PASSWORD); await page.locator('input[autocomplete="new-password"]').nth(1).fill(PASSWORD); await page.getByRole('button', { name: 'Create account' }).click(); await expect(page.getByRole('button', { name: 'Finishing sign-in...' })).toBeDisabled(); await expect(page.getByText('Establishing your session and opening the workspace...')).toBeVisible(); gate.resolve(); await expectStableDashboard(page); }); test('shows recovery guidance if session establishment never completes', async ({ page }) => { let forceMissingSession = false; await page.route('**/api/auth/get-session**', async (route) => { if (!forceMissingSession) { await route.continue(); return; } await route.fulfill({ status: 200, contentType: 'application/json', body: 'null' }); }); await gotoAuthPage(page, '/auth/signup'); forceMissingSession = true; await page.locator('input[autocomplete="name"]').fill('Playwright User'); await page.locator('input[autocomplete="email"]').fill(uniqueEmail('playwright-timeout')); await page.locator('input[autocomplete="new-password"]').first().fill(PASSWORD); await page.locator('input[autocomplete="new-password"]').nth(1).fill(PASSWORD); await page.getByRole('button', { name: 'Create account' }).click(); await expect(page.getByRole('button', { name: 'Finishing sign-in...' })).toBeDisabled(); await expect(page.getByText('Establishing your session and opening the workspace...')).toBeVisible(); await expect(page.getByText('Authentication completed, but the session was not established on this device. Please sign in again.')).toBeVisible({ timeout: 15_000 }); await expect(page).toHaveURL(/\/auth\/signup$/); await expect(page.getByRole('button', { name: 'Create account' })).toBeEnabled(); });