ghostguild-org/e2e/auth.spec.js
Jennie Robinson Faber 521efb0890 fix(a11y,test): USelect placeholder contrast + auth logout hydration wait
a11y (main.css):
- Nuxt UI's default placeholder color (text-dimmed = #a6a09b) failed WCAG
  AA contrast on cream (2.43:1) and white (2.58:1) backgrounds, blocking
  axe checks on /member/profile (timezone) and /admin/events/create
  (tags). Override [data-slot="placeholder"] globally to var(--text-dim)
  (#5a5040), comfortably above 4.5:1 on both surfaces.

auth.spec.js (logout):
- Same hydration race as the board/admin-board-channels click tests:
  /admin's sidebar Sign-out @click handler isn't bound when Playwright
  fires the click immediately after admin-tag visibility, so the click
  no-ops and waitForResponse for /api/auth/logout times out.
- Add waitForLoadState('networkidle') after goto so hydration completes
  before the click.
2026-04-26 18:30:32 +01:00

65 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect } from '@playwright/test'
import { loginAsAdmin, loginAsMember } from './helpers/auth.js'
test.describe('Authentication flows', () => {
test('protected page shows sign-in prompt when logged out', async ({ page }) => {
// Navigate to a protected member page without being logged in
await page.goto('/member/dashboard')
// Modal auto-opens on load; close it via the × button and wait for it to dismiss
await page.locator('.modal-close').click()
await expect(page.getByRole('dialog')).toBeHidden({ timeout: 5000 })
// Page shows the unauth state with sign-in button
await expect(page.getByRole('heading', { name: 'Sign in required' })).toBeVisible({ timeout: 10000 })
await expect(page.getByRole('button', { name: 'Sign In' })).toBeVisible()
// Clicking Sign In re-opens the login modal with email input
await page.getByRole('button', { name: 'Sign In' }).click()
await expect(page.locator('.modal-title')).toBeVisible({ timeout: 5000 })
await expect(page.locator('input[type="email"]')).toBeVisible()
})
test('admin login sets auth cookie', async ({ page }) => {
await loginAsAdmin(page)
// Verify cookie was set
const cookies = await page.context().cookies()
const authCookie = cookies.find((c) => c.name === 'auth-token')
expect(authCookie).toBeTruthy()
// Navigate to admin page — should show admin layout
await page.goto('/admin')
await expect(page.locator('.admin-tag')).toBeVisible({ timeout: 15000 })
})
test('member login sets auth cookie', async ({ page }) => {
await loginAsMember(page, 'test-admin@ghostguild.dev')
const cookies = await page.context().cookies()
const authCookie = cookies.find((c) => c.name === 'auth-token')
expect(authCookie).toBeTruthy()
})
test('logout clears auth', async ({ page }) => {
await loginAsAdmin(page)
await page.goto('/admin')
await page.waitForLoadState('networkidle')
await expect(page.locator('.admin-tag')).toBeVisible({ timeout: 15000 })
// Set up response listener BEFORE clicking to avoid race
const logoutResponse = page.waitForResponse((resp) => resp.url().includes('/api/auth/logout'))
// Click the "Sign out" link in the sidebar meta area
await page.locator('.sidebar-meta a').filter({ hasText: 'Sign out' }).click()
// Wait for the logout API call to complete
await logoutResponse
// Navigating to a protected page should show the sign-in prompt
await page.goto('/member/dashboard')
await page.locator('.modal-close').click()
await expect(page.getByRole('dialog')).toBeHidden({ timeout: 5000 })
await expect(page.getByRole('heading', { name: 'Sign in required' })).toBeVisible({ timeout: 10000 })
})
})