Skip to content
Testing
Skill

/kb-playwright-testing

Reference data, not a reviewer. Browser accessibility testing using Playwright and @axe-core/playwright. Keyboard scans, contrast verification, and accessibility tree snapshots.

From plugin
accessibility-agents
431113 skills7 hooks

Claude Code

$ npx -y skills add Community-Access/accessibility-agents --skill kb-playwright-testing --agent claude-code

SKILL.md

kb-playwright-testing.SKILL.md
name: kb-playwright-testing
description: "Reference data, not a reviewer. Browser accessibility testing using Playwright and @axe-core/playwright. Keyboard scans, contrast verification, and accessibility tree snapshots."
license: MIT
disable-model-invocation: true
user-invocable: false
metadata:
  tier: reference
  domain: cross-cutting
  output: none
  effort: low
  title: Playwright Testing

Playwright Accessibility Testing

Reusable knowledge module for browser-based accessibility testing using Playwright and @axe-core/playwright.

MCP Tools Available

Each tool the MCP server registers (`mcp-server/tools/playwright-tools.js`), with its purpose and whether it needs @axe-core/playwright. All of them need Playwright with Chromium. There is no state-scan tool: to scan content revealed by an interaction, script the interaction and run `AxeBuilder` yourself (see Scan After Interaction).

| Tool | Purpose | Requires @axe-core/playwright | |------|---------|------------------------------| | `run_axe_scan` | axe-core scan of a URL; default tags `wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa` | Yes | | `run_playwright_keyboard_scan` | Tab-order traversal, focus visibility, keyboard trap detection | No | | `run_playwright_viewport_scan` | Horizontal scrolling at 320, 768, 1024 and 1280 CSS px (1.4.10 Reflow only; no zoom, text resize or target-size check) | No | | `run_playwright_contrast_scan` | Computed-style text contrast against the AA thresholds (4.5:1, or 3:1 for large text) | No | | `run_playwright_a11y_tree` | Browser accessibility tree snapshot (`ariaSnapshot`) | No |

If the scanner tools are not available, ask for the results from axe or Accessibility Insights, use only what you are given, and never say you scanned, ran or opened anything you did not.

@axe-core/playwright Patterns

Full Page Scan

import AxeBuilder from '@axe-core/playwright';

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

In axe-core 4.14 the only `wcag22aa` rule is `target-size`, which runs only when that tag is listed. Most WCAG 2.2 criteria (2.4.11, 2.5.7, 3.2.6, 3.3.7, 3.3.8) have no axe rule and need manual or scripted checks. `AxeBuilder` needs a page opened from a browser context; the Playwright Test `page` fixture is one, `browser.newPage()` is not.

Scoped Element Scan

const results = await new AxeBuilder({ page })
  .include('.modal-content')
  .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
  .analyze();

Single Rule Verification

const results = await new AxeBuilder({ page })
  .include('#hero-image')
  .withRules(['image-alt'])
  .analyze();
expect(results.violations).toEqual([]);

Scan After Interaction

await page.locator('[aria-expanded="false"]').first().click();
await page.locator('.accordion-content').first().waitFor({ state: 'visible' });
const results = await new AxeBuilder({ page })
  .include('.accordion-content')
  .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
  .analyze();

Keyboard Traversal Patterns

Record Tab Sequence

const tabStops = [];
for (let i = 0; i < maxTabs; i++) {
  await page.keyboard.press('Tab');
  const info = await page.evaluate(() => {
    const el = document.activeElement;
    return {
      tagName: el?.tagName,
      role: el?.getAttribute('role'),
      name: el?.getAttribute('aria-label') || el?.textContent?.trim().slice(0, 50),
      id: el?.id,
      tabIndex: el?.tabIndex
    };
  });
  tabStops.push(info);
}

Detect Keyboard Traps

A keyboard trap is detected when the same element receives focus after consecutive Tab presses:

let trapCount = 0;
let lastSelector = '';
for (const stop of tabStops) {
  const currentSelector = `${stop.tagName}#${stop.id}`;
  if (currentSelector === lastSelector) {
    trapCount++;
    if (trapCount >= 3) { /* TRAP DETECTED */ }
  } else {
    trapCount = 0;
  }
  lastSelector = currentSelector;
}

Focus Management After Modal Open

await page.locator('[data-modal-trigger]').click();
await page.getByRole('dialog').waitFor({ state: 'visible' });
const focusedRole = await page.evaluate(() =>
  document.activeElement?.closest('[role="dialog"]') ? 'inside-dialog' : 'outside-dialog'
);
// focusedRole should be 'inside-dialog'

Focus Management Test Templates

Modal Focus Trap Test

test('modal traps focus correctly', async ({ page }) => {
  await page.goto(url);
  await page.locator('[data-open-modal]').click();
  await expect(page.getByRole('dialog')).toBeVisible();

  // Focus should be inside the dialog
  const inDialog = await page.evaluate(() =>
    document.activeElement?.closest('[role="dialog"]') !== null
  );
  expect(inDialog).toBe(true);

  // Tab through dialog - should not escape
  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);
  }

  // Escape should close and return focus to trigger
  await page.keyboard.press('Escape');
  const focusedId = await page.evaluate(() => document.activeElement?.id);
  expect(focusedId).toBe('modal-trigger-id');
});

Skip Link Test

test('skip link moves focus to main content', async ({ page }) => {
  await page.goto(url);
  await page.keyboard.press('Tab'); // Focus skip link
  await page.keyboard.press('Enter'); // Activate it
  const focusedId = await page.evaluate(() => document.activeElement?.id);
  expect(focusedId).toBe('main-content');
});

CI Integration

GitHub Actions with Playwright

name: Accessibility Tests
on: [push, pull_request]

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/se
Read more
@community-access
Ships withaccessibility-agents

WCAG 2.2 AA enforcement for agentic coding, as a set of Agent Skills. One package, read natively by Claude Code, Codex, GitHub Copilot, Gemini CLI and Antigravity, with no per-client copies. Models forget accessibility while generating code.

Stars on GitHub

Momentum

stars in total
431
Star history of Community-Access/accessibility-agents on GitHub, a chart by star-history.com

Other skills on accessibility-agents.

More skills from other plugins.

api-review

api-review

Produce the "Playwright 1.xx API Review" artifact for a range of commits - the public API additions and the breaking or behavior…

@microsoft@microsoftInstall Skill
add-e2e-selectors

add-e2e-selectors

Add reliable @grafana/e2e-selectors to interactive elements and key containers in the Grafana frontend. Use when adding e2e…

grafanatoday78k
@grafana@grafanaInstall Skill
promptfoo-evals

promptfoo-evals

Write, refine, run, and QA promptfoo evaluation suites: promptfooconfig.yaml, prompts, providers, vars, tests, assertions,…

@promptfoo@promptfooInstall Skill
deepeval

deepeval

DeepEval evaluation workflow for AI agents and LLM applications. TRIGGER when the user wants to evaluate or improve an AI agent,…

@confident-ai@confident-aiInstall Skill

Questions about kb-playwright-testing.

Which coding agents can use kb-playwright-testing?
kb-playwright-testing installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.