ghostguild-org/e2e/updates.spec.js
Jennie Robinson Faber c40f2c7c63 fix: accessibility improvements and test infrastructure hardening
Add aria-labels to form controls (selects, checkboxes, switches), set
html lang attribute and page title, fix color contrast for --candle-dim
and --text-faint tokens, underline inline links, remove opacity hack.
Harden dev login endpoints with atomic findOneAndUpdate and tokenVersion
in JWT. Update Playwright timeouts and E2E test helpers.
2026-04-05 21:59:02 +01:00

123 lines
4.4 KiB
JavaScript

import { test, expect } from './helpers/fixtures.js'
test.describe('My Updates page', () => {
test('authenticated user sees the my-updates page', async ({ adminPage }) => {
await adminPage.goto('/member/my-updates')
await expect(adminPage.locator('h1', { hasText: 'My Updates' })).toBeVisible({
timeout: 10000,
})
})
test('authenticated user sees the new update link', async ({ adminPage }) => {
await adminPage.goto('/member/my-updates')
// Wait for ClientOnly content to hydrate
await expect(adminPage.locator('h1', { hasText: 'My Updates' })).toBeVisible({
timeout: 10000,
})
// The page shows either the "+ New Update" button (stats row) or
// the "+ Post Your First Update" link (empty state) — both go to /updates/new
const newUpdateLink = adminPage.locator('a[href="/updates/new"]')
await expect(newUpdateLink.first()).toBeVisible({ timeout: 10000 })
})
test('unauthenticated user sees sign-in prompt', async ({ browser }) => {
const context = await browser.newContext()
const page = await context.newPage()
await page.goto('/member/my-updates')
// Should show the page's "Sign in required" heading
await expect(page.locator('.state-heading')).toBeVisible({ timeout: 10000 })
await context.close()
})
})
test.describe('New Update page', () => {
test('loads the new update form', async ({ adminPage }) => {
await adminPage.goto('/updates/new')
await expect(adminPage.locator('h1', { hasText: 'New Update' })).toBeVisible({
timeout: 10000,
})
// Form elements are present
await expect(adminPage.locator('textarea')).toBeVisible()
await expect(adminPage.locator('select')).toBeVisible()
// Submit button exists and starts disabled (empty textarea)
const submitBtn = adminPage.locator('button[type="submit"]', { hasText: 'Post Update' })
await expect(submitBtn).toBeVisible()
await expect(submitBtn).toBeDisabled()
})
test('submit button enables when content is entered', async ({ adminPage }) => {
await adminPage.goto('/updates/new')
await adminPage.waitForLoadState('networkidle')
await expect(adminPage.locator('h1', { hasText: 'New Update' })).toBeVisible({
timeout: 10000,
})
const textarea = adminPage.locator('textarea')
const submitBtn = adminPage.locator('button[type="submit"]', { hasText: 'Post Update' })
await expect(submitBtn).toBeDisabled()
// Use click + type to ensure Vue hydration processes the input events
await textarea.click()
await textarea.pressSequentially('Test update content', { delay: 10 })
await expect(submitBtn).toBeEnabled({ timeout: 5000 })
})
test('privacy selector defaults to members and has all options', async ({ adminPage }) => {
await adminPage.goto('/updates/new')
await expect(adminPage.locator('h1', { hasText: 'New Update' })).toBeVisible({
timeout: 10000,
})
const select = adminPage.locator('select')
await expect(select).toHaveValue('members')
// Verify all three privacy options exist
await expect(select.locator('option[value="members"]')).toBeAttached()
await expect(select.locator('option[value="public"]')).toBeAttached()
await expect(select.locator('option[value="private"]')).toBeAttached()
})
test('cancel link navigates back to my-updates', async ({ adminPage }) => {
await adminPage.goto('/updates/new')
await expect(adminPage.locator('h1', { hasText: 'New Update' })).toBeVisible({
timeout: 10000,
})
const cancelLink = adminPage.locator('a', { hasText: 'Cancel' })
await expect(cancelLink).toHaveAttribute('href', '/member/my-updates')
})
test('back link points to my-updates', async ({ adminPage }) => {
await adminPage.goto('/updates/new')
const backLink = adminPage.locator('.back-link a')
await expect(backLink).toBeVisible({ timeout: 10000 })
await expect(backLink).toHaveAttribute('href', '/member/my-updates')
})
})
test.describe('Updates API (public access)', () => {
test('public updates endpoint returns data', async ({ page }) => {
const response = await page.request.get('/api/updates')
expect(response.ok()).toBe(true)
const data = await response.json()
expect(data).toHaveProperty('updates')
expect(data).toHaveProperty('total')
expect(data).toHaveProperty('hasMore')
expect(Array.isArray(data.updates)).toBe(true)
})
})