CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-storybook--addon-storyshots-puppeteer

Image snapshots addition to StoryShots based on puppeteer

Pending
Overview
Eval results
Files

axe-test.mddocs/

Accessibility Testing

Automated accessibility audits using Axe Core integration to verify component accessibility compliance with WCAG guidelines.

Capabilities

Axe Test Function

Creates a test function that runs accessibility audits on Storybook stories using @axe-core/puppeteer integration.

/**
 * Creates a test function for automated accessibility testing
 * @param customConfig - Optional configuration to override defaults
 * @returns Test function that performs accessibility audits
 */
function axeTest(customConfig?: Partial<AxeConfig>): TestFunction;

interface AxeConfig extends CommonConfig {
  /** Hook executed before accessibility test runs */
  beforeAxeTest: (page: Page, options: Options) => Promise<void>;
}

Usage Examples:

import initStoryshots from '@storybook/addon-storyshots';
import { axeTest } from '@storybook/addon-storyshots-puppeteer';

// Basic accessibility testing
initStoryshots({ 
  suite: 'A11y checks', 
  test: axeTest() 
});

// With pre-test setup
initStoryshots({
  suite: 'Accessibility audits',
  test: axeTest({
    storybookUrl: 'http://localhost:6006',
    beforeAxeTest: async (page) => {
      // Wait for dynamic content
      await page.waitForSelector('[data-loaded="true"]');
      // Set focus state for testing
      await page.focus('input[type="text"]');
    }
  })
});

Story Parameter Integration

Accessibility test configuration uses the same a11y parameters as @storybook/addon-a11y for consistency.

interface A11yParameters {
  /** Element selector to test (default: '#storybook-root') */
  element?: string;
  /** Elements to exclude from testing */
  exclude?: string | string[];
  /** Axe rules to disable */
  disabledRules?: string[];
  /** Axe run options */
  options?: AxeRunOptions;
  /** Axe configuration */
  config?: AxeConfig;
}

interface AxeRunOptions {
  /** Include only specific rules */
  runOnly?: string[] | { type: 'rule' | 'tag'; values: string[] };
  /** Rule configuration overrides */
  rules?: { [ruleId: string]: { enabled: boolean } };
  /** Tags to include */
  tags?: string[];
  /** Result types to return */
  resultTypes?: ('violations' | 'incomplete' | 'passes' | 'inapplicable')[];
}

Usage Examples:

// Story with accessibility configuration
export const AccessibleForm = () => <ContactForm />;

AccessibleForm.parameters = {
  a11y: {
    element: '#contact-form',
    exclude: ['.skip-a11y-test'],
    disabledRules: ['color-contrast'], // Temporary exclusion
    options: {
      tags: ['wcag2a', 'wcag2aa'],
      runOnly: {
        type: 'tag',
        values: ['wcag2a', 'wcag2aa']
      }
    }
  }
};

// Testing specific accessibility rules
export const ColorContrastTest = () => <ThemeProvider><Button /></ThemeProvider>;

ColorContrastTest.parameters = {
  a11y: {
    options: {
      runOnly: ['color-contrast']
    }
  }
};

Axe Core Integration

Direct integration with @axe-core/puppeteer provides comprehensive accessibility testing capabilities.

/**
 * Axe Puppeteer instance methods used internally
 */
interface AxePuppeteer {
  /** Include elements in accessibility scan */
  include(selector: string): AxePuppeteer;
  /** Exclude elements from accessibility scan */
  exclude(selector: string | string[]): AxePuppeteer;
  /** Set axe-core options */
  options(options: AxeRunOptions): AxePuppeteer;
  /** Disable specific rules */
  disableRules(rules: string | string[]): AxePuppeteer;
  /** Configure axe-core */
  configure(config: AxeConfig): AxePuppeteer;
  /** Run accessibility analysis */
  analyze(): Promise<AxeResults>;
}

interface AxeResults {
  /** Accessibility violations found */
  violations: AxeViolation[];
  /** Incomplete tests */
  incomplete: AxeIncomplete[];
  /** Passing tests */
  passes: AxePass[];
  /** Inapplicable tests */
  inapplicable: AxeInapplicable[];
}

interface AxeViolation {
  /** Rule identifier */
  id: string;
  /** Impact level */
  impact: 'minor' | 'moderate' | 'serious' | 'critical';
  /** Human-readable description */
  description: string;
  /** Help text and documentation */
  help: string;
  /** Help URL for more information */
  helpUrl: string;
  /** Elements that failed the rule */
  nodes: AxeNodeResult[];
}

Test Execution Flow

The axe test follows a specific execution pattern for consistent accessibility auditing.

/**
 * Internal test execution steps
 */
interface AxeTestFlow {
  /** 1. Execute beforeAxeTest hook */
  beforeAxeTest: (page: Page, options: Options) => Promise<void>;
  /** 2. Create AxePuppeteer instance */
  createAxeInstance: (page: Page) => AxePuppeteer;
  /** 3. Configure accessibility test */
  configureAxe: (axe: AxePuppeteer, parameters: A11yParameters) => AxePuppeteer;
  /** 4. Run accessibility analysis */
  analyzeAccessibility: () => Promise<AxeResults>;
  /** 5. Assert no violations */
  assertNoViolations: (violations: AxeViolation[]) => void;
}

Test Flow Example:

// Internal test execution (for reference)
async function testBody(page, testOptions) {
  const {
    element = '#storybook-root',
    exclude,
    disabledRules,
    options,
    config,
  } = testOptions.context.parameters.a11y || {};
  
  // 1. Pre-test setup
  await beforeAxeTest(page, options);
  
  // 2. Create and configure Axe instance
  const axe = new AxePuppeteer(page);
  axe.include(element);
  
  if (exclude) axe.exclude(exclude);
  if (options) axe.options(options);
  if (disabledRules) axe.disableRules(disabledRules);
  if (config) axe.configure(config);
  
  // 3. Run analysis and assert
  const { violations } = await axe.analyze();
  expect(violations).toHaveLength(0);
}

Before Test Hook

Configure page state before accessibility testing runs.

/**
 * Hook for pre-test setup
 * @param page - Puppeteer page instance
 * @param options - Test context and URL information
 * @returns Promise for async setup operations
 */
type BeforeAxeTest = (page: Page, options: Options) => Promise<void>;

Usage Examples:

// Wait for dynamic content to load
const beforeAxeTest = async (page, { context }) => {
  if (context.story.includes('loading')) {
    await page.waitForSelector('[data-loaded="true"]', { timeout: 5000 });
  }
  
  // Allow animations to complete
  await page.waitForTimeout(600);
  
  // Set up specific interaction states
  if (context.story.includes('focus')) {
    await page.focus('input[type="email"]');
  }
};

// Complex interaction setup
const beforeAxeTest = async (page) => {
  // Open modal or dropdown
  await page.click('[aria-haspopup="true"]');
  await page.waitForSelector('[role="dialog"]');
  
  // Wait for any transitions
  await page.waitForFunction(
    () => window.getComputedStyle(document.querySelector('[role="dialog"]')).opacity === '1'
  );
};

initStoryshots({
  suite: 'Dynamic accessibility',
  test: axeTest({ beforeAxeTest })
});

Default Configuration

Default accessibility testing configuration targets the main Storybook root element.

const defaultAxeConfig: AxeConfig = {
  ...defaultCommonConfig,
  beforeAxeTest: () => Promise.resolve(),
};

// Default story parameters when none specified
const defaultA11yParameters: A11yParameters = {
  element: '#storybook-root',
  exclude: undefined,
  disabledRules: undefined,
  options: undefined,
  config: undefined
};

Install with Tessl CLI

npx tessl i tessl/npm-storybook--addon-storyshots-puppeteer

docs

axe-test.md

configuration.md

image-snapshot.md

index.md

puppeteer-test.md

tile.json