关于 accessibility-test-axe
使用 axe-core 规划并实施自动化无障碍检查的技能,适用于开发和测试团队,提供浏览器测试、Playwright、Storybook、React 与 CI/CD 示例。
主要功能
- 01基于浏览器的 axe-core 审计
- 02Playwright 测试集成
- 03Storybook 和 React 示例
- 04规则配置与违规处理
- 05CI/CD 检查与报告
适用场景
- 01在开发期间扫描页面
- 02将无障碍检查加入 Playwright 测试
- 03检查 Storybook 组件
- 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
};
}
}
最佳实践
- 集成到 CI/CD 中——存在严重违规时阻止合并
- 尽早测试——在 React/Vue 开发模式下使用
- 结合多种方法——自动化测试 + 手动测试
- 记录例外情况——说明停用规则的原因
- 跟踪趋势——监控违规数量随时间的变化
评论
支持 Markdown