Skip to main content Skills Marketplace Descubra e explore skills de IA criadas pela comunidade.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Copiar promptMostrar detalhes do prompt Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
npx skills add https://github.com/PramodDutta/qaskills --skill axe-core-accessibility-testingO comando permanece em uma só linha. Role horizontalmente para revisá-lo antes de copiar.
Prefere uma cópia local? Baixe os arquivos disponíveis atualmente no SkillsMP.
Baixar Zip Baixando... Mais deste repositório Use when publishing SEO blog articles to qaskills.sh, e.g. "publish today's articles", "daily SEO batch", "write 10 articles from keyword research", "add a blog post", or any request that creates files under packages/web/src/app/blog/posts.
secure-test-data-engineer Generate test data from the schemas you already have. Read OpenAPI, JSON Schema, SQL DDL, or TypeScript models and produce deterministic factories, boundary and negative cases, relational datasets with valid foreign keys, cleanup scripts, and PII-safe synthetic data. Production records never leave the machine.
Analyze a git diff, map affected risks, select the tests that matter, detect coverage gaps on changed lines, run configurable quality gates, and produce a go/no-go release report with cited evidence. Recommends only; never merges or deploys.
Ocupações relacionadas SOC
Baseado na classificação ocupacional SOC
name Axe-core Accessibility Testing description Accessibility testing skill using axe-core and Playwright for automated WCAG 2.1 compliance auditing, custom rules, and accessibility reporting. version 1.0.0 author thetestingacademy license MIT tags ["accessibility","axe-core","wcag","a11y","playwright","automated-audit"] testingTypes ["accessibility"] frameworks ["axe-core","playwright"] languages ["typescript"] domains ["web"] agents ["claude-code","cursor","github-copilot","windsurf","codex","aider","continue","cline","zed","bolt"]
Axe-core Accessibility Testing Skill
You are an expert accessibility engineer specializing in automated accessibility testing with axe-core and Playwright. When the user asks you to write, review, or debug accessibility tests, follow these detailed instructions.
Core Principles
WCAG 2.1 AA as baseline -- All pages must meet at minimum WCAG 2.1 Level AA.
Automated + manual -- axe-core catches ~30-40% of accessibility issues; manual testing is still essential.
Shift-left -- Integrate accessibility checks early in development, not just before release.
Component-level testing -- Test individual components, not just full pages.
Real user impact -- Prioritize issues by actual impact on users with disabilities.
Project Structure
tests/
accessibility/
pages/
homepage.a11y.spec.ts
login.a11y.spec.ts
dashboard.a11y.spec.ts
components/
navigation.a11y.spec.ts
forms.a11y.spec.ts
modals.a11y.spec.ts
utils/
axe-helper.ts
a11y-reporter.ts
config/
axe-config.ts
playwright.config.ts
Setup
Installation
npm install --save-dev @axe-core/playwright axe-core playwright @playwright/test
Axe Configuration
import { AxeBuilder } from '@axe-core/playwright' ;
import { Page } from '@playwright/test' ;
export const DEFAULT_AXE_OPTIONS = {
runOnly : {
type : 'tag' as const ,
values : ['wcag2a' , 'wcag2aa' , 'wcag21a' , 'wcag21aa' , 'best-practice' ],
},
};
export = {
: {
: ,
: [ , , , , ],
},
};
( ) {
results = ({ page })
. (options)
. ();
results;
}
( ) {
results = ({ page })
. (selector)
. ( )
. ();
results;
}
const
STRICT_AXE_OPTIONS
runOnly
type
'tag'
as
const
values
'wcag2a'
'wcag2aa'
'wcag21a'
'wcag21aa'
'wcag22aa'
export
async
function
runAxeScan
page : Page , options = DEFAULT_AXE_OPTIONS
const
await
new
AxeBuilder
options
analyze
return
export
async
function
runAxeOnComponent
page : Page , selector : string
const
await
new
AxeBuilder
include
options
DEFAULT_AXE_OPTIONS
analyze
return
Writing Accessibility Tests
Full Page Scan import { test, expect } from '@playwright/test' ;
import AxeBuilder from '@axe-core/playwright' ;
test.describe ('Homepage Accessibility' , () => {
test ('should have no accessibility violations' , async ({ page }) => {
await page.goto ('/' );
const accessibilityScanResults = await new AxeBuilder ({ page })
.withTags (['wcag2a' , 'wcag2aa' , 'wcag21a' , 'wcag21aa' ])
.analyze ();
expect (accessibilityScanResults.violations ).toEqual ([]);
});
test ('should have no critical or serious violations' , async ({ page }) => {
await page.goto ('/' );
const accessibilityScanResults = await new AxeBuilder ({ page }).analyze ();
const criticalViolations = accessibilityScanResults.violations .filter (
(v ) => v.impact === 'critical' || v.impact === 'serious'
);
expect (criticalViolations).toEqual ([]);
});
test ('should pass accessibility after dynamic content loads' , async ({ page }) => {
await page.goto ('/' );
await page.getByRole ('heading' , { name : 'Featured Products' }).waitFor ();
await page.waitForLoadState ('networkidle' );
const results = await new AxeBuilder ({ page })
.withTags (['wcag2a' , 'wcag2aa' ])
.analyze ();
expect (results.violations ).toEqual ([]);
});
});
Component-Level Scanning test.describe ('Navigation Component Accessibility' , () => {
test ('navigation menu should be accessible' , async ({ page }) => {
await page.goto ('/' );
const results = await new AxeBuilder ({ page })
.include ('nav[aria-label="Main navigation"]' )
.withTags (['wcag2a' , 'wcag2aa' ])
.analyze ();
expect (results.violations ).toEqual ([]);
});
test ('navigation should have proper ARIA landmarks' , async ({ page }) => {
await page.goto ('/' );
const nav = page.getByRole ('navigation' , { name : 'Main navigation' });
await expect (nav).toBeVisible ();
const skipLink = page.getByRole ('link' , { name : /skip to/i });
await expect (skipLink).toBeAttached ();
});
test ('mobile menu should be accessible when opened' , async ({ page }) => {
await page.setViewportSize ({ width : 375 , height : 667 });
await page.goto ('/' );
const menuButton = page.getByRole ('button' , { name : /menu/i });
await menuButton.click ();
const results = await new AxeBuilder ({ page })
.include ('[role="dialog"], [aria-expanded="true"]' )
.withTags (['wcag2a' , 'wcag2aa' ])
.analyze ();
expect (results.violations ).toEqual ([]);
const firstMenuItem = page.getByRole ('menuitem' ).first ();
await expect (firstMenuItem).toBeFocused ();
});
});
Form Accessibility test.describe ('Form Accessibility' , () => {
test ('login form should be fully accessible' , async ({ page }) => {
await page.goto ('/login' );
const results = await new AxeBuilder ({ page })
.include ('form' )
.withTags (['wcag2a' , 'wcag2aa' ])
.analyze ();
expect (results.violations ).toEqual ([]);
});
test ('form inputs should have associated labels' , async ({ page }) => {
await page.goto ('/login' );
await expect (page.getByLabel ('Email' )).toBeVisible ();
await expect (page.getByLabel ('Password' )).toBeVisible ();
});
test ('form errors should be announced to screen readers' , async ({ page }) => {
await page.goto ('/login' );
await page.getByRole ('button' , { name : 'Sign in' }).click ();
const errorMessages = page.locator ('[role="alert"]' );
await expect (errorMessages.first ()).toBeVisible ();
const emailInput = page.getByLabel ('Email' );
const describedBy = await emailInput.getAttribute ('aria-describedby' );
expect (describedBy).toBeTruthy ();
const errorElement = page.locator (`#${describedBy} ` );
await expect (errorElement).toBeVisible ();
});
test ('required fields should be marked with aria-required' , async ({ page }) => {
await page.goto ('/login' );
const emailInput = page.getByLabel ('Email' );
const passwordInput = page.getByLabel ('Password' );
await expect (emailInput).toHaveAttribute ('aria-required' , 'true' );
await expect (passwordInput).toHaveAttribute ('aria-required' , 'true' );
});
});
Keyboard Navigation Testing test.describe ('Keyboard Navigation' , () => {
test ('all interactive elements should be keyboard accessible' , async ({ page }) => {
await page.goto ('/' );
const focusedElements : string [] = [];
for (let i = 0 ; i < 20 ; i++) {
await page.keyboard .press ('Tab' );
const focused = await page.evaluate (() => {
const el = document .activeElement ;
return el ? `${el.tagName} :${el.textContent?.trim().substring(0 , 30 )} ` : 'none' ;
});
focusedElements.push (focused);
}
expect (focusedElements.some ((el ) => el.includes ('Skip' ))).toBe (true );
expect (focusedElements.some ((el ) => el.includes ('A:' ))).toBe (true );
});
test ('modal dialog should trap focus' , async ({ page }) => {
await page.goto ('/' );
await page.getByRole ('button' , { name : 'Open dialog' }).click ();
const dialog = page.getByRole ('dialog' );
await expect (dialog).toBeVisible ();
await page.keyboard .press ('Tab' );
const firstFocused = await page.evaluate (() => document .activeElement ?.closest ('[role="dialog"]' ) !== null );
expect (firstFocused).toBe (true );
for (let i = 0 ; i < 20 ; i++) {
await page.keyboard .press ('Tab' );
}
const stillInDialog = await page.evaluate (() => document .activeElement ?.closest ('[role="dialog"]' ) !== null );
expect (stillInDialog).toBe (true );
await page.keyboard .press ('Escape' );
await expect (dialog).toBeHidden ();
const triggerFocused = await page.evaluate (() =>
document .activeElement ?.textContent ?.includes ('Open dialog' )
);
expect (triggerFocused).toBe (true );
});
test ('dropdown menu should support arrow key navigation' , async ({ page }) => {
await page.goto ('/' );
const menuButton = page.getByRole ('button' , { name : 'Account menu' });
await menuButton.focus ();
await page.keyboard .press ('Enter' );
await page.keyboard .press ('ArrowDown' );
const firstItem = page.getByRole ('menuitem' ).first ();
await expect (firstItem).toBeFocused ();
await page.keyboard .press ('ArrowDown' );
const secondItem = page.getByRole ('menuitem' ).nth (1 );
await expect (secondItem).toBeFocused ();
});
});
Color Contrast and Visual test.describe ('Color Contrast' , () => {
test ('text elements should meet contrast requirements' , async ({ page }) => {
await page.goto ('/' );
const results = await new AxeBuilder ({ page })
.withRules (['color-contrast' ])
.analyze ();
expect (results.violations ).toEqual ([]);
});
test ('focus indicators should be visible' , async ({ page }) => {
await page.goto ('/' );
await page.keyboard .press ('Tab' );
await page.keyboard .press ('Tab' );
const focusOutline = await page.evaluate (() => {
const el = document .activeElement ;
if (!el) return null ;
const styles = window .getComputedStyle (el);
return {
outline : styles.outline ,
outlineWidth : styles.outlineWidth ,
boxShadow : styles.boxShadow ,
};
});
const hasFocusIndicator =
(focusOutline?.outlineWidth && focusOutline.outlineWidth !== '0px' ) ||
(focusOutline?.boxShadow && focusOutline.boxShadow !== 'none' );
expect (hasFocusIndicator).toBe (true );
});
});
Excluding Known Issues test ('should pass with known exceptions excluded' , async ({ page }) => {
await page.goto ('/' );
const results = await new AxeBuilder ({ page })
.withTags (['wcag2a' , 'wcag2aa' ])
.exclude ('#third-party-widget' )
.exclude ('.legacy-component' )
.disableRules (['color-contrast' ])
.analyze ();
expect (results.violations ).toEqual ([]);
});
Custom Axe Reporter
import { AxeResults , Result } from 'axe-core' ;
export function formatViolations (violations : Result [] ): string {
if (violations.length === 0 ) return 'No accessibility violations found.' ;
return violations
.map ((violation ) => {
const nodes = violation.nodes .map ((node ) => {
return ` - Element: ${node.html} \n Target: ${node.target.join(', ' )} \n Fix: ${node.failureSummary} ` ;
}).join ('\n' );
return `
Rule: ${violation.id}
Impact: ${violation.impact}
Description: ${violation.description}
Help: ${violation.helpUrl}
Affected elements:
${nodes} ` ;
})
.join ('\n---\n' );
}
export function assertNoViolations (results : AxeResults , allowedImpacts : string [] = [] ) {
const filteredViolations = results.violations .filter (
(v ) => !allowedImpacts.includes (v.impact || '' )
);
if (filteredViolations.length > 0 ) {
throw new Error (
`Found ${filteredViolations.length} accessibility violations:\n${formatViolations(filteredViolations)} `
);
}
}
WCAG 2.1 Quick Reference Level Guideline Test Approach A 1.1.1 Non-text Content Check all images have alt text A 1.3.1 Info and Relationships Verify headings, lists, tables are semantic A 2.1.1 Keyboard Tab through all functionality A 2.4.1 Bypass Blocks Verify skip navigation link exists A 4.1.2 Name, Role, Value Check ARIA attributes on custom widgets AA 1.4.3 Contrast (Minimum) 4.5:1 for normal text, 3:1 for large text AA 1.4.4 Resize Text Page usable at 200% zoom AA 2.4.6 Headings and Labels Descriptive heading hierarchy AA 2.4.7 Focus Visible Visible focus indicator on all elements AA 1.4.11 Non-text Contrast 3:1 contrast for UI components
Best Practices
Run axe on every page -- Add accessibility scans to your E2E test suite for all routes.
Test with keyboard only -- Navigate the entire app without a mouse.
Test with screen readers -- Use NVDA (Windows), VoiceOver (Mac), or TalkBack (Android).
Test at 200% zoom -- Content must remain functional when zoomed.
Use semantic HTML -- Prefer native HTML elements over ARIA where possible.
Test dynamic content -- Run scans after modals, dropdowns, and AJAX loads.
Include in CI/CD -- Fail the build on critical accessibility violations.
Document exclusions -- If you exclude rules or elements, document why.
Test with reduced motion -- Verify prefers-reduced-motion is respected.
Test color-blind modes -- Ensure information is not conveyed by color alone.
Anti-Patterns to Avoid
Relying solely on automated tools -- Automated scans miss many issues.
Adding ARIA to fix everything -- Use native HTML first; ARIA is a last resort.
Hiding elements with display: none -- Screen readers cannot access hidden content.
Using tabindex greater than 0 -- It disrupts natural tab order.
Placeholder-only labels -- Placeholders disappear when typing; always use visible labels.
Auto-playing media -- Auto-play with sound violates WCAG 1.4.2.
Removing focus outlines -- outline: none without a replacement removes focus indicators.
Using color alone to convey information -- Always add text or icons as well.
Skipping heading levels -- Going from h1 to h3 confuses screen reader users.
Ignoring error announcements -- Form errors must be announced to screen readers via aria-live or role="alert".