test: Add E2E test framework with Playwright
feat: Basic Homepage elements
This commit is contained in:
46
tests/e2e/auth.spec.ts
Normal file
46
tests/e2e/auth.spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { deleteAllMail, getMagicLink } from './helpers/mailpit'
|
||||
|
||||
const TEST_EMAIL = 'e2e-test@example.com'
|
||||
|
||||
test.beforeEach(async () => {
|
||||
await deleteAllMail()
|
||||
})
|
||||
|
||||
test.describe('Auth flow', () => {
|
||||
test('splash → login → magic link → home', async ({ page }) => {
|
||||
// Splash page shows logo and login button
|
||||
await page.goto('/')
|
||||
await expect(page.getByRole('img', { name: 'OYS Borrow a Boat' })).toBeVisible()
|
||||
await expect(page.getByRole('link', { name: 'Log In' })).toBeVisible()
|
||||
|
||||
// Navigate to login page
|
||||
await page.getByRole('link', { name: 'Log In' }).click()
|
||||
await expect(page).toHaveURL('/login')
|
||||
await expect(page.getByText('Sign In')).toBeVisible()
|
||||
|
||||
// Submit email for magic link
|
||||
await page.getByPlaceholder('you@example.com').fill(TEST_EMAIL)
|
||||
await page.getByRole('button', { name: 'Send Sign-In Link' }).click()
|
||||
await expect(page.getByText('Check your email')).toBeVisible()
|
||||
|
||||
// Fetch magic link from Mailpit, then follow the Supabase verify redirect
|
||||
// server-side (Node.js fetch) to get our app's callback URL.
|
||||
// Playwright's headless shell can't reach 127.0.0.1:54321 directly.
|
||||
const magicLink = await getMagicLink(TEST_EMAIL)
|
||||
const redirectRes = await fetch(magicLink, { redirect: 'manual' })
|
||||
const callbackUrl = redirectRes.headers.get('location')
|
||||
if (!callbackUrl) throw new Error('Supabase did not redirect to app callback')
|
||||
await page.goto(callbackUrl)
|
||||
|
||||
// Auth callback redirects to home (authenticated state)
|
||||
await expect(page).toHaveURL('/')
|
||||
await expect(page.getByText('Welcome to OYS Borrow a Boat')).toBeVisible()
|
||||
})
|
||||
|
||||
test('unauthenticated access to protected route redirects to splash', async ({ page }) => {
|
||||
await page.goto('/dashboard')
|
||||
await expect(page).toHaveURL('/')
|
||||
await expect(page.getByRole('link', { name: 'Log In' })).toBeVisible()
|
||||
})
|
||||
})
|
||||
54
tests/e2e/helpers/mailpit.ts
Normal file
54
tests/e2e/helpers/mailpit.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
const MAILPIT_URL = 'http://127.0.0.1:54324'
|
||||
|
||||
interface MailpitAddress {
|
||||
Address: string
|
||||
Name: string
|
||||
}
|
||||
|
||||
interface MailpitMessage {
|
||||
ID: string
|
||||
To: MailpitAddress[]
|
||||
Subject: string
|
||||
Date: string
|
||||
}
|
||||
|
||||
interface MailpitListResponse {
|
||||
messages: MailpitMessage[] | null
|
||||
total: number
|
||||
}
|
||||
|
||||
interface MailpitMessageDetail {
|
||||
Text: string
|
||||
HTML: string
|
||||
}
|
||||
|
||||
export async function deleteAllMail(): Promise<void> {
|
||||
await fetch(`${MAILPIT_URL}/api/v1/messages`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export async function getMagicLink(toEmail: string, timeoutMs = 15_000): Promise<string> {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
const res = await fetch(`${MAILPIT_URL}/api/v1/messages`)
|
||||
if (res.ok) {
|
||||
const data: MailpitListResponse = await res.json()
|
||||
const msg = (data.messages ?? []).find(m =>
|
||||
m.To.some(t => t.Address === toEmail),
|
||||
)
|
||||
if (msg) {
|
||||
const detail: MailpitMessageDetail = await fetch(
|
||||
`${MAILPIT_URL}/api/v1/message/${msg.ID}`,
|
||||
).then(r => r.json())
|
||||
|
||||
const body = detail.Text || detail.HTML
|
||||
// Supabase local auth confirmation URL
|
||||
const match = body.match(/https?:\/\/127\.0\.0\.1:54321\/auth\/v1\/verify\?[^\s"<]+/)
|
||||
if (match) return match[0]
|
||||
}
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
}
|
||||
|
||||
throw new Error(`No magic link email for ${toEmail} within ${timeoutMs}ms`)
|
||||
}
|
||||
Reference in New Issue
Block a user