accessibility-test-axe
27
总安装量
4
周安装量
#13962
全站排名
安装命令
npx skills add https://github.com/dengineproblem/agents-monorepo --skill accessibility-test-axe
Agent 安装分布
github-copilot
3
codex
3
amp
2
kimi-cli
2
gemini-cli
2
Skill 文档
Axe-Core Accessibility Testing Expert
ÐкÑпеÑÑ Ð¿Ð¾ авÑомаÑизиÑÐ¾Ð²Ð°Ð½Ð½Ð¾Ð¼Ñ ÑеÑÑиÑÐ¾Ð²Ð°Ð½Ð¸Ñ Ð´Ð¾ÑÑÑпноÑÑи Ñ Ð¸ÑполÑзованием 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@example.com">
Ðнопка без ÑекÑÑа
<!-- ÐлоÑ
о -->
<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 â блокиÑÑйÑе merge пÑи critical наÑÑÑениÑÑ
- ТеÑÑиÑÑйÑе Ñано â иÑполÑзÑйÑе в dev Ñежиме React/Vue
- ÐомбиниÑÑйÑе меÑÐ¾Ð´Ñ â авÑомаÑиÑеÑкие ÑеÑÑÑ + ÑÑÑное ÑеÑÑиÑование
- ÐокÑменÑиÑÑйÑе иÑклÑÑÐµÐ½Ð¸Ñ â обÑÑÑнÑйÑе поÑÐµÐ¼Ñ Ð¿Ñавило оÑклÑÑено
- ÐÑÑлеживайÑе ÑÑÐµÐ½Ð´Ñ â мониÑоÑÑÑе колиÑеÑÑво наÑÑÑений во вÑемени