boy in green shirt
Photo by CDC on Pexels.com
目次

失敗しないアクセシビリティテスト完全戦略:自動・手動・ユーザー・CI/CDで“壊れにくい品質”を育てる

概要サマリー(先に要点)

  • 4層テスト戦略(自動テスト → 手動検証 → 支援技術検証 → ユーザーテスト)で抜け漏れを最小化。
  • CI/CDに常設してリグレッション(回帰)を防止、デプロイ前に“赤”を止める。
  • 仕様化の鍵は「達成基準→観察可能な振る舞い」への言い換え。誰が見ても同じ判定に。
  • サンプルコード(Jest + jest-axe/Playwright + axe/Cypress + cypress-axe/pa11y-ci/Lighthouse CI)をそのまま導入。
  • 手動チェック手順(キーボード、コントラスト、拡大・リフロー、動作削減、モバイル)と支援技術テスト(NVDA/VoiceOver/TalkBack)を標準化。
  • レポートの書き方(What/Where/Why/How)と優先度付け基準(影響×頻度×回避不能性)。
  • 誰が恩恵を受けるかを明確化し、組織に運用を根づかせるための方針・DoD(Doneの定義)テンプレート付き。

対象読者(具体):フロントエンドエンジニア、UI/UXデザイナー、QA/テストエンジニア、PM/Webディレクター、CS/サポート担当
アクセシビリティレベル:本記事は WCAG 2.1 AA 準拠を基準に、可能であれば WCAG 2.2 の追加項目(ターゲットサイズ・ドラッグ代替・フォーカス外観など)を段階導入する前提で構成しています。


1. はじめに:テストは“最終工程”ではなく“設計そのもの”です

アクセシビリティは、HTMLの意味(セマンティクス)・操作可能性・理解可能性が土台。ゆえにテストはバグ取りの最終工程ではなく、要件定義と設計の延長にあります。

  • 早期発見のコスパ:デザイン・実装の初期にわかる問題(見出し階層、色依存、フォーカス欠落)は修正コストが小さい。
  • 回帰の顕在化:新UIの追加や文言更新で、フォーカス順やコントラストが崩れることは日常茶飯事。CIで常時監視が必須ですわ。
  • “誰が見ても同じ判定”:達成基準を「観察可能な振る舞い」に落とし込むと、チーム全員が同じ基準で品質を語れます。

2. 4層テスト戦略の全体像(重ねて守る)

  1. レイヤー1:自動テスト(静的・実行時)
    • ルールに落とし込める問題(ラベルの欠落、roleの不整合、ARIA誤用、コントラストの多く など)を機械で一掃
  2. レイヤー2:手動検証(動作・視覚)
    • キーボード操作・フォーカス順・視認性・拡大など、人の感覚でしか判断できない部分を確認。
  3. レイヤー3:支援技術検証(SR・拡大鏡 等)
    • NVDA/VoiceOver/TalkBackなど、実際の読み上げ・移動をざっくりでも毎リリースで触る。
  4. レイヤー4:ユーザーテスト(当事者/多様な利用者)
    • 実利用の課題(専門用語、認知負荷、予測可能性の欠如)を観察で拾う。

すべて毎回は大変なので、デイリーは1・2、スプリント末に3、四半期に4のように頻度を決めると続けやすいです。


3. スコーピング:どこから始め、何を終わりとするか

  • 優先ページ:トップ、検索・一覧、詳細、フォーム送信、アカウント、購入完了など主要ファネル
  • 代表端末:PC(Chrome/Edge + NVDA)、Mac(Safari + VoiceOver)、iOS(Safari + VoiceOver)、Android(Chrome + TalkBack)。
  • 代表環境:縮小画面幅(320px想定)、フォント拡大(OS150%)、ダーク/ライト、動作削減オン。
  • 終了条件(DoD)
    • 自動検査は 重大(critical)違反0
    • 手動8項目(後述)を満たす。
    • 主要シナリオ(検索→詳細→購入)がキーボードのみで完了。
    • SRで見出し・ランドマーク・フォームが論理的に読み上げられる。

4. レイヤー1:自動テストを配管する(サンプルコード多数)

4.1 単体・コンポーネント:Jest + jest-axe

// __tests__/button.a11y.test.js
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
import Button from '../Button';

expect.extend(toHaveNoViolations);

test('Button はa11y違反がない', async () => {
  const { container } = render(<Button>送信</Button>);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

4.2 E2E:Playwright + @axe-core/playwright

// tests/a11y.spec.ts
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('トップページのa11y検査(wcag2a/aa)', async ({ page }) => {
  await page.goto('http://localhost:3000/');
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a','wcag2aa'])
    .analyze();
  expect(results.violations).toEqual([]);
});

4.3 ブラウザ操作テスト:Cypress + cypress-axe

// cypress/e2e/a11y.cy.js
import 'cypress-axe';

it('フォームがa11y違反なし', () => {
  cy.visit('/contact');
  cy.injectAxe();
  cy.checkA11y(null, {
    runOnly: { type: 'tag', values: ['wcag2a','wcag2aa'] }
  });
});

4.4 サイト全体のクロール:pa11y-ci

// .pa11yci
{
  "defaults": { "standard": "WCAG2AA", "timeout": 30000, "concurrency": 4 },
  "urls": [
    "http://localhost:3000/",
    "http://localhost:3000/search",
    "http://localhost:3000/contact"
  ]
}

4.5 品質ゲート:Lighthouse CI(アクセシビリティカテゴリ)

// .lighthouserc.js
module.exports = {
  ci: {
    collect: { staticDistDir: "./dist" },
    assert: { assertions: { "categories:accessibility": ["error", { minScore: 0.9 }] } },
    upload: { target: "temporary-public-storage" }
  }
};

4.6 CI/CD(例:GitHub Actions)

# .github/workflows/a11y.yml
name: a11y-ci
on: [push, pull_request]
jobs:
  axe-playwright:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: npm run build && npm run start & npx wait-on http://localhost:3000
      - run: npx playwright install --with-deps
      - run: npx playwright test -g "a11y検査"
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci && npm run build
      - run: npx lhci autorun

注意:自動検査は万能ではありません。コントラスト・ラベル・構造の多くは拾えますが、意味・理解・予測可能性は人が確かめる必要があります。


5. レイヤー2:手動検証の標準手順(“8つの柱”)

  1. キーボード操作(2.1.1/2.4.3/2.4.7)

    • Tabで論理順に巡回できる/Shift+Tab で逆順でも破綻がない。
    • スキップリンクが最初のTabで現れ、mainへ移動。
    • ドロップダウン・モーダル:Enter/Space開閉、Escで閉じ、トリガに復帰
  2. フォーカスの可視化(2.4.7)

    • すべてのフォーカス可能要素に明確なインジケーター(太さ・色コントラスト)。
    • :focus-visible でマウスと区別し、見失わない表示に。
  3. コントラスト(1.4.3/1.4.11)

    • 本文4.5:1・大きな文字3:1、アイコン/境界線など非テキスト3:1
    • エラーメッセージやリンクも基準を満たす。
  4. 画像・代替テキスト(1.1.1)

    • 情報画像に要約alt、装飾はalt=""(またはCSS背景)。
    • アイコンボタンは可視ラベルまたはaria-labelで意味が伝わる。
  5. 見出し・ランドマーク(1.3.1/2.4.1)

    • h1は1つ。階層はh2→h3…で論理的。
    • header/nav/main/aside/footer を適切配置。複数あるnavラベル付き
  6. 拡大・リフロー(1.4.10/1.4.12)

    • 幅320px相当で横スクロール強要なし(地図等の例外除く)。
    • 行間1.5、段落2em、文字間0.12em、単語間0.16emでも欠落・重なりなし
  7. 動き・点滅(2.3/動作削減)

    • prefers-reduced-motion: reduce大きな動きが弱まる/停止
    • 点滅(3回/秒以上)は避ける。
  8. モバイル(ターゲットサイズ・向き・代替操作)

    • タッチターゲットは44〜48px目安、近接しすぎない。
    • 縦横の強制なし、複雑ジェスチャは一本指代替を用意。

6. レイヤー3:支援技術(スクリーンリーダー等)検証のコア観点

  • 読み上げの“地図”
    • 見出しの一覧でページ構造が追えるか。
    • ランドマークで主要領域を移動できるか。
  • フォーム
    • ラベル・ヒント・エラーが**aria-describedby**で結びつく。
    • エラー発生時は**role="alert"** で即時通知。
  • ダイアログ
    • 開いたらタイトルにフォーカスaria-modal="true"、フォーカストラップ、Esc閉じ、トリガ復帰。
  • ライブ更新
    • role="status"(控えめ)/role="alert"(緊急)で過不足ない通知。

最小セット:NVDA(Windows)・VoiceOver(macOS/iOS)・TalkBack(Android)で各5分だけでも毎リリース触ると、問題の早期検知力が段違いですわ。


7. レイヤー4:ユーザーテスト(当事者・多様な利用者)の設計

7.1 テスト設計のステップ

  1. 目的と仮説:例)「キーボードのみでチェックアウト完了まで迷わない」
  2. タスク設計:例)商品検索 → カート → 住所入力 → 注文確定
  3. 参加条件:支援技術利用の有無、モバイル/PC、色覚特性などを幅広く
  4. 環境準備:SRのログ取り、画面・音声の録画、同意の取得。
  5. 観察指標:完了率、所要時間、エラー数、発話プロトコルの気づき。

7.2 セッション進行の雛形

  • 導入:「普段どおりにご利用ください。サイトの評価であって、あなたの評価ではありません」
  • 実施:観察者は口を挟まない。詰まったら「いま何が起きていますか?」程度の促し。
  • 振り返り:困りごとを具体例で聞く(例:「〇〇のボタンは見つけやすかったですか?」)。

ユーザーテストはWCAGの適否が目的ではなく、理解可能性・予測可能性・学習コストを見ます。自動や手動では出ない“実用の壁”を発見できます。


8. レポートの書き方:誰が読んでも同じ行動に至る

ひとつの問題 = ひとつのチケットで、以下を必須にします。

  • What(何が問題?):観察できる事実(例:「Tabでヘッダー内を循環して本文へ到達できない」)。
  • Where(どこで?):URL/画面名/コンポーネント名(再現パス)。
  • Why(なぜ問題?):ユーザー影響(例:「キーボード利用者が主要内容へ到達できず離脱」)。
  • Which(基準):該当するWCAG(例:2.4.1/2.1.1)。
  • How(どう直す?):修正案(例:スキップリンク追加、main#content へフォーカス移動)。
  • 証跡:スクリーンショット/動画/SRログ/HTML断片。

優先度付け(P1〜P4)の目安

  • P1(今すぐ):致命(完了不能・情報欠落・発作リスク)
  • P2(次リリース):主要フロー阻害・大多数に影響
  • P3(計画的に):回避策あり・軽微だが累積でUX低下
  • P4(観察):改善提案・将来の負債候補

9. 受け入れ基準(DoD)テンプレート

  • [ ] 自動検査(axe/pa11y/LHCI)で重大違反0
  • [ ] 手動8項目を満たす(§5)。
  • [ ] 主要シナリオキーボードのみで完遂。
  • [ ] SRで見出し・ランドマーク・フォームが論理的に読まれる。
  • [ ] モバイルでターゲットサイズ・拡大・向き・代替操作が機能。
  • [ ] 変更箇所の回帰テスト(スナップショット/E2E)に合格。
  • [ ] リリースノートにアクセシビリティ影響(改善・既知の制約)を記載。

10. サンプル:デザインレビュー用 “即席チェックカード”

10.1 レイアウト・情報構造

  • h1は1つ。h2→h3の階層が論理的。
  • nav/main/footer があり、複数のnavはラベル付き。
  • ☐ DOM順=視覚順。CSSで順序を入れ替えていない。

10.2 コントラスト・色

  • ☐ テキスト4.5:1/大きい文字3:1。
  • ☐ リンク・アイコン・フォーカスリングなど非テキスト3:1。
  • 色のみで伝えない(アイコン・ラベル・形状の併用)。

10.3 フォーム

  • ☐ すべての入力に可視ラベル
  • ☐ ヒント・エラーはaria-describedbyで関連付け、role="alert"で通知。
  • ☐ 必須表示は色+テキスト

10.4 インタラクション

  • ☐ ドロップダウン・タブ・モーダルは期待されるキー操作が機能。
  • ☐ 開閉状態はaria-expanded等で状態が伝わる
  • ☐ 初期フォーカス・復帰先が自然

10.5 モバイル

  • ☐ タッチターゲット44〜48px、近接しすぎない。
  • ☐ 拡大許可・320px幅でも崩れない。
  • ☐ 縦横どちらも操作可能。

11. 組織運用:デザインシステムとCIに“恒常化”させる

  • デザインシステムに、コンポーネントごとの**a11y仕様(名前・役割・値・キー操作・コントラスト)**を明文化。
  • Storybookでアクセシビリティアドオンを有効化し、単位テスト+目視の“二刀流”。
  • CIの失敗理由Slack通知担当自動アサイン
  • アクセシビリティ負債台帳を作り、P1/P2はスプリント中、P3/P4は四半期で計画返済
  • 教育:新メンバー向けに「15分の手動ルーティン」を動画化。

12. 誰に、どう効くの?(具体的インパクト)

  • フロントエンドエンジニア

    • 事前に“落とし穴”を自動で塞げるのでレビューが本質的に。
    • 回帰に強くなり、土壇場の火消しが減る。
  • UI/UXデザイナー

    • コントラスト・ラベル・状態設計がデザインシステムに固定化
    • 検証観点が共有化され、議論が事実ベースに。
  • QA/テストエンジニア

    • 手動8項目とSR観点でテスト設計がテンプレ化
    • 重大度の判定が揺れない
  • PM/Webディレクター

    • DoDがクリアでリリース判定が明快
    • 訴訟・炎上・解約などリスクの早期遮断
  • CS/サポート

    • 問い合わせの再現性と回答の品質が上がる。
    • 既知課題をナレッジ化しやすい。
  • 利用者(支援技術ユーザー・高齢者・一時的制約のある人)

    • 迷い・疲労・誤操作が減り、平等で安定した体験を得られる。

13. 参考テンプレート:アクセシビリティ方針(抜粋・社内外公開用)

当社アクセシビリティ方針(抜粋)

  • 目標:すべての新規ページと主要機能で WCAG 2.1 AA 準拠
  • 手段:デザインレビューにa11yチェックカード、CIにaxe/LHCIを導入。
  • テスト:各リリースで手動8項目NVDA/VoiceOverの簡易検証を実施。
  • 改善:問い合わせ・ユーザーテスト結果を四半期ごとに負債台帳へ登録し、優先度に基づき返済。
  • 連絡先:アクセシビリティ窓口(メール/フォーム/電話・テキスト中継対応)。
  • 免責・既知の制約:一部の過去PDF/外部ウィジェットは段階的に改善予定。

14. サンプル:Playwright E2Eに“キーボード流儀”を組み込む

import { test, expect } from '@playwright/test';

test('キーボードだけで検索→詳細→購入確認に到達できる', async ({ page }) => {
  await page.goto('http://localhost:3000/');
  // スキップリンク
  await page.keyboard.press('Tab');
  await expect(page.locator('a.skip')).toBeVisible();
  await page.keyboard.press('Enter');
  // 検索フォーム入力
  await page.keyboard.type('キーボード対応');
  await page.keyboard.press('Tab');
  await page.keyboard.press('Enter');
  // 結果一覧 → 最初の詳細へ
  await page.keyboard.press('Tab');
  await page.keyboard.press('Enter');
  // モーダルが開く → タイトルにフォーカス
  await expect(page.locator('[role="dialog"] h2')).toBeFocused();
  // Escで閉じ→トリガに復帰
  await page.keyboard.press('Escape');
  await expect(page.locator('a[aria-controls="modal"]')).toBeFocused();
});

自動テストに**“キーボードの旅路”を入れると、UI変更での破綻が即座に検知**できます。


15. よくある質問(FAQ)

Q1:自動検査を0違反にしても、手動で指摘が出るのはなぜ?
A:自動は「機械が判定できる事実」しか見ません。意味・予測可能性・理解容易性は人の判断が必要です。

Q2:SR検証は毎回どこまで?
A:毎リリースは見出し・ランドマーク・主要フォーム5分スモークを。大規模改修時に詳細検証を。

Q3:どのブラウザ/OSを優先?
A:トラフィックと支援技術の利用状況で決めます。最低限、Windows+NVDA、iOS+VoiceOver、Android+TalkBackをカバー。


16. アクセシビリティレベルの評価(本記事の到達点)

  • 準拠目標WCAG 2.1 AA
    • 自動検査と手動8項目で 1.1.1 / 1.3.1 / 1.4.3 / 1.4.11 / 2.1.1 / 2.4.1 / 2.4.3 / 2.4.7 / 3.3.x / 4.1.2 に対応。
  • 発展的対応(推奨)WCAG 2.2 AA の一部(2.5.7 Dragging Movements2.5.8 Target Size (Minimum)2.4.11 Focus Appearance)を運用へ段階導入。
  • 読みやすさ配慮:本文は短文中心・見出しで階層化・専門用語に補足を添え、学習コストの低い表現を意識しています。

17. まとめ:品質は“仕組み”で守る。情熱は“観察”で磨く。

  1. 4層戦略(自動→手動→支援技術→ユーザー)で、抜け漏れを塞ぐ。
  2. CI/CD常設で回帰を防ぎ、赤はデプロイ前に止める
  3. 観察可能な振る舞いに落とし込んだチェックリストで、判定の一貫性を確保。
  4. レポートの型(What/Where/Why/How)で、直す人に迷いを残さない
  5. 負債台帳とDoDで、継続改善をプロセスに固定。

小さな“確認の習慣”が、大きな“安心の体験”を生みます。あなたのプロダクトが、誰にとっても迷いなく使える存在になるよう、わたしも心を込めて応援していますね。


投稿者 greeden

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)