From 16d8c5df19b59a926e8d194e401d8558730a408e Mon Sep 17 00:00:00 2001 From: Jamie Folsom Date: Thu, 19 Feb 2026 20:25:49 -0500 Subject: [PATCH 1/4] Improve a11y test suite and add CI workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix path detail URL bug (posts/ → paths/) in a11y.test.ts - Filter axe-core to WCAG 2.0/2.1/2.2 AA rules only - Use soft assertions so all violations surface per page - Add manual-trigger GitHub Actions workflow (.github/workflows/a11y.yml) - Add test results report (docs/a11y-test-results.md) - Update CLAUDE.md with multi-tenant env var and TinaCMS gotchas --- .github/workflows/a11y.yml | 80 ++++++++++++++++++++++++++++++++++++++ docs/a11y-test-results.md | 57 +++++++++++++++++++++++++++ test/browser/a11y.test.ts | 9 +++-- 3 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/a11y.yml create mode 100644 docs/a11y-test-results.md diff --git a/.github/workflows/a11y.yml b/.github/workflows/a11y.yml new file mode 100644 index 00000000..ea8cd816 --- /dev/null +++ b/.github/workflows/a11y.yml @@ -0,0 +1,80 @@ +name: Accessibility Tests + +on: + workflow_dispatch: + inputs: + url: + description: 'URL to test (e.g., https://uss-staging.netlify.app). Leave empty to build and test locally.' + required: false + type: string + +jobs: + a11y: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Install Playwright chromium + run: npx playwright install --with-deps chromium + + # When testing a deployed URL, just fetch config for test scaffolding + - name: Fetch config + if: ${{ inputs.url != '' }} + env: + CONFIG_URL: ${{ secrets.CONFIG_URL }} + run: curl -fsSL "$CONFIG_URL" -o public/config.json + + # When no URL provided, do a full static build + - name: Build static site + if: ${{ inputs.url == '' }} + env: + STATIC_BUILD: 'true' + CONFIG_URL: ${{ secrets.CONFIG_URL }} + GITHUB_OWNER: ${{ secrets.CONTENT_GITHUB_OWNER }} + GITHUB_REPO: ${{ secrets.CONTENT_GITHUB_REPO }} + GITHUB_BRANCH: ${{ secrets.CONTENT_GITHUB_BRANCH }} + GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.CONTENT_GITHUB_PERSONAL_ACCESS_TOKEN }} + MONGODB_URI: ${{ secrets.MONGODB_URI }} + MONGODB_NAME: ${{ secrets.MONGODB_NAME }} + MONGODB_COLLECTION_NAME: ${{ secrets.MONGODB_COLLECTION_NAME }} + NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} + run: npm run build + + - name: Start preview server + if: ${{ inputs.url == '' }} + run: npx astro preview & + + - name: Wait for server + if: ${{ inputs.url == '' }} + run: | + for i in $(seq 1 30); do + curl -sf http://localhost:4321 > /dev/null 2>&1 && exit 0 + sleep 1 + done + echo "Server failed to start" && exit 1 + + - name: Run accessibility tests + env: + A11Y_HOST: ${{ inputs.url || 'http://localhost:4321' }} + STATIC_BUILD: ${{ inputs.url != '' && 'false' || 'true' }} + run: npx playwright test --project=chromium + + - name: Upload report + if: always() + uses: actions/upload-artifact@v4 + with: + name: a11y-report + path: | + playwright-report/ + test-results/ + retention-days: 30 diff --git a/docs/a11y-test-results.md b/docs/a11y-test-results.md new file mode 100644 index 00000000..07b1ba10 --- /dev/null +++ b/docs/a11y-test-results.md @@ -0,0 +1,57 @@ +# Accessibility Test Results + +**Date:** 2026-02-19 +**Engine:** axe-core 4.11 via `@axe-core/playwright` +**Ruleset:** WCAG 2.0/2.1/2.2 Level A and AA only +**Browser:** Chromium +**Target:** USS content deployment (localhost:4321) + +## Summary + +| Status | Count | +|--------|-------| +| Passed | 6 | +| Failed | 1 | +| Skipped | 7 | + +**1 unique WCAG violation** found. The test suite filters to WCAG 2.2 AA criteria and uses soft assertions to report all violations per page without stopping early. + +## WCAG Violations + +### Critical: `image-alt` — Images must have alternative text + +- **WCAG:** 1.1.1 Non-text Content (Level A) +- **Issue:** [#553](https://github.com/performant-software/core-data-places/issues/553) +- **Pages:** `/en/pages/Institutions`, `/en/pages/About`, `/en/` (Home, intermittent due to `server:defer` timing) +- **Element:** `` in Banner component (`src/apps/pages/Banner.astro` line 52) +- **Root cause:** `Banner.astro` uses `alt={imageAlt}`, but when TinaCMS content omits the `imageAlt` field, the `alt` attribute is undefined (missing entirely). +- **Fix:** Default to `alt={imageAlt || ''}` for decorative images, or require alt text in the CMS schema. + +## Best Practice Issues (Not WCAG Criteria) + +These were found during initial testing with all axe-core rules enabled. They are not WCAG violations but are worth addressing: + +- **`page-has-heading-one`** — 6 pages lack a visible `

` at scan time because the heading is inside `Header.astro` with `server:defer` +- **`region`** — Header and footer use `
` instead of semantic landmarks (`
`, `

{ test('Sessions should have no violations', async ({ page }) => { await page.goto('sessions/search'); + await page.getByRole('button', { name: /clear/i }).waitFor(); await checkPage(page); }); }); @@ -170,9 +171,9 @@ test.describe('Accessibility testing', () => { // Detail pages test.describe('Detail pages', () => { - test.skip(!_config.detail_pages, 'No detail pages configured.'); + test.skip(!_config.detail_pages?.models, 'No detail pages configured.'); - const models = Object.keys(_config.detail_pages.models); + const models = Object.keys(_config.detail_pages?.models || {}); models.forEach((name) => { test(`${name} should have no violations`, async ({ page }) => { From 7099360338f35ad0ae0dd13c5752ae255334904e Mon Sep 17 00:00:00 2001 From: Jamie Folsom Date: Wed, 1 Apr 2026 17:38:05 -0400 Subject: [PATCH 4/4] add image_alt field to column image template Column images in the page builder had no alt text support, causing critical image-alt a11y violations on pages using them. --- src/components/ColumnContent.astro | 2 +- tina/content/pages.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ColumnContent.astro b/src/components/ColumnContent.astro index 7d5e1121..ba493cfb 100644 --- a/src/components/ColumnContent.astro +++ b/src/components/ColumnContent.astro @@ -23,7 +23,7 @@ const BlockTypes = { --- { item.__typename.endsWith(BlockTypes.image) && ( - {item.image_alt[] = [{ name: 'image', label: 'Image', type: 'image' + }, { + name: 'image_alt', + label: 'Image Alt Text', + type: 'string' }, { name: 'full_height', label: 'Full height?',