Files
OpenFrame/tests/e2e/auth.spec.ts
T
yusufipek 364e60146d test(e2e): expect a fresh account to reach onboarding, not billing
The registration spec asserted that a brand new account lands on /settings,
which was true only because registering left trialEndsAt empty. It is now
filled at signup, so the account has billing access and the dashboard lets it
through to the onboarding wizard. That redirect is the done condition of the
cardless trial, so the spec should hold it.
2026-08-18 09:13:42 +03:00

116 lines
5.0 KiB
TypeScript

// 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 <div>, 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$/);
});