All skills
fugazi avatar

/a11y-playwright-testing

@2149484

Accessibility testing for web applications using Playwright (@playwright/test), TypeScript, and axe-core. Use to write, run, or debug WCAG 2.2 AA checks, keyboard and focus tests, ARIA/semantic validation, accessible names, form labels, color contrast, or screen-reader test patterns. Keywords: accessibility, WCAG, axe-core, keyboard navigation, focus management, ARIA.

Use this Skill: https://skilld.dev/gh/fugazi/test-automation-skills-agents/a11y-playwright-testing

This session only. Nothing lands on disk.

referencessnippets-visual-names-checklist.md

≈1.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

A11y Snippets: Visual Accessibility, Accessible Names, Critical Pages

Part of the a11y-playwright-testing skill. See SKILL.md for full context.

Related: this file was split from the original snippets.md. See the other related files in this folder.

Visual Accessibility

Reduced Motion

test("respects prefers-reduced-motion", async ({ page }) => {
  await page.emulateMedia({ reducedMotion: "reduce" });
  await page.goto("/");

  // Check animations are disabled
  const hero = page.getByTestId("hero-animation");
  const animationDuration = await hero.evaluate(
    (el) => getComputedStyle(el).animationDuration,
  );

  expect(animationDuration).toBe("0s");
});

High Contrast Mode

test("works in high contrast mode", async ({ page }) => {
  await page.emulateMedia({ forcedColors: "active" });
  await page.goto("/");

  // Verify key elements remain visible
  await expect(page.getByRole("navigation")).toBeVisible();
  await expect(page.getByRole("main")).toBeVisible();

  // Buttons should be identifiable
  const primaryButton = page.getByRole("button", { name: "Get started" });
  await expect(primaryButton).toBeVisible();
});

Focus Visibility

test("focus indicator is visible", async ({ page }) => {
  await page.goto("/");

  // Tab to focusable element
  await page.keyboard.press("Tab");
  const focusedElement = page.locator(":focus");

  // Check focus is visible (has outline or other indicator)
  const outline = await focusedElement.evaluate((el) => {
    const styles = getComputedStyle(el);
    return {
      outlineWidth: styles.outlineWidth,
      outlineStyle: styles.outlineStyle,
      boxShadow: styles.boxShadow,
    };
  });

  // Should have visible focus indicator
  const hasVisibleFocus =
    (outline.outlineWidth !== "0px" && outline.outlineStyle !== "none") ||
    outline.boxShadow !== "none";

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

Accessible Names

Buttons Have Names

test("all buttons have accessible names", async ({ page }) => {
  await page.goto("/");

  const buttons = page.getByRole("button");
  const count = await buttons.count();

  for (let i = 0; i < count; i++) {
    const button = buttons.nth(i);
    const name = await button.evaluate(
      (el) => el.textContent?.trim() || el.getAttribute("aria-label"),
    );
    expect(name, `Button ${i} lacks accessible name`).toBeTruthy();
  }
});

Images Have Alt Text

test("informative images have alt text", async ({ page }) => {
  await page.goto("/");

  const images = page.locator('img:not([role="presentation"]):not([alt=""])');
  const count = await images.count();

  for (let i = 0; i < count; i++) {
    const img = images.nth(i);
    const alt = await img.getAttribute("alt");
    expect(alt, `Image ${i} missing alt text`).toBeTruthy();
  }
});

Links Have Purpose

test("links convey purpose", async ({ page }) => {
  await page.goto("/");

  const links = page.getByRole("link");
  const count = await links.count();

  const genericNames = ["click here", "read more", "learn more", "here"];

  for (let i = 0; i < count; i++) {
    const link = links.nth(i);
    const name = await link.textContent();

    // Link should not have generic, non-descriptive text
    const isGeneric = genericNames.some(
      (generic) => name?.toLowerCase().trim() === generic,
    );
    expect(isGeneric, `Link "${name}" is not descriptive`).toBe(false);
  }
});

Test Data: Critical Pages Checklist

// tests/a11y/critical-pages.spec.ts
import { test } from "@playwright/test";
import { runA11yCheck } from "./a11y-helper";

const criticalPages = [
  { name: "Homepage", path: "/" },
  { name: "Login", path: "/login" },
  { name: "Registration", path: "/register" },
  { name: "Contact", path: "/contact" },
  { name: "Search results", path: "/search?q=test" },
  { name: "Product detail", path: "/products/1" },
  { name: "Shopping cart", path: "/cart" },
  { name: "Checkout", path: "/checkout" },
  { name: "Error 404", path: "/nonexistent-page" },
];

for (const page of criticalPages) {
  test(`${page.name} is accessible`, async ({
    page: playwrightPage,
  }, testInfo) => {
    await playwrightPage.goto(page.path);
    await runA11yCheck(playwrightPage, testInfo);
  });
}

Source: SKILL.md on GitHub

1 warning16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides a comprehensive toolkit for automated accessibility testing using Playwright and axe-core. It includes numerous code snippets, project structure recommendations, and WCAG 2.2 AA compliance checklists. The security analysis identified an indirect prompt injection surface inherent to the process of testing untrusted web content, where external DOM data is processed by the agent.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    4/4 files flagged

Signed by skilld at 2149484. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 4 days ago.

Activeupdated 2 months ago

README badge

README badge for fugazi/test-automation-skills-agents/a11y-playwright-testing