TL;DR — axe-core + Playwright をGitHub ActionsのCIに組み込み、プルリクエスト段階でWCAG 2.1/2.2違反を自動検出する。AIによる修正提案を加えることで、デザイン→実装→リリースの全フェーズでアクセシビリティ品質ゲートを機能させる。
はじめに:なぜ今、アクセシビリティCIなのか
「アクセシビリティは後でやる」——そう言い続けた結果、リリース前に数十件のWCAG違反が見つかって修正コストが跳ね上がる。この構造的な問題を解決するには、コードレビューと同じタイミングで自動チェックを走らせる仕組みが必要だ。
2023年10月にW3CはWCAG 2.2を公式勧告として策定し、モバイル操作性・認知負荷に関する9つの新基準を追加した。EU Web Accessibility Directive・日本のJIS X 8341-3:2016(経済産業省参照)も準拠を義務化する方向に進んでいる。
本記事では以下を実装する:
- axe-core + Playwright によるWCAG自動テストをGitHub Actionsに組み込む
- Lighthouse CIでスコアゲートを設定する
- AIによる違反箇所の修正提案を自動生成する
- デザイン→実装の品質ゲートを設計する
ツール選定の整理
アクセシビリティ自動チェックの主要ツールは3種類ある。
| ツール | 検出率 | 統合容易性 | 主な用途 |
|---|---|---|---|
| axe-core | WCAG自動検出可能項目の約57%(公式値) | 高(npm package) | 単体・E2Eテスト |
| Lighthouse | パフォーマンス含む総合スコア | 高(CLI/CI対応) | スコアゲート設定 |
| Playwright | 単体では非検出 | 高(axe-coreと組合せ) | ブラウザE2Eテスト |
axe-coreの検出率57%はDequeの公式ドキュメントに基づく数値で、自動化できない「知覚可能か」「操作可能か」の判断は手動レビューで補完する必要がある。
WCAG 2.1/2.2 主要基準の実装チェックリスト
WCAG 2.2(2023-10-05公開、W3C公式)は4原則(POUR)で構成される。CI自動化に対応できる基準と、手動確認が必要な基準を区別して管理する。
自動検出可能な基準
| 基準 | レベル | チェック内容 | 自動化 |
|---|---|---|---|
| 1.1.1 非テキストコンテンツ | A | img の alt 属性存在確認 | axe-core |
| 1.3.1 情報と関係 | A | セマンティックHTML(<header>, <nav> 等) | axe-core |
| 1.4.3 コントラスト(最低限) | AA | テキスト色と背景色のコントラスト比4.5:1以上 | axe-core |
| 1.4.11 非テキストコントラスト | AA | UIコンポーネントの3:1コントラスト | axe-core |
| 2.1.1 キーボード操作 | A | タブ操作でフォーカス移動可能 | Playwright |
| 2.4.3 フォーカス順序 | A | DOM順序に沿ったフォーカス | axe-core |
| 2.4.7 フォーカスの可視性 | AA | フォーカスリングの表示 | Playwright |
| 3.1.1 ページの言語 | A | <html lang> 属性の存在 | axe-core |
| 3.3.1 エラーの特定 | A | フォームエラーのテキスト表示 | axe-core |
| 4.1.1 構文解析 | A | HTMLバリデーション(id重複等) | axe-core |
| 4.1.2 名前・役割・値 | A | aria-* 属性の正確な使用 | axe-core |
WCAG 2.2 新基準(要手動確認)
| 基準 | レベル | チェック内容 |
|---|---|---|
| 2.4.11 フォーカスの非遮蔽(最小) | AA | フォーカス時にUI要素が隠れない |
| 2.4.12 フォーカスの非遮蔽(強化) | AAA | フォーカス時に完全表示 |
| 2.5.7 ドラッグ操作 | AA | ドラッグ操作に代替手段あり |
| 2.5.8 ターゲットサイズ(最小) | AA | クリック可能領域24×24px以上 |
| 3.2.6 一貫したヘルプ | A | ヘルプリンクの配置一貫性 |
| 3.3.7 冗長な認証 | A | 認知テスト不要な認証フロー |
| 3.3.8 アクセシブルな認証(最小) | AA | コピペ可能なパスワード入力 |
GitHub Actions への組み込み実装
Step 1: 依存パッケージのインストール
npm install --save-dev @axe-core/playwright @playwright/test lighthouse
Step 2: Playwright + axe-core テストファイル
// tests/a11y/accessibility.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
const TARGET_PAGES = [
{ name: 'トップページ', path: '/' },
{ name: 'お問い合わせ', path: '/contact' },
{ name: 'ブログ一覧', path: '/blog' },
];
for (const { name, path } of TARGET_PAGES) {
test(`WCAG 2.1 AA: ${name}`, async ({ page }) => {
await page.goto(path);
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
// 違反件数を出力(CI ログで確認可能)
if (results.violations.length > 0) {
console.log(`violations on ${name}:`);
results.violations.forEach(v => {
console.log(` [${v.impact}] ${v.id}: ${v.description}`);
v.nodes.forEach(n => console.log(` → ${n.target}`));
});
}
expect(results.violations).toHaveLength(0);
});
}
Step 3: GitHub Actions YAML
# .github/workflows/accessibility.yml
name: Accessibility CI
on:
pull_request:
branches: [main, develop]
push:
branches: [main]
jobs:
axe-check:
name: axe-core WCAG Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Build application
run: npm run build
- name: Start application (background)
run: npm start &
env:
PORT: 3000
- name: Wait for server
run: npx wait-on http://localhost:3000 --timeout 30000
- name: Run axe-core accessibility tests
run: npx playwright test tests/a11y/
env:
BASE_URL: http://localhost:3000
- name: Upload test results
if: failure()
uses: actions/upload-artifact@v4
with:
name: a11y-test-results
path: test-results/
lighthouse-check:
name: Lighthouse Accessibility Score Gate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v12
with:
urls: |
http://localhost:3000/
http://localhost:3000/blog
budgetPath: ./lighthouse-budget.json
uploadArtifacts: true
temporaryPublicStorage: true
ai-suggestion:
name: AI Violation Fix Suggestions
needs: axe-check
if: failure()
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download test results
uses: actions/download-artifact@v4
with:
name: a11y-test-results
path: test-results/
- name: Generate AI fix suggestions
run: node scripts/ai-a11y-suggest.js
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
Step 4: Lighthouse スコアゲート設定
// lighthouse-budget.json
[
{
"path": "/*",
"resourceSizes": [],
"resourceCounts": [],
"timings": [],
"scores": [
{ "id": "accessibility", "minScore": 0.9 }
]
}
]
AIによる違反箇所の修正提案
CIが失敗したとき、どこをどう直すか分かりにくい——これがアクセシビリティ改善の最大の摩擦点だ。axe-coreのviolationオブジェクトをLLMに渡すことで、具体的なコード修正提案を自動生成できる。
AI修正提案スクリプト
// scripts/ai-a11y-suggest.js
const fs = require('fs');
const path = require('path');
async function generateFixSuggestions() {
const resultsDir = 'test-results';
const violations = [];
// テスト結果からviolationを収集
const files = fs.readdirSync(resultsDir)
.filter(f => f.endsWith('.json'));
for (const file of files) {
const data = JSON.parse(
fs.readFileSync(path.join(resultsDir, file), 'utf8')
);
if (data.violations) {
violations.push(...data.violations);
}
}
if (violations.length === 0) {
console.log('No violations found.');
return;
}
// OpenAI API に修正提案を依頼
const prompt = buildPrompt(violations);
const suggestion = await callOpenAI(prompt);
// GitHub Actionsのサマリーに出力
const summary = `## アクセシビリティ違反の修正提案\n\n${suggestion}`;
fs.appendFileSync(
process.env.GITHUB_STEP_SUMMARY || 'a11y-suggestions.md',
summary
);
}
function buildPrompt(violations) {
const violationText = violations
.map(v => `- [${v.impact}] ${v.id}: ${v.description}
影響ノード: ${v.nodes.map(n => n.target.join(', ')).join('; ')}
修正ヒント: ${v.nodes[0]?.failureSummary || 'N/A'}`)
.join('\n');
return `以下のWCAG違反を修正するHTMLコード例を提案してください。
各違反について、Before/Afterのコードスニペットを示してください。
${violationText}`;
}
async function callOpenAI(prompt) {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify({
model: 'gpt-4o',
messages: [
{
role: 'system',
content: 'あなたはWCAGアクセシビリティの専門家です。具体的なコード修正例を提供します。',
},
{ role: 'user', content: prompt },
],
max_tokens: 2000,
}),
});
const data = await response.json();
return data.choices[0]?.message?.content || 'AI提案の生成に失敗しました。';
}
generateFixSuggestions().catch(console.error);
このスクリプトはGitHub Actionsのジョブサマリーに修正提案を書き込む。PRのActionsタブから開発者が確認でき、コードレビューと同じ文脈で修正方針を議論できる。
デザイン→実装の品質ゲート設計
アクセシビリティ問題は実装段階で発見するより、デザイン段階で防ぐ方が修正コストが低い(経験則:実装後修正は設計段階の5〜10倍のコスト)。
品質ゲートのフェーズ設計
[デザイン] → [コーディング] → [PR] → [ステージング] → [本番]
↓ ↓ ↓ ↓
コントラスト比 ユニットテスト axe-core CI Lighthouse 実ユーザー
チェック(Figma) (axe-core) YAML スコアゲート フィードバック
フェーズ別チェックポイント
フェーズ1: デザイン段階
- Figmaプラグイン「Contrast」でコントラスト比を確認する(AA基準: 4.5:1)
- コンポーネントのフォーカス状態を必ず設計に含める
- カラーだけで情報を伝えない(1.4.1 色の使用)
フェーズ2: 実装段階
- セマンティックHTMLを使う(
<div>ではなく<button>,<nav>等) axe-coreのjest-axeを使った単体テスト
// tests/components/Button.test.js
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import Button from './Button';
expect.extend(toHaveNoViolations);
test('Button has no axe violations', async () => {
const { container } = render(<Button>送信</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
フェーズ3: PR段階
- 本記事のGitHub Actions YAMLが自動実行する
- 違反があればPRをブロック(required status checkに設定)
フェーズ4: ステージング段階
- Lighthouse CI でスコア90以上を維持
- スクリーンリーダー(NVDA/VoiceOver)での手動確認
他ツールとの連携
アクセシビリティCIは単独で運用するよりも、AIコードレビューやAI品質ゲートと組み合わせることで効果が増す。
具体的には、AIデザインシステムでコンポーネント設計段階からアクセシビリティ属性を標準化し、AI開発計画でスプリント単位のアクセシビリティ改善目標を設定する流れが効果的だ。
CI パイプラインの全体像
PR作成
│
├── [axe-core] WCAG 2.1/2.2 AA違反チェック
│ ↓ FAIL → AI修正提案を GITHUB_STEP_SUMMARY に出力
│
├── [Lighthouse CI] アクセシビリティスコア ≥ 90 チェック
│
└── [コードレビュー] ARIAパターン・セマンティックHTMLのレビュー
↓ 全PASS
マージ可能
まとめ
アクセシビリティをCIに組み込むことで、発見→修正のサイクルを「リリース前」から「PR段階」に前倒しできる。
実装ステップを整理する:
@axe-core/playwrightをインストールし、E2Eテストを書く- GitHub Actions YAMLをリポジトリに追加し、required status checkに設定する
lighthouse-budget.jsonでアクセシビリティスコアゲートを設定する- AI修正提案スクリプトで違反の修正コストを下げる
- Figmaのデザイン段階からコントラスト・フォーカス設計を標準化する
axe-coreで自動検出できるのはWCAG違反全体の約57%(公式値)だが、「後で発見する」よりCIで早期に検出する方が圧倒的にコストが低い。残りの43%は定期的な手動レビューと実ユーザーテストで補完する体制を整えよう。
FAQ
Q1. axe-core と Lighthouse のどちらを使えばいいですか?
目的が異なるため、両方を使うことを推奨します。axe-coreはコード単位のWCAG準拠チェックに特化し、特定の違反箇所を特定します。Lighthouseはページ全体の総合スコア(パフォーマンス含む)を測定するため、スコアゲートの設定に向いています。
Q2. WCAG 2.1と2.2の違いは何ですか?
WCAG 2.2(2023年10月勧告)はWCAG 2.1の上位互換で、モバイル操作性・認知負荷・認証に関する9つの新基準を追加しています。既存サイトがWCAG 2.1 AAに準拠していれば、追加対応が必要なのは主に2.5.7(ドラッグ代替)、2.5.8(ターゲットサイズ)、3.3.7(冗長な認証)などです。
Q3. CIでアクセシビリティテストが失敗した場合、どう対応しますか?
本記事のAI修正提案スクリプトを使うと、GitHub ActionsのジョブサマリーにBefore/Afterのコードスニペットが自動生成されます。それを参考に修正し、再度PRをプッシュしてCIを通過させます。Critical(重大)な違反から優先して修正してください。
Q4. 自動テストでは検出できない項目はありますか?
はい。axe-coreで自動検出できるのはWCAG違反全体の約57%(Deque公式値)です。「画像の代替テキストが内容を適切に説明しているか」「動画に適切な字幕があるか」「フォームのラベルが意味的に正確か」などは、文脈を理解する必要があるため自動検出が困難です。これらは手動レビューまたはユーザーテストで確認します。
Q5. アクセシビリティ対応にかかるコストを抑える方法はありますか?
最も効果的なのは「設計段階で組み込む」ことです。Figmaでコントラスト比とフォーカス状態を設計段階で確認し、セマンティックHTMLをコンポーネントの標準にすることで、後工程での修正コストを大幅に削減できます。本記事のCIゲートを導入すれば、違反の蓄積を防いで技術的負債を抑制できます。
References
- WCAG 2.2 W3C公式勧告 — W3C, 2023-10-05
- axe-core GitHub リポジトリ — Deque Systems
- Deque axe 公式ドキュメント(検出率57%の出典)
- Playwright アクセシビリティテスト公式ドキュメント
- Lighthouse CI GitHub Actions — treosh
- GitHub Actions ジョブサマリー公式ドキュメント
- JIS X 8341-3:2016(経済産業省)
