mcpField
A
accessibility-test-axe

accessibility-test-axe

github.com/dengineproblem测试与质量64 次安装
代码仓库

关于 accessibility-test-axe

使用 axe-core 规划并实施自动化无障碍检查的技能,适用于开发和测试团队,提供浏览器测试、Playwright、Storybook、React 与 CI/CD 示例。

主要功能

  1. 01基于浏览器的 axe-core 审计
  2. 02Playwright 测试集成
  3. 03Storybook 和 React 示例
  4. 04规则配置与违规处理
  5. 05CI/CD 检查与报告

适用场景

  1. 01在开发期间扫描页面
  2. 02将无障碍检查加入 Playwright 测试
  3. 03检查 Storybook 组件
  4. 04在 CI/CD 中跟踪违规

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dengineproblem/agents-monorepo --skill accessibility-test-axe

安装后在 Claude Code 中输入/accessibility-test-axe确认已加载。

详细介绍

Axe-Core 专家级无障碍测试

使用 axe-core 进行无障碍自动化测试的专家实践。axe-core 是用于检查 WCAG 合规性的行业标准。

核心理念

采用 左移(shift-left)方法——在开发早期集成无障碍检查,而不是等到发布后再检查。将 axe-core 自动扫描与手动测试结合起来。

配置 axe-core

安装
npm install axe-core @axe-core/playwright @axe-core/react
在浏览器中基本使用
import axe from 'axe-core';

async function runAccessibilityAudit(element = document) {
  try {
    const results = await axe.run(element, {
      runOnly: {
        type: 'tag',
        values: ['wcag2a', 'wcag2aa', 'wcag21aa']
      }
    });

    return {
      violations: results.violations,
      passes: results.passes,
      incomplete: results.incomplete,
      inapplicable: results.inapplicable
    };
  } catch (error) {
    console.error('Accessibility audit failed:', error);
    throw error;
  }
}

与 Playwright 集成

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

test.describe('Accessibility Tests', () => {
  test('should not have accessibility violations', async ({ page }) => {
    await page.goto('/');

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

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

  test('specific component accessibility', async ({ page }) => {
    await page.goto('/components/modal');

    const results = await new AxeBuilder({ page })
      .include('#modal-component')
      .exclude('.decorative-element')
      .analyze();

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

与 Storybook 集成

// .storybook/test-runner.js
const { injectAxe, checkA11y } = require('axe-playwright');

module.exports = {
  async preRender(page) {
    await injectAxe(page);
  },
  async postRender(page) {
    await checkA11y(page, '#storybook-root', {
      detailedReport: true,
      detailedReportOptions: {
        html: true
      }
    });
  }
};

CI/CD 集成(GitHub Actions)

name: Accessibility Tests

on: [push, pull_request]

jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright
        run: npx playwright install --with-deps

      - name: Run accessibility tests
        run: npm run test:a11y

      - name: Upload results
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: a11y-report
          path: a11y-results.json

处理结果

function processViolations(violations) {
  const report = {
    critical: [],
    serious: [],
    moderate: [],
    minor: []
  };

  violations.forEach(violation => {
    const issue = {
      id: violation.id,
      impact: violation.impact,
      description: violation.description,
      help: violation.help,
      helpUrl: violation.helpUrl,
      nodes: violation.nodes.map(node => ({
        html: node.html,
        target: node.target,
        failureSummary: node.failureSummary
      }))
    };

    report[violation.impact].push(issue);
  });

  return report;
}

规则配置

const axeConfig = {
  rules: [
    // Отключение правил для декоративных элементов
    { id: 'image-alt', enabled: true },
    { id: 'color-contrast', enabled: true },
    { id: 'label', enabled: true },

    // Исключения для специфичных случаев
    {
      id: 'button-name',
      selector: 'button:not(.icon-only)'
    }
  ],

  // Исключение областей
  exclude: [
    '.third-party-widget',
    '#ads-container'
  ]
};

React 集成

import React from 'react';
import ReactDOM from 'react-dom';
import axe from '@axe-core/react';

if (process.env.NODE_ENV !== 'production') {
  axe(React, ReactDOM, 1000);
}

// Компонент отчёта
function AccessibilityReport({ violations }) {
  if (!violations.length) {
    return <div className="a11y-pass">No accessibility violations found</div>;
  }

  return (
    <div className="a11y-violations" role="alert">
      <h2>Accessibility Issues Found: {violations.length}</h2>
      <ul>
        {violations.map(violation => (
          <li key={violation.id}>
            <strong>{violation.impact}</strong>: {violation.description}
            <a href={violation.helpUrl} target="_blank" rel="noopener">
              Learn more
            </a>
          </li>
        ))}
      </ul>
    </div>
  );
}

常见违规及修复方法

颜色对比度不足
/* Плохо */
.text-light {
  color: #999;
  background: #fff;
}

/* Хорошо - соотношение 4.5:1 */
.text-light {
  color: #767676;
  background: #fff;
}
表单缺少 label

<input type="email" placeholder="Email">


<label for="email">Email</label>
<input type="email" id="email" placeholder="[email protected]">
按钮没有文本

<button><svg>...</svg></button>


<button aria-label="Close menu">
  <svg aria-hidden="true">...</svg>
</button>

监控与报告

class AccessibilityMonitor {
  constructor() {
    this.history = [];
  }

  async audit(url) {
    const results = await runAccessibilityAudit();

    this.history.push({
      timestamp: new Date().toISOString(),
      url,
      violationCount: results.violations.length,
      violations: results.violations
    });

    return this.generateTrend();
  }

  generateTrend() {
    const recent = this.history.slice(-10);
    return {
      current: recent[recent.length - 1]?.violationCount || 0,
      trend: this.calculateTrend(recent),
      history: recent
    };
  }
}

最佳实践

  1. 集成到 CI/CD 中——存在严重违规时阻止合并
  2. 尽早测试——在 React/Vue 开发模式下使用
  3. 结合多种方法——自动化测试 + 手动测试
  4. 记录例外情况——说明停用规则的原因
  5. 跟踪趋势——监控违规数量随时间的变化

评论

支持 Markdown