axe Accessibility CI with Playwright
Section titled “axe Accessibility CI with Playwright”axe Accessibility CI is the automated accessibility safety net for the NutsNews web app.
It complements Lighthouse CI by running deeper page-level accessibility checks with Playwright and @axe-core/playwright on every push, pull request, and manual workflow run.
Use this for checking:
- Missing image alternative text
- Form label problems
- Button/link naming problems
- Landmark and heading issues
- Color contrast issues detected by axe
- Serious and critical WCAG violations before public launch or App Store review
This does not replace a manual WAVE browser-extension pass. Use WAVE manually before important releases, and use axe CI to catch regressions automatically.
Important NutsNews Repository Layout
Section titled “Important NutsNews Repository Layout”NutsNews is a multi-folder repository.
The repository root is:
/Users/ramideltoro/WebstormProjects/nutsnews3The Next.js website is inside:
/Users/ramideltoro/WebstormProjects/nutsnews3/webThat means Playwright and axe dependencies must be installed inside web/, not at the repository root.
What Was Added
Section titled “What Was Added”| File | Purpose |
|---|---|
web/playwright.config.ts | Playwright test configuration for the built NutsNews web app |
web/tests/accessibility.spec.ts | axe accessibility tests for public NutsNews pages |
.github/workflows/accessibility-ci.yml | GitHub Actions workflow that runs the accessibility tests |
web/package.json | Adds the test:accessibility script and dev dependencies |
web/package-lock.json | Locks Playwright and axe dependency versions |
web/.gitignore | Ignores generated local Playwright and Lighthouse reports |
Audited Pages
Section titled “Audited Pages”The first automated axe pass checks these public reader/support pages:
//about/privacy/contactThese are safe because they are public pages and do not trigger ingestion, AI review, translation, refresh work, database writes, OAuth flows, or admin-only workflows.
Do not add Worker refresh URLs, controller trigger URLs, admin pages, OAuth callback routes, or API routes that perform writes.
Blocking Threshold
Section titled “Blocking Threshold”The first setup fails CI only for axe violations with these impacts:
criticalseriousModerate and minor findings are still useful during manual review, but starting with serious and critical issues keeps the CI signal practical while NutsNews is still evolving.
Later, the threshold can be tightened to fail on moderate issues too.
Step 1 — Install Dependencies in the Web App
Section titled “Step 1 — Install Dependencies in the Web App”cd /Users/ramideltoro/WebstormProjects/nutsnews3/web
npm install --save-dev @playwright/test @axe-core/playwrightExpected changed files:
web/package.jsonweb/package-lock.jsonStep 2 — Add the Package Script
Section titled “Step 2 — Add the Package Script”web/package.json should include:
{ "scripts": { "test:accessibility": "playwright test --config=playwright.config.ts" }}Keep the existing dev, build, start, and lint scripts.
Step 3 — Add web/playwright.config.ts
Section titled “Step 3 — Add web/playwright.config.ts”The config starts the already-built Next.js app on a local CI port and runs Chromium-only accessibility tests.
import { defineConfig, devices } from '@playwright/test';
const PORT = Number(process.env.PLAYWRIGHT_PORT ?? 3100);const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${PORT}`;
export default defineConfig({ testDir: './tests', timeout: 60_000, expect: { timeout: 10_000, }, fullyParallel: false, forbidOnly: !!process.env.CI, retries: process.env.CI ? 1 : 0, workers: 1, reporter: process.env.CI ? [['line'], ['html', { open: 'never' }]] : [['list'], ['html', { open: 'never' }]], use: { baseURL, trace: 'retain-on-failure', screenshot: 'only-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, ], webServer: { command: `npm run start -- -p ${PORT}`, url: baseURL, reuseExistingServer: !process.env.CI, timeout: 120_000, },});Step 4 — Add web/tests/accessibility.spec.ts
Section titled “Step 4 — Add web/tests/accessibility.spec.ts”The test opens each public NutsNews page and scans it with axe.
The test currently uses these axe tags:
wcag2awcag2aawcag21awcag21aaThe test fails when serious or critical violations are found.
Step 5 — Add GitHub Actions Workflow
Section titled “Step 5 — Add GitHub Actions Workflow”The workflow file belongs at the repository root:
.github/workflows/accessibility-ci.ymlThe workflow commands run from web/ using:
defaults: run: working-directory: webThe workflow:
- Checks out the repository.
- Sets up Node.js 22.
- Installs web dependencies with
npm ci. - Installs Playwright Chromium and Linux browser dependencies.
- Builds NutsNews.
- Runs the axe accessibility tests.
- Uploads the Playwright HTML report and failure traces as a GitHub Actions artifact.
Step 6 — Required GitHub Secrets
Section titled “Step 6 — Required GitHub Secrets”The CI workflow uses the same public Supabase values as Lighthouse CI:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYAdd or confirm them here:
GitHub repo → Settings → Secrets and variables → Actions → Repository secretsUse the values from:
cd /Users/ramideltoro/WebstormProjects/nutsnews3
grep -E '^NEXT_PUBLIC_SUPABASE_(URL|ANON_KEY)=' web/.env.local | sed 's/NEXT_PUBLIC_SUPABASE_ANON_KEY=.*/NEXT_PUBLIC_SUPABASE_ANON_KEY=<hidden>/'Step 7 — Run Locally
Section titled “Step 7 — Run Locally”From the web app folder:
cd /Users/ramideltoro/WebstormProjects/nutsnews3/web
npm run buildnpx playwright install chromiumnpm run test:accessibilityExpected result:
4 passedIf there is a failure, open the local report:
npx playwright show-reportStep 8 — Commit and Push
Section titled “Step 8 — Commit and Push”From the repository root:
cd /Users/ramideltoro/WebstormProjects/nutsnews3
git status
git add \ web/package.json \ web/package-lock.json \ web/playwright.config.ts \ web/tests/accessibility.spec.ts \ web/.gitignore \ .github/workflows/accessibility-ci.yml \ README.md \ docs/README.md \ docs/OBSERVABILITY.md \ docs/AXE_PLAYWRIGHT_ACCESSIBILITY_CI.md \ docs/updates/README_AXE_PLAYWRIGHT_ACCESSIBILITY_CI_UPDATE.md
git commit -m "Add axe Playwright accessibility CI"
git pushStep 9 — Read the GitHub Actions Result
Section titled “Step 9 — Read the GitHub Actions Result”After pushing, open:
GitHub → Actions → Accessibility CIA good run shows:
Install dependencies ✅Install Playwright Chromium ✅Build NutsNews ✅Run axe accessibility checks ✅Upload Playwright accessibility report ✅If it fails, open the failed step. The test output lists:
- axe rule ID
- impact level
- help text
- affected DOM selectors
- axe help URL
The uploaded accessibility-playwright-report artifact can be downloaded from the workflow run.
Manual WAVE Still Matters
Section titled “Manual WAVE Still Matters”Automated axe checks are useful, but they do not catch every accessibility issue. Before App Store review and public launch, still run the WAVE browser extension manually on:
https://www.nutsnews.com/https://www.nutsnews.com/abouthttps://www.nutsnews.com/privacyhttps://www.nutsnews.com/contactUse WAVE for visual review of:
- Contrast warnings
- Heading structure
- Visible labels
- Button readability
- Link text quality
- Layout and reading order
For NutsNews, the best setup is:
Lighthouse CI = broad web quality scoreaxe Playwright CI = automated accessibility regression checksWAVE extension = final manual accessibility polish