From e094798979656526053f8edcb1453d3ad9316c3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 19:02:58 +0000 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20add=20accessibility=20audit=20?= =?UTF-8?q?=E2=80=94=20axe-core=20E2E=20tests,=20Lighthouse=20CI=20job,=20?= =?UTF-8?q?and=20e2e-summary=20extension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - e2e/accessibility.spec.ts: axe-core WCAG 2.x scans for home, create-game, and privacy pages; fail on critical/serious violations; write per-page violation JSON to playwright-report/a11y/ - lighthouserc.yml: Lighthouse CI config with accessibility ≄ 0.90 threshold, onlyCategories:accessibility, and temporary-public-storage upload - .github/workflows/ci-cd.yml: new accessibility-audit job (needs: build) — runs lhci autorun, uploads artifact, posts/updates PR comment with score and report link, gates deploy-pr-infrastructure and deploy-qa-infrastructure via needs: [e2e, accessibility-audit] - scripts/e2e-summary.sh: appends accessibility violation count table (critical/serious/moderate/minor) from playwright-report/a11y/*.json to the step summary --- .github/workflows/ci-cd.yml | 152 +++++++++++++++++++++++++++++++++++- e2e/accessibility.spec.ts | 89 +++++++++++++++++++++ lighthouserc.yml | 25 ++++++ scripts/e2e-summary.sh | 34 ++++++++ 4 files changed, 298 insertions(+), 2 deletions(-) create mode 100644 e2e/accessibility.spec.ts create mode 100644 lighthouserc.yml diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index eb0c617..aba6acf 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -163,6 +163,154 @@ jobs: if: always() run: bash scripts/e2e-summary.sh "E2E Test Results" "" "playwright-report" + # ========================================================================== + # ACCESSIBILITY AUDIT (Lighthouse CI) + # Runs Lighthouse against the pre-built dist/ artifact and enforces an + # accessibility score threshold of ≄ 90 %. Results are uploaded to + # temporary public storage and posted as a PR comment so developers get + # immediate feedback without having to download any artifact. + # ========================================================================== + accessibility-audit: + name: Accessibility Audit (Lighthouse) + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: build + if: github.event_name != 'pull_request' || github.event.action != 'closed' + + steps: + - uses: actions/checkout@v7 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Download build artifact + uses: actions/download-artifact@v8 + with: + name: build + + - name: Install Lighthouse CI + run: npm install -g @lhci/cli@0.14.x + + - name: Run Lighthouse CI + id: lhci + run: | + set +e + lhci autorun --config=lighthouserc.yml 2>&1 | tee /tmp/lhci-output.txt + echo "exit_code=${PIPESTATUS[0]}" >> $GITHUB_OUTPUT + continue-on-error: true + + - name: Extract Lighthouse results + id: lhci-results + if: always() + run: | + if [ -f .lighthouseci/manifest.json ]; then + SCORE=$(jq -r '(.[0].summary.accessibility * 100 | floor) | tostring' .lighthouseci/manifest.json) + echo "score=$SCORE" >> $GITHUB_OUTPUT + if [ "$SCORE" -ge 90 ]; then + echo "passed=true" >> $GITHUB_OUTPUT + else + echo "passed=false" >> $GITHUB_OUTPUT + fi + else + echo "score=N/A" >> $GITHUB_OUTPUT + echo "passed=false" >> $GITHUB_OUTPUT + fi + REPORT_URL=$(grep -oP 'https://storage\.googleapis\.com\S+' /tmp/lhci-output.txt 2>/dev/null | head -1 || echo "") + echo "report_url=$REPORT_URL" >> $GITHUB_OUTPUT + + - name: Upload Lighthouse report + uses: actions/upload-artifact@v7 + if: always() + with: + name: lighthouse-report + path: .lighthouseci/ + retention-days: 7 + + - name: Lighthouse Accessibility Summary + if: always() + run: | + SCORE="${{ steps.lhci-results.outputs.score }}" + PASSED="${{ steps.lhci-results.outputs.passed }}" + REPORT_URL="${{ steps.lhci-results.outputs.report_url }}" + + echo "## šŸ”¦ Lighthouse Accessibility Audit" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [[ "$PASSED" == "true" ]]; then + echo "### āœ… Accessibility Threshold Met" >> $GITHUB_STEP_SUMMARY + else + echo "### āŒ Accessibility Threshold Not Met" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Category | Score | Threshold |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|-----------|" >> $GITHUB_STEP_SUMMARY + echo "| ♿ Accessibility | ${SCORE}% | ≄ 90% |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ -n "$REPORT_URL" ]; then + echo "šŸ”— **Full Report:** [View on Temporary Storage]($REPORT_URL) _(expires in 7 days)_" >> $GITHUB_STEP_SUMMARY + fi + echo "šŸ“Š **Artifact:** Download the \`lighthouse-report\` artifact for the HTML report." >> $GITHUB_STEP_SUMMARY + + - name: Post Lighthouse score as PR comment + if: github.event_name == 'pull_request' + uses: actions/github-script@v9 + with: + script: | + const score = '${{ steps.lhci-results.outputs.score }}' + const passed = '${{ steps.lhci-results.outputs.passed }}' === 'true' + const reportUrl = '${{ steps.lhci-results.outputs.report_url }}' + const emoji = passed ? 'āœ…' : 'āŒ' + const status = passed + ? 'Threshold (≄ 90%) **met**.' + : 'āš ļø Score is **below** the 90 % threshold — please fix the violations.' + const reportLine = reportUrl + ? `\n\nšŸ”— [View full Lighthouse report](${reportUrl}) _(expires in 7 days)_` + : '\n\nšŸ“Š Download the `lighthouse-report` artifact for details.' + + const marker = '' + const body = [ + marker, + `## ${emoji} Lighthouse Accessibility Score`, + '', + '| Category | Score | Threshold |', + '|----------|-------|-----------|', + `| ♿ Accessibility | ${score}% | ≄ 90% |`, + '', + status + reportLine, + ].join('\n') + + const { data: comments } = await github.rest.issues.listComments({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }) + const existing = comments.find(c => c.body && c.body.includes(marker)) + + if (existing) { + await github.rest.issues.updateComment({ + comment_id: existing.id, + owner: context.repo.owner, + repo: context.repo.repo, + body, + }) + } else { + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body, + }) + } + + - name: Fail if accessibility threshold not met + if: steps.lhci.outputs.exit_code != '0' + run: | + echo "Lighthouse accessibility score is below the 90 % threshold." + echo "See the lighthouse-report artifact or the PR comment for details." + exit 1 + # ========================================================================== # PR INFRASTRUCTURE DEPLOYMENT # Creates an ephemeral Azure resource group for the PR with all necessary @@ -173,7 +321,7 @@ jobs: name: Deploy PR Infrastructure runs-on: ubuntu-latest timeout-minutes: 15 - needs: e2e + needs: [e2e, accessibility-audit] if: github.event_name == 'pull_request' && github.event.action != 'closed' outputs: resource_group: ${{ steps.rg.outputs.name }} @@ -393,7 +541,7 @@ jobs: name: Deploy QA Infrastructure runs-on: ubuntu-latest timeout-minutes: 15 - needs: e2e + needs: [e2e, accessibility-audit] if: github.ref == 'refs/heads/main' && github.event_name == 'push' outputs: resource_group: ${{ steps.rg.outputs.name }} diff --git a/e2e/accessibility.spec.ts b/e2e/accessibility.spec.ts new file mode 100644 index 0000000..b143294 --- /dev/null +++ b/e2e/accessibility.spec.ts @@ -0,0 +1,89 @@ +import { test } from '@playwright/test' +import AxeBuilder from '@axe-core/playwright' +import * as fs from 'fs' +import * as path from 'path' + +/** + * Accessibility tests using axe-core / WCAG 2.x rules. + * + * Each test scans a key page and writes all violations to + * playwright-report/a11y/.json so the CI summary script can + * aggregate and report counts by severity. + * + * Tests fail only on critical / serious violations so that moderate / + * minor issues are visible in the report without blocking PRs outright. + */ + +const WCAG_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] +const A11Y_REPORT_DIR = path.join('playwright-report', 'a11y') + +function saveViolations(pageName: string, violations: object[]): void { + fs.mkdirSync(A11Y_REPORT_DIR, { recursive: true }) + fs.writeFileSync( + path.join(A11Y_REPORT_DIR, `${pageName}.json`), + JSON.stringify(violations, null, 2), + ) +} + +test.describe('Accessibility (axe-core)', () => { + test('home page has no critical or serious violations', async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + + const { violations } = await new AxeBuilder({ page }).withTags(WCAG_TAGS).analyze() + saveViolations('home', violations) + + const blocking = violations.filter( + (v) => v.impact === 'critical' || v.impact === 'serious', + ) + if (blocking.length > 0) { + const details = blocking + .map((v) => ` [${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node(s))`) + .join('\n') + throw new Error( + `${blocking.length} critical/serious violation(s) on home page:\n${details}`, + ) + } + }) + + test('create game page has no critical or serious violations', async ({ page }) => { + await page.goto('/') + await page.getByRole('button', { name: /crear nuevo juego|create new game/i }).click() + await page.waitForLoadState('networkidle') + + const { violations } = await new AxeBuilder({ page }).withTags(WCAG_TAGS).analyze() + saveViolations('create-game', violations) + + const blocking = violations.filter( + (v) => v.impact === 'critical' || v.impact === 'serious', + ) + if (blocking.length > 0) { + const details = blocking + .map((v) => ` [${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node(s))`) + .join('\n') + throw new Error( + `${blocking.length} critical/serious violation(s) on create-game page:\n${details}`, + ) + } + }) + + test('privacy page has no critical or serious violations', async ({ page }) => { + await page.goto('/?view=privacy') + await page.waitForLoadState('networkidle') + + const { violations } = await new AxeBuilder({ page }).withTags(WCAG_TAGS).analyze() + saveViolations('privacy', violations) + + const blocking = violations.filter( + (v) => v.impact === 'critical' || v.impact === 'serious', + ) + if (blocking.length > 0) { + const details = blocking + .map((v) => ` [${v.impact}] ${v.id}: ${v.description} (${v.nodes.length} node(s))`) + .join('\n') + throw new Error( + `${blocking.length} critical/serious violation(s) on privacy page:\n${details}`, + ) + } + }) +}) diff --git a/lighthouserc.yml b/lighthouserc.yml new file mode 100644 index 0000000..86b3879 --- /dev/null +++ b/lighthouserc.yml @@ -0,0 +1,25 @@ +ci: + collect: + # Serve the pre-built frontend from dist/ via a local static server + staticDistDir: ./dist + numberOfRuns: 1 + settings: + # Only run the accessibility category for speed; other categories are + # covered separately by axe-core in Playwright. + onlyCategories: + - accessibility + # Required flags for running headless Chrome in CI (no sandbox / display) + chromeFlags: '--no-sandbox --headless' + + assert: + assertions: + # Fail the job when the accessibility score drops below 90 % + 'categories:accessibility': + - error + - minScore: 0.9 + aggregationMethod: pessimistic + + upload: + # Upload to Google's temporary public storage so the report URL can be + # shared in the PR comment. Reports expire automatically after 7 days. + target: temporary-public-storage diff --git a/scripts/e2e-summary.sh b/scripts/e2e-summary.sh index a1fda0d..be8da08 100644 --- a/scripts/e2e-summary.sh +++ b/scripts/e2e-summary.sh @@ -72,3 +72,37 @@ fi echo "" >> "$GITHUB_STEP_SUMMARY" echo "šŸ“Š **Full Report:** Download the \`${ARTIFACT_NAME}\` artifact for detailed HTML report." >> "$GITHUB_STEP_SUMMARY" + +# --------------------------------------------------------------------------- +# Accessibility violation summary (populated by e2e/accessibility.spec.ts) +# Each scanned page writes its axe-core violations to +# playwright-report/a11y/.json as a JSON array. +# --------------------------------------------------------------------------- +if ls playwright-report/a11y/*.json 1>/dev/null 2>&1; then + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "### ♿ Accessibility Violations (axe-core)" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + + # Count violations by impact level across all scanned pages. + # jq processes each file independently and outputs one number per file; + # awk sums the stream. + CRITICAL=$(jq '[.[] | select(.impact == "critical")] | length' playwright-report/a11y/*.json 2>/dev/null | awk '{s+=$1} END {print s+0}') + SERIOUS=$(jq '[.[] | select(.impact == "serious")] | length' playwright-report/a11y/*.json 2>/dev/null | awk '{s+=$1} END {print s+0}') + MODERATE=$(jq '[.[] | select(.impact == "moderate")] | length' playwright-report/a11y/*.json 2>/dev/null | awk '{s+=$1} END {print s+0}') + MINOR=$(jq '[.[] | select(.impact == "minor")] | length' playwright-report/a11y/*.json 2>/dev/null | awk '{s+=$1} END {print s+0}') + TOTAL=$((CRITICAL + SERIOUS + MODERATE + MINOR)) + + if [ "$TOTAL" -eq 0 ]; then + echo "āœ… No accessibility violations found across scanned pages." >> "$GITHUB_STEP_SUMMARY" + else + echo "| Severity | Count |" >> "$GITHUB_STEP_SUMMARY" + echo "|----------|-------|" >> "$GITHUB_STEP_SUMMARY" + echo "| šŸ”“ Critical | $CRITICAL |" >> "$GITHUB_STEP_SUMMARY" + echo "| 🟠 Serious | $SERIOUS |" >> "$GITHUB_STEP_SUMMARY" + echo "| 🟔 Moderate | $MODERATE |" >> "$GITHUB_STEP_SUMMARY" + echo "| šŸ”µ Minor | $MINOR |" >> "$GITHUB_STEP_SUMMARY" + echo "| **Total** | **$TOTAL** |" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "> Critical and serious violations fail the accessibility tests. Download the \`${ARTIFACT_NAME}\` artifact for full axe-core details." >> "$GITHUB_STEP_SUMMARY" + fi +fi From 005b5e0a35c58afc84f28bb292f24d7493f90d66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 19:04:32 +0000 Subject: [PATCH 2/8] fix: use '90%' formatting (no space before %) in lighthouserc.yml and ci-cd.yml --- .github/workflows/ci-cd.yml | 4 ++-- lighthouserc.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index aba6acf..1f99bb5 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -264,7 +264,7 @@ jobs: const emoji = passed ? 'āœ…' : 'āŒ' const status = passed ? 'Threshold (≄ 90%) **met**.' - : 'āš ļø Score is **below** the 90 % threshold — please fix the violations.' + : 'āš ļø Score is **below** the 90% threshold — please fix the violations.' const reportLine = reportUrl ? `\n\nšŸ”— [View full Lighthouse report](${reportUrl}) _(expires in 7 days)_` : '\n\nšŸ“Š Download the `lighthouse-report` artifact for details.' @@ -307,7 +307,7 @@ jobs: - name: Fail if accessibility threshold not met if: steps.lhci.outputs.exit_code != '0' run: | - echo "Lighthouse accessibility score is below the 90 % threshold." + echo "Lighthouse accessibility score is below the 90% threshold." echo "See the lighthouse-report artifact or the PR comment for details." exit 1 diff --git a/lighthouserc.yml b/lighthouserc.yml index 86b3879..1907729 100644 --- a/lighthouserc.yml +++ b/lighthouserc.yml @@ -13,7 +13,7 @@ ci: assert: assertions: - # Fail the job when the accessibility score drops below 90 % + # Fail the job when the accessibility score drops below 90% 'categories:accessibility': - error - minScore: 0.9 From 58ceedf6126aedcd29553a23505db52be51a4b8f Mon Sep 17 00:00:00 2001 From: David Sanchez Date: Wed, 15 Jul 2026 15:18:44 -0400 Subject: [PATCH 3/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- scripts/e2e-summary.sh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/scripts/e2e-summary.sh b/scripts/e2e-summary.sh index be8da08..8f52082 100644 --- a/scripts/e2e-summary.sh +++ b/scripts/e2e-summary.sh @@ -84,12 +84,17 @@ if ls playwright-report/a11y/*.json 1>/dev/null 2>&1; then echo "" >> "$GITHUB_STEP_SUMMARY" # Count violations by impact level across all scanned pages. - # jq processes each file independently and outputs one number per file; - # awk sums the stream. - CRITICAL=$(jq '[.[] | select(.impact == "critical")] | length' playwright-report/a11y/*.json 2>/dev/null | awk '{s+=$1} END {print s+0}') - SERIOUS=$(jq '[.[] | select(.impact == "serious")] | length' playwright-report/a11y/*.json 2>/dev/null | awk '{s+=$1} END {print s+0}') - MODERATE=$(jq '[.[] | select(.impact == "moderate")] | length' playwright-report/a11y/*.json 2>/dev/null | awk '{s+=$1} END {print s+0}') - MINOR=$(jq '[.[] | select(.impact == "minor")] | length' playwright-report/a11y/*.json 2>/dev/null | awk '{s+=$1} END {print s+0}') + # Use jq -s to read all files at once; default to 0 if parsing fails so the + # summary step doesn't fail the whole job. + sum_impact() { + local impact="$1" + jq -s --arg impact "$impact" '[.[].[] | select(.impact == $impact)] | length' playwright-report/a11y/*.json || echo 0 + } + + CRITICAL=$(sum_impact critical) + SERIOUS=$(sum_impact serious) + MODERATE=$(sum_impact moderate) + MINOR=$(sum_impact minor) TOTAL=$((CRITICAL + SERIOUS + MODERATE + MINOR)) if [ "$TOTAL" -eq 0 ]; then From 342f5ec86a39c6d5f48648f895436ebd1955337f Mon Sep 17 00:00:00 2001 From: David Sanchez Date: Wed, 15 Jul 2026 15:28:49 -0400 Subject: [PATCH 4/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/ci-cd.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 1f99bb5..5a34c86 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -207,11 +207,16 @@ jobs: if: always() run: | if [ -f .lighthouseci/manifest.json ]; then - SCORE=$(jq -r '(.[0].summary.accessibility * 100 | floor) | tostring' .lighthouseci/manifest.json) - echo "score=$SCORE" >> $GITHUB_OUTPUT - if [ "$SCORE" -ge 90 ]; then - echo "passed=true" >> $GITHUB_OUTPUT + SCORE=$(jq -r '(.[0].summary.accessibility * 100 | floor) // empty' .lighthouseci/manifest.json 2>/dev/null || true) + if [[ "$SCORE" =~ ^[0-9]+$ ]]; then + echo "score=$SCORE" >> $GITHUB_OUTPUT + if [ "$SCORE" -ge 90 ]; then + echo "passed=true" >> $GITHUB_OUTPUT + else + echo "passed=false" >> $GITHUB_OUTPUT + fi else + echo "score=N/A" >> $GITHUB_OUTPUT echo "passed=false" >> $GITHUB_OUTPUT fi else From da86c748beabc01a5850703269be0c2261a3739e Mon Sep 17 00:00:00 2001 From: David Sanchez Date: Wed, 15 Jul 2026 15:29:19 -0400 Subject: [PATCH 5/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- e2e/accessibility.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/accessibility.spec.ts b/e2e/accessibility.spec.ts index b143294..a12933e 100644 --- a/e2e/accessibility.spec.ts +++ b/e2e/accessibility.spec.ts @@ -28,7 +28,7 @@ function saveViolations(pageName: string, violations: object[]): void { test.describe('Accessibility (axe-core)', () => { test('home page has no critical or serious violations', async ({ page }) => { await page.goto('/') - await page.waitForLoadState('networkidle') + await page.waitForLoadState('domcontentloaded') const { violations } = await new AxeBuilder({ page }).withTags(WCAG_TAGS).analyze() saveViolations('home', violations) From 27ae1df06133dd2765dc389871b3667bb67cd7e3 Mon Sep 17 00:00:00 2001 From: David Sanchez Date: Wed, 15 Jul 2026 15:29:55 -0400 Subject: [PATCH 6/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- e2e/accessibility.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/accessibility.spec.ts b/e2e/accessibility.spec.ts index a12933e..aad4e6d 100644 --- a/e2e/accessibility.spec.ts +++ b/e2e/accessibility.spec.ts @@ -49,7 +49,7 @@ test.describe('Accessibility (axe-core)', () => { test('create game page has no critical or serious violations', async ({ page }) => { await page.goto('/') await page.getByRole('button', { name: /crear nuevo juego|create new game/i }).click() - await page.waitForLoadState('networkidle') + await page.waitForLoadState('domcontentloaded') const { violations } = await new AxeBuilder({ page }).withTags(WCAG_TAGS).analyze() saveViolations('create-game', violations) From eae5bc9da3cd58c62c6d5f768aa6fa071fe8d632 Mon Sep 17 00:00:00 2001 From: David Sanchez Date: Wed, 15 Jul 2026 15:30:03 -0400 Subject: [PATCH 7/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- e2e/accessibility.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/accessibility.spec.ts b/e2e/accessibility.spec.ts index aad4e6d..85d60ab 100644 --- a/e2e/accessibility.spec.ts +++ b/e2e/accessibility.spec.ts @@ -69,7 +69,7 @@ test.describe('Accessibility (axe-core)', () => { test('privacy page has no critical or serious violations', async ({ page }) => { await page.goto('/?view=privacy') - await page.waitForLoadState('networkidle') + await page.waitForLoadState('domcontentloaded') const { violations } = await new AxeBuilder({ page }).withTags(WCAG_TAGS).analyze() saveViolations('privacy', violations) From a14fde30cc1b8b6f157ea988ec3047d6cd402f32 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Jul 2026 20:18:28 +0000 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20resolve=20accessibility=20violations?= =?UTF-8?q?=20=E2=80=94=20remove=20opacity=20animation=20and=20add=20aria-?= =?UTF-8?q?labels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PrivacyView: change motion.div initial opacity from 0→1 to 1→1 so axe-core does not encounter semi-transparent text when scanning the page. The fade-in animation (opacity: 0 → 1) caused color-contrast failures on 6 of 7 sections because axe-core runs immediately after domcontentloaded, before Framer Motion animations complete. - CreateGameView: add aria-label={t('selectCurrency')} to SelectTrigger so the currency dropdown button has a discernible accessible name, fixing the critical button-name violation. - CreateGameView: add aria-label to the icon-only remove-participant button (step 2) that only contains an X icon, fixing a future button-name violation. --- src/components/CreateGameView.tsx | 3 ++- src/components/PrivacyView.tsx | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/CreateGameView.tsx b/src/components/CreateGameView.tsx index 874c5f4..a28b56a 100644 --- a/src/components/CreateGameView.tsx +++ b/src/components/CreateGameView.tsx @@ -292,7 +292,7 @@ export function CreateGameView({ onGameCreated, onBack, emailConfigured = false