// The one spec that drives the real login and registration forms. Everything // else signs in over HTTP through the fixture in fixtures.ts. // // `anonTest` gives database seeding with an anonymous browser. import { anonTest as test, expect, E2E_PASSWORD } from './fixtures'; // Registration is rate limited to five requests per hour per IP, and the window // lives in Postgres. tests/e2e/global-setup.ts empties that table before each // run, so this file may spend a few of them; keep it to the two below. const INVITE_CODE = 'test-invite'; test('an anonymous visitor to a protected route lands on the login page', async ({ page }) => { await page.goto('/dashboard'); await expect(page).toHaveURL(/\/login$/); // `CardTitle` renders a
, not a heading, so there is no role to match. await expect(page.getByText('Welcome back')).toBeVisible(); }); test('registration is refused when the invite code is wrong', async ({ page }) => { await page.goto('/register'); await expect(page.getByText('Join OpenFrame to collaborate on video projects')).toBeVisible(); await page.getByLabel('Invite Code').fill('definitely-not-the-invite-code'); await page.getByLabel('Full Name').fill('Wrong Code Person'); await page.getByLabel('Email').fill(`e2e-rejected-${Date.now()}@example.com`); await page.getByLabel('Password', { exact: true }).fill(E2E_PASSWORD); await page.getByLabel('Confirm Password').fill(E2E_PASSWORD); await page.getByRole('button', { name: 'Create Account' }).click(); await expect(page.getByText('Invalid invite code')).toBeVisible(); await expect(page).toHaveURL(/\/register$/); }); test('a new account can be registered with the invite code and then signed in', async ({ page, seed, }) => { const email = `e2e-registered-${Date.now()}@example.com`; await page.goto('/register'); await page.getByLabel('Invite Code').fill(INVITE_CODE); await page.getByLabel('Full Name').fill('Freshly Registered'); await page.getByLabel('Email').fill(email); await page.getByLabel('Password', { exact: true }).fill(E2E_PASSWORD); await page.getByLabel('Confirm Password').fill(E2E_PASSWORD); await page.getByRole('button', { name: 'Create Account' }).click(); // SMTP is unset for the app under test, so isEmailVerificationEnabled() is // false and the account is auto-verified rather than parked on /verify-email. // // Deliberately tolerant of extra query parameters rather than anchored with // `$`. What the register flow promises is the login page plus `registered=true`; // the rest of the query string is not part of that contract. On CI the login // page arrives carrying `callbackUrl=%2Fdashboard`, which it derives from its // own default in getSafeCallbackUrl(null), and an anchored pattern turned that // into a deterministic CI-only failure while passing locally. await expect(page).toHaveURL(/\/login\?(?:.*&)?registered=true(?:&|$)/); await expect(page.getByText('Account created successfully!')).toBeVisible(); await page.getByLabel('Email').fill(email); await page.getByLabel('Password').fill(E2E_PASSWORD); await page.getByRole('button', { name: 'Sign in' }).click(); // /onboarding rather than /settings, which is the whole point of the cardless // trial: registration grants the trial (here at signup, because SMTP is unset // and there is no verification step to hang it on), so // requireBillingAccessOrRedirect() on /dashboard lets the account through to // the wizard instead of parking it on billing. await expect(page).toHaveURL(/\/onboarding$/); await expect( page.getByRole('heading', { name: 'Welcome to OpenFrame, Freshly!', level: 2 }) ).toBeVisible(); // The Seed fixture only tracks rows it created itself, so remove this one by // hand rather than leaving it behind for the next run. await seed.deleteUserByEmail(email); }); test('signing in with the wrong password is refused', async ({ page, seed }) => { const user = await seed.user(); await page.goto('/login'); await page.getByLabel('Email').fill(user.email ?? ''); await page.getByLabel('Password').fill('not-the-password'); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page.getByText('Invalid email or password')).toBeVisible(); await expect(page).toHaveURL(/\/login$/); }); test('signing in through the form reaches the dashboard, and signing out returns to login', async ({ page, seed, }) => { const user = await seed.user(); await page.goto('/login'); await page.getByLabel('Email').fill(user.email ?? ''); await page.getByLabel('Password').fill(E2E_PASSWORD); await page.getByRole('button', { name: 'Sign in' }).click(); await expect(page).toHaveURL(/\/dashboard$/); await expect(page.getByRole('heading', { name: 'Projects', level: 1 })).toBeVisible(); await page.goto('/signout'); await page.getByRole('button', { name: 'Sign out' }).click(); await expect(page).toHaveURL(/\/login$/); // The session really is gone, not just navigated away from. await page.goto('/dashboard'); await expect(page).toHaveURL(/\/login$/); });