Help us improve
Share bugs, ideas, or general feedback.
From test-automation-skills-agents
Automates WCAG 2.1/2.2 compliance checks using Selenium WebDriver 4+, Java 21+, and axe-core. Scans pages, audits color contrast, ARIA semantics, keyboard navigation, and generates accessibility reports.
npx claudepluginhub fugazi/test-automation-skills-agents --plugin test-automation-skills-agentsHow this skill is triggered — by the user, by Claude, or both
Slash command
/test-automation-skills-agents:accessibility-selenium-testingThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill enables automated accessibility analysis within the Selenium WebDriver framework using the **axe-core** engine to detect WCAG violations and best practice issues directly in the browser.
Automates accessibility testing with Playwright (@playwright/test), TypeScript, and axe-core for WCAG 2.1 AA compliance, keyboard navigation, focus management, ARIA validations, and semantic checks.
Audits web apps for WCAG 2.1/2.2 compliance with axe-core/Playwright, Pa11y, Lighthouse scans plus keyboard nav, ARIA, color contrast checks.
Runs WCAG accessibility checks using axe-core with Playwright for E2E tests and jest-axe for component tests. Includes keyboard navigation and ARIA testing.
Share bugs, ideas, or general feedback.
This skill enables automated accessibility analysis within the Selenium WebDriver framework using the axe-core engine to detect WCAG violations and best practice issues directly in the browser.
Activation: This skill is triggered when you need to validate WCAG compliance, scan for accessibility violations, test keyboard navigation, audit ARIA semantics, or generate a11y reports.
| Component | Version | Purpose |
|---|---|---|
| Java JDK | 21+ | Runtime with modern features |
| Maven | 3.9+ | Dependency management |
| Selenium WebDriver | 4.x | Browser automation |
| axe-core-selenium | 4.10+ | Deque axe-core integration |
| JUnit 5 | 5.10+ | Test framework |
| AssertJ | 3.x | Fluent assertions for readable failures |
| Allure | 2.x | Reporting with a11y violation attachments |
Note: Use
com.deque.html.axe-core:seleniumMaven dependency for axe integration.
| Level | Requirement | Legal Status | Axe Tags |
|---|---|---|---|
| Level A | Basic accessibility (must have) | Minimum legal requirement | wcag2a, wcag21a |
| Level AA | Intermediate (should have) | Legal requirement in most jurisdictions | wcag2aa, wcag21aa |
| Level AAA | Advanced (nice to have) | Not typically required | wcag2aaa, wcag21aaa |
| Best Practice | Industry recommendations | Not WCAG but improves UX | best-practice |
| Method | Purpose | Example |
|---|---|---|
new AxeBuilder() | Create scanner instance | Entry point |
.withTags(List<String>) | Filter by WCAG tags | wcag2aa, wcag21aa |
.include(String) | Scan specific selector | #main-content |
.exclude(String) | Skip selector from scan | .third-party-widget |
.disableRules(List<String>) | Disable specific rules | color-contrast |
.withRules(List<String>) | Run only specific rules | label, button-name |
.analyze(WebDriver) | Execute the scan | Returns Results |
| Method | Returns | Purpose |
|---|---|---|
getViolations() | List<Rule> | Rules that failed |
getPasses() | List<Rule> | Rules that passed |
getIncomplete() | List<Rule> | Rules needing manual review |
getInapplicable() | List<Rule> | Rules not applicable to page |
violationFree() | boolean | True if no violations |
| Impact | Severity | CI Action |
|---|---|---|
| Critical | Blocks users completely | Always fail build |
| Serious | Significant barrier | Always fail build |
| Moderate | Some difficulty | Warn or fail |
| Minor | Inconvenience | Log for review |
new AxeBuilder().analyze(driver)new AxeBuilder().include("#my-component").analyze(driver).withTags(List.of("wcag2a", "wcag2aa")).exclude(".legacy-footer") (use carefully, document reason)Results.getViolations() - should be emptyResults to JSON for dashboardsAs an Accessibility Automation Specialist:
AxeBuilder with appropriate WCAG tagsAdd dependency to pom.xml
<dependency>
<groupId>com.deque.html.axe-core</groupId>
<artifactId>selenium</artifactId>
<version>4.10.0</version>
</dependency>
Create AccessibilityHelper utility
Add scan after page loads
driver.get("https://example.com");
waitForPageReady();
AccessibilityHelper.verifyPageAccessibility(driver);
Run and review violations
mvn test -Dtest=A11yTest
Navigate to page with component visible
Trigger component state (open modal, show dropdown)
Scan only the component
Results results = new AxeBuilder()
.withTags(List.of("wcag2a", "wcag2aa"))
.include("#login-modal")
.analyze(driver);
Assert and log
element.sendKeys(Keys.TAB);
WebElement focused = driver.switchTo().activeElement();
Configure headless browser
mvn test -Dheadless=true -Dgroups=a11y
Set zero-tolerance for Critical/Serious
long criticalCount = violations.stream()
.filter(v -> List.of("critical", "serious").contains(v.getImpact()))
.count();
assertThat(criticalCount).isZero();
Generate JSON report for tracking
@Step("Verify page accessibility - WCAG 2.1 AA")
public void verifyPageAccessibility(WebDriver driver) {
Results results = new AxeBuilder()
.withTags(List.of("wcag2a", "wcag2aa", "wcag21a", "wcag21aa"))
.analyze(driver);
logViolations(results.getViolations());
assertThat(results.violationFree())
.as("Accessibility violations found on: %s", driver.getCurrentUrl())
.isTrue();
}
@Step("Verify component accessibility: {selectors}")
public void verifyComponentAccessibility(WebDriver driver, String... selectors) {
AxeBuilder builder = new AxeBuilder()
.withTags(List.of("wcag2a", "wcag2aa"));
for (String selector : selectors) {
builder.include(selector);
}
Results results = builder.analyze(driver);
logViolations(results.getViolations());
assertThat(results.violationFree())
.as("Component accessibility check failed")
.isTrue();
}
@Step("Verify no critical accessibility violations")
public void verifyCriticalViolations(WebDriver driver) {
Results results = new AxeBuilder()
.withTags(List.of("wcag2a", "wcag2aa"))
.analyze(driver);
List<Rule> criticalViolations = results.getViolations().stream()
.filter(v -> List.of("critical", "serious").contains(v.getImpact()))
.toList();
if (!criticalViolations.isEmpty()) {
logViolations(criticalViolations);
}
assertThat(criticalViolations)
.as("Critical/Serious accessibility violations found")
.isEmpty();
}
/**
* Scan with exclusions for known issues.
* Exclusions must be documented with ticket reference.
*/
@Step("Verify accessibility with documented exclusions")
public void verifyWithExclusions(WebDriver driver) {
Results results = new AxeBuilder()
.withTags(List.of("wcag2a", "wcag2aa"))
.exclude(".third-party-chat-widget") // JIRA-1234: Vendor limitation
.exclude("#legacy-footer") // JIRA-5678: Scheduled for Q2 fix
.analyze(driver);
assertThat(results.violationFree()).isTrue();
}
private void logViolations(List<Rule> violations) {
if (violations.isEmpty()) {
log.info("✓ No accessibility violations found");
return;
}
log.error("✗ Found {} accessibility violations:", violations.size());
for (Rule violation : violations) {
log.error(" [{}/{}] {}",
violation.getImpact().toUpperCase(),
violation.getId(),
violation.getDescription());
log.error(" Help: {}", violation.getHelpUrl());
for (CheckedNode node : violation.getNodes()) {
log.error(" Target: {}", String.join(", ", node.getTarget()));
log.error(" HTML: {}", truncate(node.getHtml(), 100));
}
}
}
@Epic("Accessibility")
@Feature("WCAG 2.1 AA Compliance")
class AccessibilityTest extends BaseTest {
@Test
@Tag("a11y")
@Severity(SeverityLevel.CRITICAL)
@DisplayName("Homepage should meet WCAG 2.1 AA standards")
void homePage_shouldBeAccessible() {
driver.get(ConfigReader.get("base.url"));
waitForPageReady();
Results results = new AxeBuilder()
.withTags(List.of("wcag2a", "wcag2aa", "wcag21a", "wcag21aa"))
.analyze(driver);
attachResultsToAllure(results);
SoftAssertions.assertSoftly(softly -> {
softly.assertThat(results.violationFree())
.as("Page should have no accessibility violations")
.isTrue();
});
}
@Test
@Tag("a11y")
@DisplayName("Login modal should be keyboard accessible")
void loginModal_shouldBeKeyboardAccessible() {
driver.get(ConfigReader.get("base.url"));
// Open modal
driver.findElement(By.id("login-btn")).click();
waitForVisible(By.id("login-modal"));
// Scan modal only
Results results = new AxeBuilder()
.withTags(List.of("wcag2a", "wcag2aa"))
.include("#login-modal")
.analyze(driver);
assertThat(results.violationFree()).isTrue();
// Test keyboard navigation
WebElement modal = driver.findElement(By.id("login-modal"));
WebElement firstInput = modal.findElement(By.cssSelector("input:first-of-type"));
assertThat(driver.switchTo().activeElement())
.as("Focus should be inside modal")
.isEqualTo(firstInput);
// Test Escape closes modal
modal.sendKeys(Keys.ESCAPE);
assertThat(isDisplayed(By.id("login-modal"))).isFalse();
}
}
| Problem | Cause | Solution |
|---|---|---|
| Axe returns empty results | Page not fully loaded | Add explicit wait for page ready state |
| False positives on contrast | Dynamic themes | Test both light and dark modes |
| Violations in third-party widgets | Cannot modify vendor code | Use .exclude() with documented ticket |
| Incomplete rules | Requires manual review | Log for manual audit, don't auto-fail |
| Different results between runs | Async content loading | Ensure deterministic page state before scan |
| CI fails but local passes | Different viewport/browser | Use same headless config as CI |
✅ Wait for page ready - Ensure DOM is stable before axe analysis
✅ Scan unique states - Test modal open, form error, empty state separately
✅ Zero tolerance for Critical/Serious - Always fail CI on these
✅ Use specific tags - Define wcag2aa vs best-practice to reduce noise
✅ Log Help URLs - Developers need the link to fix issues
✅ Document exclusions - Every .exclude() needs a JIRA ticket
✅ Test keyboard navigation - Tab order, focus traps, Escape key
✅ Attach JSON reports - Enable tracking violations over time
✅ Combine with manual audit - Axe catches ~30-50% of issues
⚠️ Automated tooling cannot prove full WCAG conformance - only the presence of certain issues ⚠️ Use automation to prevent regressions - use manual audits for complete coverage ⚠️ Prefer native HTML semantics - use ARIA only when required ⚠️ Never disable rules globally - scope exceptions narrowly with documentation
| Principle | Focus Areas | Common Violations |
|---|---|---|
| Perceivable | Text alternatives, captions, contrast, structure | Missing alt text, low contrast, missing labels |
| Operable | Keyboard access, focus order, bypass blocks | Keyboard traps, no skip link, focus not visible |
| Understandable | Labels, predictable behavior, error handling | Unclear instructions, unexpected changes |
| Robust | Valid HTML, ARIA, name/role/value | Invalid ARIA, duplicate IDs, missing roles |
| Command | Purpose |
|---|---|
mvn test -Dgroups=a11y | Run all accessibility tests |
mvn test -Dtest=A11yTest | Run specific test class |
mvn test -Dheadless=true | Run headless (CI mode) |
mvn allure:serve | View Allure report with violations |
- name: Run Accessibility Tests
run: mvn test -Dgroups=a11y -Dheadless=true
- name: Upload A11y Report
uses: actions/upload-artifact@v3
with:
name: a11y-report
path: target/a11y-results/
Common shortcuts and "good enough" excuses that erode test quality — and the reality behind each.
| Rationalization | Reality |
|---|---|
| "Selenium isn't good for a11y testing" | axe-core + Selenium is battle-tested, CI-ready, and covers WCAG violations programmatically. |
| "We can just run a scan at the end" | Shift-left: catch violations as code is written. Late scans mean expensive fixes. |
| "The framework handles accessibility" | No framework auto-generates proper ARIA roles, labels, or keyboard interactions. |
| "We only need to test the homepage" | Every page a user visits must be accessible. Start with high-risk pages, expand coverage. |
| "Skip the contrast checks, designers fix that" | Automated contrast checks take seconds and prevent lawsuits. They are tests, not design reviews. |
| "Our users don't have disabilities" | ~15% of the global population has some form of disability. Accessibility is for everyone. |
| Task | Code Pattern |
|---|---|
| Full page scan | new AxeBuilder().withTags(List.of("wcag2aa")).analyze(driver) |
| Component scan | new AxeBuilder().include("#selector").analyze(driver) |
| Exclude element | new AxeBuilder().exclude(".ignore").analyze(driver) |
| Check violations | results.getViolations().isEmpty() |
| Filter critical | .filter(v -> v.getImpact().equals("critical")) |
| Get help URL | violation.getHelpUrl() |
| Tab navigation | element.sendKeys(Keys.TAB) |
| Get focused element | driver.switchTo().activeElement() |
After completing this skill's workflow, confirm:
AxeBuilder.analyze(driver) returns zero violationsmvn test -Dtest=*Accessibility* passes