ghostguild-org/e2e/admin-events.spec.js
Jennie Robinson Faber 039a6802e3 fix(e2e): repair failing suite — a11y fixes and stale assertions
Three product a11y defects: drop role="radiogroup" from the /join PWYC
<ul> (it stripped the list role; native radios already group), use
--parch-text on the active contribution chip (was --text-bright, 1.17:1
on --parch), and label the New tag pool USelect on event create.

Three stale tests: real event-type filter labels, updated location
placeholder, and click the label instead of the hidden 0×0 radio.
2026-05-24 00:43:54 +01:00

168 lines
6.2 KiB
JavaScript

import { test, expect } from './helpers/fixtures.js'
test.describe('Admin events list', () => {
test('events list loads for admin', async ({ adminPage }) => {
await adminPage.goto('/admin/events')
await expect(adminPage.locator('h1')).toContainText('Events')
})
test('create event button present', async ({ adminPage }) => {
await adminPage.goto('/admin/events')
await expect(
adminPage.getByRole('link', { name: 'Create Event' })
).toBeVisible()
})
})
test.describe('Admin events create', () => {
test('create event page loads', async ({ adminPage }) => {
await adminPage.goto('/admin/events/create')
await expect(adminPage.locator('h1')).toContainText('Create Event')
})
test('create event form has required fields', async ({ adminPage }) => {
await adminPage.goto('/admin/events/create')
// Title input
await expect(
adminPage.getByPlaceholder('Enter a clear, descriptive event title')
).toBeVisible()
// Description textarea
await expect(
adminPage.getByPlaceholder(
'Provide a clear description of what attendees can expect from this event'
)
).toBeVisible()
// Event type select (USelect renders a button with the selected value)
await expect(adminPage.getByText('Event Type')).toBeVisible()
})
})
test.describe('Admin events access control', () => {
test('non-admin redirect', async ({ page }) => {
await page.goto('/admin/events')
// Admin middleware redirects unauthenticated users to /
await page.waitForURL((url) => !url.pathname.startsWith('/admin'))
expect(page.url()).not.toContain('/admin/events')
})
})
test.describe('Admin events CRUD', () => {
test('create, edit, and delete an event', async ({ adminPage }) => {
const suffix = Date.now().toString().slice(-6)
const title = `e2e-event-${suffix}`
const editedTitle = `e2e-event-${suffix}-edited`
// Re-prime the auth cookie immediately before this multi-step flow.
// The shared test-admin account's tokenVersion is bumped whenever
// auth.spec.js's logout test runs in parallel, which would otherwise
// surface mid-flow as "Session has been revoked" on the first POST.
const loginRes = await adminPage.context().request.get('/api/dev/test-login', { maxRedirects: 0 })
if (loginRes.status() !== 302) {
throw new Error(`Failed to refresh admin session: ${loginRes.status()}`)
}
// --- Create ---
await adminPage.goto('/admin/events/create')
await expect(adminPage.locator('h1')).toContainText('Create Event')
// Ensure Vue has hydrated (initial $fetch for series/tags has resolved)
// before interacting — under cross-file load, hydration can lag and a
// pre-hydration submit will native-POST against an empty form.
await adminPage.waitForLoadState('networkidle')
await adminPage
.getByPlaceholder('Enter a clear, descriptive event title')
.fill(title)
await adminPage
.getByPlaceholder(
'Provide a clear description of what attendees can expect from this event'
)
.fill('e2e test event description')
await adminPage
.getByPlaceholder('e.g., https://zoom.us/j/123..., #channel-name, or TBD')
.fill('https://example.com/zoom')
const startInput = adminPage.getByPlaceholder(
"e.g., 'tomorrow at 3pm', 'next Friday at 9am'"
)
await startInput.fill('next Tuesday at 3pm')
await startInput.blur()
const endInput = adminPage.getByPlaceholder(
"e.g., 'tomorrow at 5pm', 'next Friday at 11am'"
)
await endInput.fill('next Tuesday at 5pm')
await endInput.blur()
await adminPage.getByRole('button', { name: 'Create Event' }).click()
// The form posts via $fetch and then auto-redirects after a 1.5s setTimeout.
// Under cross-file load that auto-redirect can race against waitForURL.
// Wait for the surfaced success/error state, fail fast on error, then
// navigate explicitly so subsequent assertions are deterministic.
await expect(
adminPage.locator('.success-box').or(adminPage.locator('.error-box'))
).toBeVisible({ timeout: 15000 })
await expect(adminPage.locator('.success-box')).toBeVisible()
await adminPage.goto('/admin/events')
await adminPage.waitForLoadState('networkidle')
// Filter to just our event — orphan rows from prior failed runs can push
// the new row off page 1 of the paginated list.
await adminPage.getByPlaceholder('Search events...').fill(title)
const row = adminPage.locator('tr', { hasText: title })
await expect(row).toBeVisible({ timeout: 10000 })
// --- Edit ---
// Find the event ID from the row's "View" link (href is /events/<slug-or-id>),
// and use the row's Edit button. Pair the click with waitForURL so we don't
// miss the navigation event under load.
await Promise.all([
adminPage.waitForURL(/\/admin\/events\/create\?edit=/, { timeout: 15000 }),
row.getByRole('button', { name: 'Edit' }).click(),
])
await expect(adminPage.locator('h1')).toContainText('Edit Event')
const titleInput = adminPage.getByPlaceholder(
'Enter a clear, descriptive event title'
)
await titleInput.fill(editedTitle)
await adminPage.getByRole('button', { name: 'Update Event' }).click()
await expect(
adminPage.locator('.success-box').or(adminPage.locator('.error-box'))
).toBeVisible({ timeout: 15000 })
await expect(adminPage.locator('.success-box')).toBeVisible()
await adminPage.goto('/admin/events')
await adminPage.waitForLoadState('networkidle')
// Filter to the edited event's unique title for the same pagination reason.
await adminPage.getByPlaceholder('Search events...').fill(editedTitle)
const editedRow = adminPage.locator('tr', { hasText: editedTitle })
await expect(editedRow).toBeVisible({ timeout: 10000 })
// --- Delete (custom modal, not browser dialog) ---
await editedRow.getByRole('button', { name: 'Del' }).click()
await expect(
adminPage.getByRole('heading', { name: 'Delete Event' })
).toBeVisible()
await adminPage
.locator('.modal')
.getByRole('button', { name: 'Delete' })
.click()
await expect(
adminPage.locator('tr', { hasText: editedTitle })
).toHaveCount(0, { timeout: 10000 })
})
})