Skip to main content

audit-accessibility

WCAG 2.2 audit via playwright-cli: crawl every page, inject axe-core, test keyboard nav, contrast, ARIA labels, heading order. Use when "audit accessibility", "check a11y", "WCAG audit", "check keyboard nav", or "test screen reader".

소스 정보

저장소
kensaurus/skills
최근 소스 활동
2026년 9월 23일 10:42
감지된 SKILL.md 언어
영어
스타
9
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
audit-accessibility
description
WCAG 2.2 audit via playwright-cli: crawl every page, inject axe-core, test keyboard nav, contrast, ARIA labels, heading order. Use when "audit accessibility", "check a11y", "WCAG audit", "check keyboard nav", or "test screen reader".
license
MIT
effort
high
# Accessibility Audit **Degree of freedom: MIXED** — Phases 0–1, 5 `[HIGH freedom]`; Phases 2–3 playwright/axe/Tab sequences `[LOW freedom — run exactly]`. Read `protocol-browser-anti-stall` before any browser step. ## How to reason 1. **Observe** — quote axe rule+selector, heading list, or Tab snapshot 2. **Interpret** — can a keyboard or AT user complete the task? 3. **Classify** — WCAG fail / axe-false-positive / manual-only / correct-as-is 4. **Severity** — Level A = P0; Level AA = P1; best-practice = P2 ## Worked example > **Observe:** axe `label` on `/signup`; `<input name="email">` has no `id`/`aria-label`. > **Interpret:** AT users cannot tell what to type (1.3.1 / 4.1.2). > **Classify:** WCAG fail (not an axe false positive). > **Severity:** P0 — Level A, blocks signup. > **Finding:** `/signup` | 1.3.1+4.1.2 | P0 | unlabeled email | `<label for>`. ## Self-critique before reporting [LOW freedom — do not skip] 1. **WCAG SC + numbers** — not "contrast is bad"; quote ratio or selector 2. **Keyboard, not axe-only** — 2.1.1 fails even if axe is clean 3. **Every reachable page** — obscure routes still count 4. **Severity justified** — Level A = P0 5. **Axe is ~30–40%** — heading/focus/link-text/reading-order always manual --- ## Phase 0: Auto-Detect Pages and Components ### 0a. Discover Route Structure ``` Glob("**/app/**/page.{tsx,jsx,ts,js}") Glob("**/pages/**/*.{tsx,jsx,ts,js}") Glob("**/src/routes/**/*.{tsx,jsx,svelte,vue}") ``` ### 0b. Find the App URL ``` Grep(pattern: "NEXT_PUBLIC_APP_URL|NEXT_PUBLIC_BASE_URL|VITE_APP_URL|APP_URL|localhost", glob: ".env*") ``` If no production URL, default to `http://localhost:3000` (or detected dev server port). ### 0c. Detect CSS Framework and Component Library ``` Grep(pattern: "tailwindcss|@chakra-ui|@mui|@radix-ui|shadcn|bootstrap|bulma", glob: "package.json") ``` ### 0d. Detect Existing a11y Tooling ``` Grep(pattern: "axe-core|@axe-core|eslint-plugin-jsx-a11y|pa11y|lighthouse|@testing-library", glob: "package.json") Grep(pattern: "aria-|role=|tabIndex|sr-only|visually-hidden", glob: "*.{tsx,jsx,vue,svelte}", output_mode: "count") ``` Record: `APP_URL`, `PAGES`, `CSS_FRAMEWORK`, `COMPONENT_LIB`, `EXISTING_A11Y_TOOLS`, `ARIA_USAGE_COUNT`. --- ## Phase 1: Research Current WCAG Guidelines ### 1a. Firecrawl Research ```json firecrawl:firecrawl_search { "query": "WCAG 2.2 success criteria checklist web accessibility [current year]", "limit": 5 } ``` ```json firecrawl:firecrawl_search { "query": "axe-core automated accessibility testing best practices common false positives", "limit": 5 } ``` ```json firecrawl:firecrawl_search { "query": "<CSS_FRAMEWORK> <COMPONENT_LIB> accessibility best practices common issues", "limit": 5 } ``` Scrape 2–3 authoritative results: ```json firecrawl:firecrawl_scrape { "url": "<BEST_RESULT_URL>", "formats": ["markdown"], "onlyMainContent": true } ``` ### 1b. Framework-Specific a11y Docs (Context7) ```json context7:resolve-library-id { "libraryName": "<COMPONENT_LIB e.g. radix-ui or chakra-ui>" } ``` ```json context7:query-docs { "libraryId": "<RESOLVED_ID>", "query": "accessibility ARIA keyboard navigation focus management" } ``` --- ## Phase 2: Automated Scanning (axe-core via Playwright) ### 2a. Navigate to Each Page For each page discovered in Phase 0a: ```bash PW="npx --yes @playwright/cli@latest" $PW -s=a11y open --headed "<APP_URL><ROUTE>" # first page; use `goto` for subsequent ones ``` Apply the `protocol-browser-anti-stall` protocol: wait 2s, snapshot, verify page loaded. ### 2b. Inject and Run axe-core Use `run-code` to inject axe-core from CDN and run a full scan. The URL pins a version so results are reproducible; if the CDN returns 404, look up the current axe-core 4.x on cdnjs and substitute it — do not skip the scan: ```bash $PW -s=a11y run-code 'async (page) => { await page.addScriptTag({ url: "https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.2/axe.min.js" }); return await page.evaluate(async () => { const r = await axe.run(); return { violations: r.violations.map(v => ({ id: v.id, impact: v.impact, description: v.description, help: v.help, helpUrl: v.helpUrl, nodes: v.nodes.length, targets: v.nodes.slice(0, 3).map(n => n.target[0]) })), passes: r.passes.length, incomplete: r.incomplete.length, inapplicable: r.inapplicable.length }; }); }' ``` For each violation returned, record: - **Rule ID** (e.g., `color-contrast`, `image-alt`, `label`) - **Impact** (critical, serious, moderate, minor) - **WCAG criteria** mapped from the rule - **Affected elements** (CSS selectors) - **Count** of affected nodes ### 2c. Check Specific WCAG Categories Manually **Heading Hierarchy (WCAG 1.3.1):** ```bash $PW -s=a11y eval '() => Array.from(document.querySelectorAll("h1,h2,h3,h4,h5,h6")).map(h => ({ tag: h.tagName, text: h.textContent.trim().substring(0, 50) }))' ``` Verify: only one `h1`, headings don't skip levels (h1 -> h3 without h2). **Image Alt Text (WCAG 1.1.1):** ```bash $PW -s=a11y eval '() => Array.from(document.querySelectorAll("img")).map(img => ({ src: img.src.split("/").pop(), alt: img.alt, hasAlt: img.hasAttribute("alt"), decorative: img.getAttribute("role") === "presentation" || img.alt === "" }))' ``` Verify: all informative images have descriptive alt text, decorative images have `alt=""` or `role="presentation"`. **Link Text (WCAG 2.4.4):** ```bash $PW -s=a11y eval '() => Array.from(document.querySelectorAll("a")).filter(a => ["click here", "here", "read more", "learn more", "", "link"].includes((a.textContent || "").trim().toLowerCase())).map(a => ({ href: a.href, text: (a.textContent || "").trim(), ariaLabel: a.getAttribute("aria-label") }))' ``` Flag generic link text ("click here", "read more", empty links without aria-label). **Form Labels (WCAG 1.3.1, 4.1.2):** ```bash $PW -s=a11y eval '() => Array.from(document.querySelectorAll("input,select,textarea")).map(el => ({ type: el.type, name: el.name, id: el.id, hasLabel: !!el.id && !!document.querySelector(`label[for="${el.id}"]`), ariaLabel: el.getAttribute("aria-label"), ariaLabelledBy: el.getAttribute("aria-labelledby"), placeholder: el.placeholder }))' ``` Verify: every form control has an associated `<label>`, `aria-label`, or `aria-labelledby`. **Language Attribute (WCAG 3.1.1):** ```bash $PW -s=a11y eval '() => ({ htmlLang: document.documentElement.lang, htmlDir: document.documentElement.dir })' ``` Verify: `<html>` has a valid `lang` attribute. ### 2d. Take Evidence Screenshots For each page, capture a screenshot as visual evidence: ```bash $PW -s=a11y screenshot --filename ".playwright-mcp/a11y-<route>.png" ``` --- ## Phase 3: Keyboard Navigation Testing ### 3a. Tab Order Test For each page, test that all interactive elements are reachable via Tab key: ```bash PW="npx --yes @playwright/cli@latest" $PW -s=a11y open --headed "<APP_URL><ROUTE>" # first page; use `goto` for subsequent ones ``` Press Tab repeatedly and snapshot after each press to track focus movement: ```bash $PW -s=a11y press Tab $PW -s=a11y snapshot ``` After each Tab, check: - [ ] Focus is visible (there is a focus indicator on the active element) - [ ] Focus order is logical (follows visual reading order) - [ ] No focus trap (Tab eventually cycles back to the beginning or reaches the end) - [ ] Skip links exist (if there is a large navigation area) Keep pressing Tab until focus has visited every interactive element on the page or cycled back to the first one; stop early only when a focus trap is found, and record it. ### 3b. Interactive Element Testing For each interactive component (dropdowns, modals, tabs, accordions): **Enter/Space activation (WCAG 2.1.1):** - Tab to the element - Press Enter or Space - Verify the action fires (snapshot to confirm state change) **Escape to close (WCAG 2.1.1 for modals/dialogs):** ```bash $PW -s=a11y press Escape ``` Verify: modals/dropdowns close, focus returns to the trigger element. **Arrow key navigation (for tab panels, menus, listboxes):** ```bash $PW -s=a11y press ArrowDown ``` ### 3c. Focus Management After State Changes Test that focus is managed correctly after dynamic updates: - After opening a modal: focus should move into the modal - After closing a modal: focus should return to the trigger - After deleting an item from a list: focus should move to the next item or a logical location - After form submission: focus should move to a success/error message --- ## Phase 4: Cross-Check with Sentry ### 4a. Search for Assistive Technology Errors ```json sentry:search_issues { "organizationSlug": "<ORG_SLUG>", "projectSlugOrId": "<PROJECT_SLUG>", "query": "is:unresolved aria OR screenreader OR voiceover OR talkback OR nvda OR jaws OR focus OR tabindex OR keyboard" } ``` ### 4b. Check for JavaScript Errors on Keyboard Interaction ```json sentry:search_issues { "organizationSlug": "<ORG_SLUG>", "projectSlugOrId": "<PROJECT_SLUG>", "query": "is:unresolved keydown OR keyup OR keypress OR focusin OR focusout" } ``` ### 4c. Browser/Device Breakdown If errors cluster on AT-favored browsers, pull the browser distribution for the issue. `get_issue_tag_values` is a catalog tool, not a direct one, so call it through `execute_sentry_tool`. If the call is rejected, run `search_sentry_tools` with `"issue tag values"` to get the current name and schema. ```json sentry:execute_sentry_tool { "name": "get_issue_tag_values", "arguments": { "organizationSlug": "<ORG_SLUG>", "issueId": "<ISSUE_ID>", "tagKey": "browser" } } ``` --- ## Phase 5: Static Code Analysis ### 5a. Missing ARIA Attributes ``` Grep(pattern: "onClick(?!.*role=)(?!.*tabIndex)", glob: "*.{tsx,jsx}") ``` Flag `div` or `span` elements with click handlers but no `role` or `tabIndex` — these are invisible to keyboard and screen reader users. ### 5b. Inaccessible Custom Components ``` SemanticSearch(query: "Where are custom interactive components defined (dropdowns, modals, tabs, accordions) that don't use a component library?", target_directories: [])
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기