Stabilize e2e suite: rate-limit, spec drift, a11y, visual baselines #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/e2e-stabilization-2026-04-26"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Brings the Playwright e2e suite from 18 failures → 0 (74 passed, 1 intentional skip).
Production-side changes
server/middleware/03.rate-limit.js— early-return whenALLOW_DEV_TEST_ENDPOINTS=true. Parallel local runs (6 workers from127.0.0.1) were burning the 100 req/min general-limiter budget within ~30s, leaving every API call (including/api/dev/test-loginand/api/dev/member-login) returning 429 for the rest of the window. Production never sets that env var, so rate limiting stays active there.app/assets/css/main.css— global[data-slot="placeholder"]color override. Nuxt UI's defaulttext-dimmed(#a6a09b) failed WCAG AA contrast on cream (2.43:1) and white (2.58:1). Now usesvar(--text-dim)(#5a5040) for ~7:1.Test-side changes
join-flow.spec.js— form was redesigned: numeric input + chip presets, agreement checkbox now required, success state lives inSignupFlowOverlay(no.success-box).member-profile.spec.js— outdated copy assertion replaced with stable structural label;waitForLoadState('networkidle')for ClientOnly hydration.board.spec.js—waitForLoadState('networkidle')before clicks;Postbutton disambiguated withexact: true; delete uses two-step in-card confirm (not native dialog).admin-board-channels.spec.js— placeholder text correction (no leading#); Slack ID input only in Edit modal.auth.spec.js— same hydration wait pattern for sidebar logout click.Bonus
The original dirty CSS polish on
join.vue/profile.vueis preserved in its own commit so it can be reviewed or cherry-picked separately.Required local setup
ALLOW_DEV_TEST_ENDPOINTS=truemust be in.envfor local e2e runs to bypass rate limiting.Test plan
npm run test:e2e— 74 passed, 1 intentional skipnpm run test:run(vitest, pre-push) — 767 passed, 2 skippedjoin-flow: - Form now requires Community Guidelines agreement; tests check the checkbox before expecting submit to enable. - Contribution input is a numeric field with preset chip buttons, not a USelect with $0/mo options — fill the input directly. - Success state lives in SignupFlowOverlay ("Welcome to Ghost Guild!"); no .success-box exists. Match by heading instead. - Inline .error-box renders OUTSIDE <form>, so duplicate-email assertion uses .signup-flow-overlay .error-box (which is the user-facing error). member-profile: - "How you appear to other members" copy was retired; replace with the stable "Show in Member Directory" structural label. - Add waitForLoadState('networkidle') after goto for ClientOnly auth hydration so "Edit Profile" reliably appears within timeout. board: - Add waitForLoadState('networkidle') after goto so the action-bar's "+ New Post" click handler is bound before the test clicks. - Submit button is named exactly "Post" — disambiguate from "+ New Post" buttons with { exact: true }. - Delete is a two-step in-card confirm (Delete → Confirm), not a native browser dialog; drop the page.once('dialog') listener. admin-board-channels: - Channel name placeholder is "e.g., coop-formation" (no leading #). - Slack Channel ID input only appears in the Edit modal (v-if="editingId"), not on Create — Slack channel is auto-created server-side. Drop the slack ID fill from the Create step. - Add waitForLoadState('networkidle') before opening the modal.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.