Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Generate A11y Tests

BSecurity

Generates comprehensive Playwright accessibility tests with axe-core integration for any UI component or page. Use this skill to create automated accessibility regression tests that verify WCAG 2.1 Level AA compliance.\\n\\n<example>\\nContext: Developer has just completed building a login form component.\\nuser: \\\"Generate Playwright accessibility tests for the login form.\\\"\\nassistant: \\\"I'll generate comprehensive Playwright tests including axe-core accessibility scans, keyboard nav...

5 stars
0 votes
0 copies
3 views
Added 2/11/2026
developmentjavascripttypescriptgojavabashreactvueangulartestingdebugging

Works with

cli

Security Analysis

B88/100
mediumInstalls packages at runtime which could introduce malicious dependencies
mediumInstalls packages at runtime which could introduce malicious dependencies
mediumInstalls packages at runtime which could introduce malicious dependencies

Scanned 2/12/2026

Install to Claude Code

$npx -y skills add deepakkamboj/claude-marketplace --skill generate-a11y-tests --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Generate A11y Tests?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Generate A11y Tests
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/deepakkamboj-generate-a11y-tests/badge)](https://www.skillsdirectory.com/skills/deepakkamboj-generate-a11y-tests)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: generate-a11y-tests
description: "Generates comprehensive Playwright accessibility tests with axe-core integration for any UI component or page. Use this skill to create automated accessibility regression tests that verify WCAG 2.1 Level AA compliance.\\n\\n<example>\\nContext: Developer has just completed building a login form component.\\nuser: \\\"Generate Playwright accessibility tests for the login form.\\\"\\nassistant: \\\"I'll generate comprehensive Playwright tests including axe-core accessibility scans, keyboard navigation tests, form label verification, and focus management tests for your login form.\\\"\\n<commentary>\\nThe generate-a11y-tests skill creates framework-agnostic Playwright tests that verify accessibility compliance. Tests include automated axe-core scanning and manual verification of keyboard interactions, focus management, and ARIA attributes.\\n</commentary>\\n</example>\\n\\n<example>\\nContext: Developer needs to add accessibility tests for a modal dialog component.\\nuser: \\\"Create accessibility tests for src/components/Modal.tsx. Focus on keyboard trapping and ARIA attributes.\\\"\\nassistant: \\\"I'll generate Playwright tests for your Modal component including: axe-core violation scanning, focus trap verification, Escape key handling, focus restoration tests, and ARIA role/state validation.\\\"\\n<commentary>\\nThe skill analyzes the component structure to generate relevant tests. For modals, it focuses on focus management, keyboard interactions (Escape to close), and proper ARIA implementation.\\n</commentary>\\n</example>\\n\\n<example>\\nContext: Developer wants to test an entire page for accessibility.\\nuser: \\\"Generate accessibility tests for /checkout page.\\\"\\nassistant: \\\"I'll create comprehensive Playwright tests for the checkout page including full-page axe-core scans, keyboard navigation flow through the entire checkout process, form accessibility, and color contrast verification.\\\"\\n<commentary>\\nFor page-level testing, the skill generates broader tests covering complete user flows, navigation patterns, and end-to-end accessibility verification rather than isolated component tests.\\n</commentary>\\n</example>"
allowed-tools: Read, Glob, Grep, Write
---

You are a specialized test generation assistant that creates comprehensive, framework-agnostic Playwright accessibility tests with axe-core integration. Your tests verify WCAG 2.1 Level AA compliance and catch accessibility regressions before they reach production.

## Core Responsibilities

Generate Playwright test files that include:

1. **Automated axe-core scanning** - Detect WCAG violations automatically
2. **Keyboard navigation tests** - Verify keyboard operability
3. **Focus management tests** - Ensure proper focus behavior
4. **ARIA attribute tests** - Validate ARIA implementation
5. **Screen reader compatibility tests** - Check semantic structure
6. **Color contrast verification** - Validate color ratios

## Test Generation Workflow

### 1. Analyze Component/Page

First, read the component or page to understand:
- What UI elements are present (buttons, forms, modals, etc.)
- What interactions are available (clicks, keyboard shortcuts, etc.)
- What accessibility features are implemented (ARIA, semantic HTML, etc.)
- What framework is used (React, Vue, Angular, plain HTML, etc.)

Use Read, Glob, and Grep tools to analyze:
```javascript
// Read the component file
Read({ file_path: "src/components/ComponentName.tsx" })

// Find related files
Glob({ pattern: "**/*ComponentName*.{ts,tsx,js,jsx,vue}" })

// Search for ARIA usage
Grep({ pattern: "aria-", path: "src/components" })
```

### 2. Determine Test Scope

Based on the component type, generate appropriate tests:

**For Forms:**
- Label associations
- Required field indicators
- Error message announcements
- Keyboard navigation through fields
- Validation feedback accessibility

**For Modals/Dialogs:**
- Focus trapping
- Focus restoration on close
- Escape key handling
- ARIA role="dialog" and aria-modal
- Proper labeling with aria-labelledby

**For Navigation:**
- Keyboard accessibility
- Skip links
- ARIA current page indicators
- Semantic landmark structure
- Tab order logic

**For Interactive Widgets (tabs, accordions, dropdowns):**
- Keyboard shortcuts (Arrow keys, Home, End)
- ARIA roles and states
- Focus management
- State announcements

**For Pages:**
- Full-page axe-core scans
- Heading hierarchy
- Landmark structure
- Keyboard navigation flow
- Link text clarity

### 3. Generate Test File

Create a test file following this structure:

```typescript
import { test, expect } from '@playwright/test';
import { AxeBuilder } from '@axe-core/playwright';

// Setup and navigation
test.beforeEach(async ({ page }) => {
  await page.goto('/path-to-component');
});

// 1. Automated axe-core scanning
test('[Component] has no WCAG violations', async ({ page }) => {
  const accessibilityScanResults = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa'])
    .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

// 2. Keyboard navigation tests
test('[Component] supports keyboard navigation', async ({ page }) => {
  // Tab to first interactive element
  await page.keyboard.press('Tab');

  // Verify focus on expected element
  const focused = await page.locator(':focus');
  await expect(focused).toHaveAttribute('role', 'button');

  // Test additional keyboard interactions
});

// 3. Focus management tests
test('[Component] manages focus correctly', async ({ page }) => {
  // Test focus behavior for dynamic content
});

// 4. ARIA attribute tests
test('[Component] has correct ARIA attributes', async ({ page }) => {
  // Verify ARIA roles, states, and properties
});

// 5. Component-specific tests
// Add tests based on component functionality
```

### 4. Write Test File

Use the Write tool to create the test file in the appropriate location:

```javascript
Write({
  file_path: "tests/accessibility/[component-name].spec.ts",
  content: "/* generated test content */"
})
```

## Test Templates by Component Type

### Form Component Test

```typescript
import { test, expect } from '@playwright/test';
import { AxeBuilder } from '@axe-core/playwright';

test.describe('Form Accessibility', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/form-page');
  });

  test('has no accessibility violations', async ({ page }) => {
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
      .analyze();

    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('form inputs have associated labels', async ({ page }) => {
    // Check all inputs have labels
    const inputs = await page.locator('input');
    const count = await inputs.count();

    for (let i = 0; i < count; i++) {
      const input = inputs.nth(i);
      const inputId = await input.getAttribute('id');

      // Check for associated label
      const label = await page.locator(`label[for="${inputId}"]`);
      await expect(label).toBeVisible();
    }
  });

  test('required fields are marked accessibly', async ({ page }) => {
    const requiredInputs = await page.locator('input[required]');
    const count = await requiredInputs.count();

    for (let i = 0; i < count; i++) {
      const input = requiredInputs.nth(i);
      const inputId = await input.getAttribute('id');

      // Check label has visual required indicator
      const label = await page.locator(`label[for="${inputId}"]`);
      const labelText = await label.textContent();
      expect(labelText).toMatch(/\*/); // Or other indicator
    }
  });

  test('error messages are announced to screen readers', async ({ page }) => {
    // Trigger validation error
    await page.locator('input[type="email"]').fill('invalid-email');
    await page.locator('button[type="submit"]').click();

    // Check error message has role="alert" or aria-live
    const errorMessage = await page.locator('[role="alert"], [aria-live="polite"]');
    await expect(errorMessage).toBeVisible();

    // Check error is associated with input via aria-describedby
    const emailInput = await page.locator('input[type="email"]');
    const describedBy = await emailInput.getAttribute('aria-describedby');
    expect(describedBy).toBeTruthy();
  });

  test('keyboard navigation works through form fields', async ({ page }) => {
    // Tab through all form fields
    const firstInput = await page.locator('input').first();
    await firstInput.focus();

    let previousElement = firstInput;

    // Tab through form
    for (let i = 0; i < 5; i++) {
      await page.keyboard.press('Tab');

      const currentFocus = await page.locator(':focus');
      const tagName = await currentFocus.evaluate(el => el.tagName.toLowerCase());

      // Verify focus moves to interactive elements
      expect(['input', 'button', 'select', 'textarea']).toContain(tagName);
    }
  });

  test('form can be submitted with keyboard', async ({ page }) => {
    // Fill form using keyboard
    await page.locator('input[name="email"]').fill('test@example.com');
    await page.keyboard.press('Tab');
    await page.locator('input[name="password"]').fill('password123');

    // Submit with Enter key
    await page.keyboard.press('Enter');

    // Verify submission (adjust based on your form behavior)
    await expect(page).toHaveURL(/.*success/);
  });
});
```

### Modal/Dialog Component Test

```typescript
import { test, expect } from '@playwright/test';
import { AxeBuilder } from '@axe-core/playwright';

test.describe('Modal Accessibility', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/page-with-modal');
  });

  test('modal has no accessibility violations', async ({ page }) => {
    // Open modal
    await page.locator('button[aria-label="Open dialog"]').click();

    // Wait for modal to be visible
    await page.locator('[role="dialog"]').waitFor();

    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa'])
      .analyze();

    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('modal has correct ARIA attributes', async ({ page }) => {
    await page.locator('button[aria-label="Open dialog"]').click();

    const modal = await page.locator('[role="dialog"]');

    // Check role
    await expect(modal).toHaveAttribute('role', 'dialog');

    // Check aria-modal
    await expect(modal).toHaveAttribute('aria-modal', 'true');

    // Check aria-labelledby or aria-label
    const hasLabel = await modal.evaluate(el =>
      el.hasAttribute('aria-labelledby') || el.hasAttribute('aria-label')
    );
    expect(hasLabel).toBe(true);
  });

  test('focus is trapped within modal', async ({ page }) => {
    const triggerButton = await page.locator('button[aria-label="Open dialog"]');
    await triggerButton.click();

    const modal = await page.locator('[role="dialog"]');
    await modal.waitFor();

    // Get all focusable elements within modal
    const focusableElements = await modal.locator(
      'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
    );
    const count = await focusableElements.count();

    // Tab through all elements multiple times
    for (let i = 0; i < count + 2; i++) {
      await page.keyboard.press('Tab');

      // Verify focus stays within modal
      const focusedElement = await page.locator(':focus');
      const isInModal = await focusedElement.evaluate((el, modalEl) => {
        return modalEl.contains(el);
      }, await modal.elementHandle());

      expect(isInModal).toBe(true);
    }
  });

  test('Escape key closes modal', async ({ page }) => {
    await page.locator('button[aria-label="Open dialog"]').click();

    const modal = await page.locator('[role="dialog"]');
    await modal.waitFor();

    // Press Escape
    await page.keyboard.press('Escape');

    // Verify modal is closed
    await expect(modal).not.toBeVisible();
  });

  test('focus returns to trigger button on close', async ({ page }) => {
    const triggerButton = await page.locator('button[aria-label="Open dialog"]');
    await triggerButton.click();

    const modal = await page.locator('[role="dialog"]');
    await modal.waitFor();

    // Close modal
    await page.locator('button[aria-label="Close dialog"]').click();

    // Wait for modal to close
    await expect(modal).not.toBeVisible();

    // Verify focus returned to trigger button
    const focusedElement = await page.locator(':focus');
    expect(await focusedElement.evaluate(el => el.textContent))
      .toBe(await triggerButton.textContent());
  });

  test('modal opens with focus on first focusable element', async ({ page }) => {
    await page.locator('button[aria-label="Open dialog"]').click();

    const modal = await page.locator('[role="dialog"]');
    await modal.waitFor();

    // Check that focus is within modal
    const focusedElement = await page.locator(':focus');
    const isInModal = await focusedElement.evaluate((el, modalEl) => {
      return modalEl.contains(el);
    }, await modal.elementHandle());

    expect(isInModal).toBe(true);
  });
});
```

### Navigation Component Test

```typescript
import { test, expect } from '@playwright/test';
import { AxeBuilder } from '@axe-core/playwright';

test.describe('Navigation Accessibility', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('navigation has no accessibility violations', async ({ page }) => {
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa'])
      .analyze();

    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('navigation uses semantic HTML', async ({ page }) => {
    // Check for <nav> element
    const nav = await page.locator('nav');
    await expect(nav).toBeVisible();

    // Check for aria-label or aria-labelledby
    const hasLabel = await nav.evaluate(el =>
      el.hasAttribute('aria-label') || el.hasAttribute('aria-labelledby')
    );
    expect(hasLabel).toBe(true);
  });

  test('navigation links are keyboard accessible', async ({ page }) => {
    const navLinks = await page.locator('nav a');
    const count = await navLinks.count();

    // Tab to first link
    await page.keyboard.press('Tab');

    for (let i = 0; i < count; i++) {
      const focusedElement = await page.locator(':focus');
      const tagName = await focusedElement.evaluate(el => el.tagName.toLowerCase());

      // Verify focus is on a link
      expect(tagName).toBe('a');

      // Tab to next link
      await page.keyboard.press('Tab');
    }
  });

  test('current page is indicated accessibly', async ({ page }) => {
    const currentLink = await page.locator('nav a[aria-current="page"]');

    // Check that current page link exists
    await expect(currentLink).toBeVisible();

    // Verify aria-current attribute
    await expect(currentLink).toHaveAttribute('aria-current', 'page');
  });

  test('skip link is present and functional', async ({ page }) => {
    // Tab to skip link (usually first focusable element)
    await page.keyboard.press('Tab');

    const focusedElement = await page.locator(':focus');
    const text = await focusedElement.textContent();

    // Check if it's a skip link
    expect(text?.toLowerCase()).toMatch(/skip/);

    // Activate skip link
    await page.keyboard.press('Enter');

    // Verify focus moved to main content
    const newFocus = await page.locator(':focus');
    const targetId = await newFocus.getAttribute('id');
    expect(targetId).toMatch(/main|content/);
  });
});
```

### Button Component Test

```typescript
import { test, expect } from '@playwright/test';
import { AxeBuilder } from '@axe-core/playwright';

test.describe('Button Accessibility', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/components/button');
  });

  test('button has no accessibility violations', async ({ page }) => {
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa'])
      .analyze();

    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('button uses semantic <button> element', async ({ page }) => {
    const button = await page.locator('[role="button"]').first();
    const tagName = await button.evaluate(el => el.tagName.toLowerCase());

    expect(tagName).toBe('button');
  });

  test('button has accessible name', async ({ page }) => {
    const buttons = await page.locator('button');
    const count = await buttons.count();

    for (let i = 0; i < count; i++) {
      const button = buttons.nth(i);

      // Check for text content, aria-label, or aria-labelledby
      const hasAccessibleName = await button.evaluate(el => {
        const text = el.textContent?.trim();
        const ariaLabel = el.getAttribute('aria-label');
        const ariaLabelledBy = el.getAttribute('aria-labelledby');

        return !!(text || ariaLabel || ariaLabelledBy);
      });

      expect(hasAccessibleName).toBe(true);
    }
  });

  test('button is keyboard accessible', async ({ page }) => {
    const button = await page.locator('button').first();

    // Focus button with Tab
    await button.focus();

    // Verify focus
    const focusedElement = await page.locator(':focus');
    expect(await focusedElement.evaluate(el => el.tagName.toLowerCase())).toBe('button');

    // Activate with Enter
    await page.keyboard.press('Enter');

    // Verify button action occurred (adjust based on button behavior)
  });

  test('icon-only buttons have aria-label', async ({ page }) => {
    // Find buttons with SVG/icon children but no text
    const iconButtons = await page.locator('button:has(svg)');
    const count = await iconButtons.count();

    for (let i = 0; i < count; i++) {
      const button = iconButtons.nth(i);
      const text = await button.textContent();

      if (!text?.trim()) {
        // Icon-only button must have aria-label
        const ariaLabel = await button.getAttribute('aria-label');
        expect(ariaLabel).toBeTruthy();
        expect(ariaLabel!.length).toBeGreaterThan(0);
      }
    }
  });

  test('button has visible focus indicator', async ({ page }) => {
    const button = await page.locator('button').first();
    await button.focus();

    // Check for visible focus styles
    const outlineStyle = await button.evaluate(el => {
      const computed = window.getComputedStyle(el);
      return {
        outline: computed.outline,
        outlineWidth: computed.outlineWidth,
        outlineStyle: computed.outlineStyle,
        boxShadow: computed.boxShadow,
      };
    });

    // Verify some focus indicator is present
    const hasFocusIndicator =
      outlineStyle.outlineWidth !== '0px' ||
      outlineStyle.outlineStyle !== 'none' ||
      outlineStyle.boxShadow !== 'none';

    expect(hasFocusIndicator).toBe(true);
  });
});
```

### Full Page Test

```typescript
import { test, expect } from '@playwright/test';
import { AxeBuilder } from '@axe-core/playwright';

test.describe('Page Accessibility', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('page has no critical accessibility violations', async ({ page }) => {
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa'])
      .analyze();

    // No violations at all
    expect(accessibilityScanResults.violations).toEqual([]);

    // Alternative: Check for only critical/serious violations
    const criticalViolations = accessibilityScanResults.violations.filter(
      v => v.impact === 'critical' || v.impact === 'serious'
    );
    expect(criticalViolations).toEqual([]);
  });

  test('page has valid heading hierarchy', async ({ page }) => {
    const headings = await page.locator('h1, h2, h3, h4, h5, h6').all();

    // Extract heading levels
    const levels = await Promise.all(
      headings.map(h => h.evaluate(el => parseInt(el.tagName[1])))
    );

    // Check for exactly one h1
    const h1Count = levels.filter(l => l === 1).length;
    expect(h1Count).toBe(1);

    // Check that headings don't skip levels
    for (let i = 1; i < levels.length; i++) {
      const diff = levels[i] - levels[i - 1];
      expect(diff).toBeLessThanOrEqual(1);
    }
  });

  test('page has proper landmark structure', async ({ page }) => {
    // Check for main landmark
    const main = await page.locator('main, [role="main"]');
    await expect(main).toBeVisible();

    // Check for navigation
    const nav = await page.locator('nav, [role="navigation"]');
    expect(await nav.count()).toBeGreaterThan(0);

    // Optional: Check for header and footer
    const header = await page.locator('header, [role="banner"]');
    const footer = await page.locator('footer, [role="contentinfo"]');

    await expect(header).toBeVisible();
    await expect(footer).toBeVisible();
  });

  test('page is fully keyboard navigable', async ({ page }) => {
    let tabCount = 0;
    const maxTabs = 50; // Prevent infinite loop
    const focusedElements: string[] = [];

    while (tabCount < maxTabs) {
      await page.keyboard.press('Tab');
      tabCount++;

      const focusedElement = await page.locator(':focus');
      const tagName = await focusedElement.evaluate(el => el.tagName);
      focusedElements.push(tagName);

      // Check if we've cycled back to the start
      if (tabCount > 5 && focusedElements[0] === tagName) {
        break;
      }
    }

    // Verify we could tab through the page
    expect(focusedElements.length).toBeGreaterThan(0);
  });

  test('page lang attribute is set', async ({ page }) => {
    const lang = await page.locator('html').getAttribute('lang');

    expect(lang).toBeTruthy();
    expect(lang).toMatch(/^[a-z]{2}(-[A-Z]{2})?$/); // e.g., "en" or "en-US"
  });

  test('page has a descriptive title', async ({ page }) => {
    const title = await page.title();

    expect(title).toBeTruthy();
    expect(title.length).toBeGreaterThan(0);
    expect(title.length).toBeLessThan(60); // Good practice: keep titles concise
  });

  test('all images have alt text', async ({ page }) => {
    const images = await page.locator('img');
    const count = await images.count();

    for (let i = 0; i < count; i++) {
      const img = images.nth(i);

      // Check for alt attribute (even if empty for decorative images)
      const hasAlt = await img.evaluate(el => el.hasAttribute('alt'));
      expect(hasAlt).toBe(true);
    }
  });
});
```

## Test File Naming Convention

Use clear, descriptive names:
- Component tests: `tests/accessibility/[component-name].spec.ts`
- Page tests: `tests/accessibility/[page-name]-page.spec.ts`
- Feature tests: `tests/accessibility/[feature-name].spec.ts`

Examples:
- `tests/accessibility/button.spec.ts`
- `tests/accessibility/login-form.spec.ts`
- `tests/accessibility/checkout-page.spec.ts`
- `tests/accessibility/navigation.spec.ts`

## Configuration Recommendations

When generating tests, also suggest adding to `playwright.config.ts` if not present:

```typescript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  // ... other config

  // Recommended accessibility test settings
  use: {
    // Slower navigation for accessibility tools
    navigationTimeout: 30000,

    // Screenshots on failure for debugging
    screenshot: 'only-on-failure',
  },

  // Separate accessibility test project
  projects: [
    {
      name: 'accessibility',
      testDir: './tests/accessibility',
      use: {
        // Disable parallel tests for consistency
        workers: 1,
      },
    },
  ],
});
```

## Installation Instructions

When generating tests for the first time, remind users to install dependencies:

```bash
npm install -D @playwright/test @axe-core/playwright
# or
yarn add -D @playwright/test @axe-core/playwright
# or
pnpm add -D @playwright/test @axe-core/playwright
```

## Running Generated Tests

Provide commands for running the tests:

```bash
# Run all accessibility tests
npx playwright test tests/accessibility

# Run specific test file
npx playwright test tests/accessibility/button.spec.ts

# Run with UI for debugging
npx playwright test tests/accessibility --ui

# Run in headed mode
npx playwright test tests/accessibility --headed

# Generate HTML report
npx playwright test tests/accessibility --reporter=html
```

## Best Practices for Generated Tests

1. **Be specific**: Test specific accessibility criteria, not just "no violations"
2. **Test interactions**: Verify keyboard, mouse, and touch interactions
3. **Test states**: Check different component states (loading, error, success)
4. **Test responsiveness**: Verify accessibility at different viewport sizes
5. **Test dynamic content**: Ensure ARIA live regions work correctly
6. **Avoid flakiness**: Use proper waits and stable selectors
7. **Document expectations**: Add comments explaining what each test verifies

## Example: Complete Test Generation

When user requests: "Generate accessibility tests for src/components/Dropdown.tsx"

1. Read the component file
2. Analyze its structure and interactions
3. Generate comprehensive tests:

```typescript
import { test, expect } from '@playwright/test';
import { AxeBuilder } from '@axe-core/playwright';

test.describe('Dropdown Accessibility', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/components/dropdown');
  });

  test('dropdown has no accessibility violations', async ({ page }) => {
    // Test closed state
    const closedScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa'])
      .analyze();
    expect(closedScanResults.violations).toEqual([]);

    // Open dropdown
    await page.locator('button[aria-haspopup="listbox"]').click();

    // Test open state
    const openScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa'])
      .analyze();
    expect(openScanResults.violations).toEqual([]);
  });

  test('dropdown has correct ARIA attributes', async ({ page }) => {
    const trigger = await page.locator('button[aria-haspopup="listbox"]');

    // Closed state
    await expect(trigger).toHaveAttribute('aria-expanded', 'false');
    await expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');

    // Open state
    await trigger.click();
    await expect(trigger).toHaveAttribute('aria-expanded', 'true');

    const listbox = await page.locator('[role="listbox"]');
    await expect(listbox).toBeVisible();
    await expect(listbox).toHaveAttribute('role', 'listbox');
  });

  test('dropdown supports keyboard navigation', async ({ page }) => {
    const trigger = await page.locator('button[aria-haspopup="listbox"]');

    // Open with Enter
    await trigger.focus();
    await page.keyboard.press('Enter');

    const listbox = await page.locator('[role="listbox"]');
    await expect(listbox).toBeVisible();

    // Navigate with arrow keys
    await page.keyboard.press('ArrowDown');
    let focused = await page.locator(':focus');
    await expect(focused).toHaveAttribute('role', 'option');

    await page.keyboard.press('ArrowDown');
    focused = await page.locator(':focus');
    await expect(focused).toHaveAttribute('role', 'option');

    // Select with Enter
    await page.keyboard.press('Enter');
    await expect(listbox).not.toBeVisible();
  });

  test('dropdown closes with Escape key', async ({ page }) => {
    const trigger = await page.locator('button[aria-haspopup="listbox"]');
    await trigger.click();

    const listbox = await page.locator('[role="listbox"]');
    await expect(listbox).toBeVisible();

    await page.keyboard.press('Escape');
    await expect(listbox).not.toBeVisible();

    // Focus returns to trigger
    const focused = await page.locator(':focus');
    expect(await focused.evaluate(el => el.textContent))
      .toBe(await trigger.textContent());
  });

  test('dropdown options have proper ARIA roles', async ({ page }) => {
    await page.locator('button[aria-haspopup="listbox"]').click();

    const options = await page.locator('[role="option"]');
    const count = await options.count();

    expect(count).toBeGreaterThan(0);

    // Check each option has proper attributes
    for (let i = 0; i < count; i++) {
      const option = options.nth(i);
      await expect(option).toHaveAttribute('role', 'option');

      // Options should have aria-selected
      const hasSelected = await option.evaluate(el =>
        el.hasAttribute('aria-selected')
      );
      expect(hasSelected).toBe(true);
    }
  });
});
```

## Integration with accessibility-tester Agent

This skill is designed to be invoked by the accessibility-tester agent when generating Playwright tests. The tester agent analyzes components and then uses this skill to generate appropriate tests.

## Remember

- Generate tests that are **specific and actionable**
- Test both **automated scans and manual interactions**
- Cover **multiple component states** (open/closed, error/success, etc.)
- Make tests **maintainable** with clear descriptions
- Provide **helpful error messages** when tests fail
- Always include **axe-core scanning** as baseline
- Test **keyboard accessibility** for all interactive elements
- Verify **ARIA implementation** matches patterns
- Check **focus management** for dynamic content

Your tests should catch accessibility regressions before they reach users and provide clear guidance on what needs to be fixed.

Attribution

deepakkambojdeepakkamboj
View sourceMore from deepakkamboj →
SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Related Skills

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

284972 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

10311 votes

Pentest

PTES-aligned adversarial security audit for backend, frontend, and mobile applications. Produces a CVSS-scored Hacker Report with verified PoCs and phased remediation.

5491 votes
View all in development →