T-CREATOR

Playwright でアクセシビリティ自動検証:axe 連携と ARIA 検証の実例

Playwright でアクセシビリティ自動検証:axe 連携と ARIA 検証の実例

Web サイトのアクセシビリティは、すべてのユーザーが快適にコンテンツを利用できるようにするための重要な要素です。しかし、アクセシビリティの検証は手動で行うと時間がかかり、見落としも発生しやすいという課題があります。

そこで注目されているのが、Playwright と axe-core を組み合わせた自動検証の仕組みです。この記事では、Playwright を使って axe-core を連携させ、ARIA 属性の検証を含めた実践的なアクセシビリティテストの実装方法をご紹介します。実際のコード例を交えながら、すぐに現場で活用できる具体的な手法を解説していきますので、ぜひ最後までお読みください。

背景

アクセシビリティ検証の重要性

現代の Web 開発において、アクセシビリティは単なるオプションではなく、法的要件や企業の社会的責任として求められるようになってきました。特に、視覚障害者や運動機能に制約のあるユーザーが、スクリーンリーダーやキーボード操作だけで Web サイトを利用できるようにすることは、インクルーシブな社会の実現に欠かせません。

WCAG(Web Content Accessibility Guidelines)は、Web アクセシビリティの国際的な標準規格として広く採用されています。この規格に準拠することで、より多くのユーザーにとって使いやすい Web サイトを構築できるのです。

Playwright と axe-core の役割

以下の図は、Playwright と axe-core がどのように連携してアクセシビリティ検証を実現するかを示しています。

mermaidflowchart TB
  dev["開発者"] -->|テストコード記述| pw["Playwright"]
  pw -->|ページ操作| browser["ブラウザ"]
  browser -->|DOM 取得| pw
  pw -->|axe inject| axe["axe-core"]
  axe -->|検証実行| dom["DOM ツリー"]
  dom -->|違反検出| axe
  axe -->|レポート| pw
  pw -->|結果出力| report["検証レポート"]

Playwright は、ブラウザ自動化ツールとして、実際のブラウザを操作しながらテストを実行できます。一方、axe-core は Deque Systems が提供するアクセシビリティ検証エンジンで、WCAG に基づいた数多くのルールを自動的にチェックしてくれるのです。

この 2 つを組み合わせることで、ページのレンダリング後に自動的にアクセシビリティの問題を検出し、継続的にチェックできる環境が整います。

ARIA(Accessible Rich Internet Applications)

ARIA は、動的な Web アプリケーションをスクリーンリーダーなどの支援技術に対応させるための仕様です。role 属性や aria-labelaria-describedby などの属性を使うことで、視覚的には分かりにくい情報を支援技術に伝えることができます。

しかし、ARIA 属性の誤った使用は、かえってアクセシビリティを損なう原因となるため、正しく実装されているかを検証することが重要なのです。

課題

手動検証の限界

アクセシビリティの検証を手動で行う場合、以下のような課題があります。

#課題詳細
1時間コストページ数が増えるほど検証に膨大な時間がかかる
2検証漏れ人的ミスにより見落としが発生しやすい
3属人化検証者のスキルに結果が左右される
4継続性毎回のリリース時に同じ検証を繰り返す必要がある
5再現性問題の再現や共有が困難

特に、動的に生成されるコンテンツや SPA(Single Page Application)では、状態によって表示内容が変わるため、手動検証だけではすべてのパターンをカバーすることが難しくなります。

ARIA 属性の複雑さ

ARIA 属性は非常に強力ですが、その分複雑でもあります。以下のような問題が発生しがちです。

  • 必須属性の不足:特定の role には必須となる ARIA 属性がある
  • 属性値の誤り:true/false を設定すべき箇所に不正な値が入る
  • 不適切な role の使用:セマンティック HTML で十分な箇所に ARIA を使用
  • aria-label の重複:同じページ内に同じラベルが複数存在し、識別できない

これらの問題を人手で発見するのは非常に困難です。

CI/CD パイプラインへの組み込み

開発フローの中で、アクセシビリティチェックを自動化し、CI/CD パイプラインに組み込むには、以下のような要件が必要となります。

  • プログラマブル:コードで制御できること
  • 軽量で高速:ビルドプロセスを遅延させないこと
  • レポート出力:問題箇所を明確に特定できること
  • カスタマイズ可能:プロジェクト固有のルールを追加できること

これらを実現するために、Playwright と axe-core の組み合わせが最適な解決策となるのです。

解決策

Playwright と @axe-core/playwright の導入

Playwright に axe-core を統合するには、@axe-core​/​playwright パッケージを使用します。このパッケージは、Playwright のテストコード内で axe-core を簡単に実行できるラッパーを提供してくれます。

以下の図は、テスト実行の基本的なフローを示しています。

mermaidflowchart LR
  start["テスト開始"] --> navigate["ページ遷移"]
  navigate --> wait["要素の読み込み待機"]
  wait --> inject["axe-core 注入"]
  inject --> analyze["アクセシビリティ分析"]
  analyze --> check{違反あり?}
  check -->|Yes| fail["テスト失敗"]
  check -->|No| pass["テスト成功"]
  fail --> report_fail["レポート出力"]
  pass --> report_pass["レポート出力"]

この流れに沿って、実際のコード実装を段階的に見ていきましょう。

環境構築とパッケージインストール

まず、必要なパッケージをインストールします。Yarn を使用してプロジェクトに追加していきます。

bashyarn add -D @playwright/test playwright @axe-core/playwright axe-core

この コマンドで、Playwright 本体とテストランナー、そして axe-core の統合パッケージがインストールされます。これだけで、アクセシビリティ検証の準備が整うのです。

基本的なテストファイルの作成

次に、基本的なアクセシビリティテストのファイルを作成します。ここでは、ページ全体に対して axe を実行する最もシンプルな例を示します。

テストファイルのインポート

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

まず、Playwright のテストランナーと AxeBuilder をインポートします。AxeBuilder が axe-core を Playwright で使うための中心的なクラスとなります。

基本的なアクセシビリティテスト

typescripttest.describe('アクセシビリティ検証', () => {
  test('トップページの基本的な検証', async ({ page }) => {
    // ページに移動
    await page.goto('https://example.com');

    // axe を実行
    const accessibilityScanResults = await new AxeBuilder({
      page,
    }).analyze();

    // 違反がないことを確認
    expect(accessibilityScanResults.violations).toEqual([]);
  });
});

このコードでは、ページに移動した後、AxeBuilder を使って axe-core による分析を実行しています。analyze() メソッドが実際の検証を行い、結果を返してくれます。

違反がある場合、violations 配列に詳細情報が格納されるため、空配列であることを確認することで、アクセシビリティの問題がないことを検証できるのです。

特定の WCAG レベルに絞った検証

WCAG には、レベル A、AA、AAA という 3 つの適合レベルがあります。多くのプロジェクトでは、レベル AA への準拠を目指すことが一般的です。

WCAG レベル AA のみをチェック

typescripttest('WCAG 2.1 レベル AA の検証', async ({ page }) => {
  await page.goto('https://example.com');

  // レベル AA のルールのみを適用
  const accessibilityScanResults = await new AxeBuilder({
    page,
  })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .analyze();

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

withTags() メソッドを使うことで、特定のタグを持つルールのみを実行できます。これにより、プロジェクトの要件に合わせた検証が可能になるのです。

特定の要素に対する検証

ページ全体ではなく、特定のコンポーネントやセクションだけを検証したい場合もあります。そのような場合は、CSS セレクタを使って対象を絞り込めます。

ナビゲーションバーのみを検証

typescripttest('ナビゲーションバーのアクセシビリティ検証', async ({
  page,
}) => {
  await page.goto('https://example.com');

  // ナビゲーションバーのみを検証対象とする
  const accessibilityScanResults = await new AxeBuilder({
    page,
  })
    .include('nav')
    .analyze();

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

include() メソッドで、検証対象となる要素を CSS セレクタで指定しています。これにより、特定のコンポーネント単位でテストを作成できます。

広告エリアを検証から除外

typescripttest('広告エリアを除外した検証', async ({ page }) => {
  await page.goto('https://example.com');

  // 外部の広告エリアを検証から除外
  const accessibilityScanResults = await new AxeBuilder({
    page,
  })
    .exclude('.advertisement')
    .exclude('#third-party-widget')
    .analyze();

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

exclude() メソッドを使うことで、サードパーティのコンテンツなど、制御できない要素を検証対象から外すことができます。これは実用的なテスト設計において非常に重要です。

詳細なレポート出力

違反が検出された場合、詳細な情報をログに出力することで、問題の特定と修正が容易になります。

カスタムレポート関数の実装

typescript/**
 * アクセシビリティ違反の詳細をログ出力する関数
 */
function logViolations(violations: any[]) {
  violations.forEach((violation, index) => {
    console.log(`\n違反 ${index + 1}: ${violation.id}`);
    console.log(`説明: ${violation.description}`);
    console.log(`影響度: ${violation.impact}`);
    console.log(`WCAG: ${violation.tags.join(', ')}`);
    console.log(`ヘルプ: ${violation.helpUrl}`);

    violation.nodes.forEach(
      (node: any, nodeIndex: number) => {
        console.log(`  要素 ${nodeIndex + 1}:`);
        console.log(`    HTML: ${node.html}`);
        console.log(
          `    セレクタ: ${node.target.join(' ')}`
        );
      }
    );
  });
}

この関数は、違反情報を読みやすい形式で出力します。違反の ID、説明、影響度、関連する WCAG タグ、そして具体的な HTML 要素を表示してくれます。

レポート関数を使用したテスト

typescripttest('詳細レポート付きの検証', async ({ page }) => {
  await page.goto('https://example.com');

  const accessibilityScanResults = await new AxeBuilder({
    page,
  }).analyze();

  // 違反があればログ出力
  if (accessibilityScanResults.violations.length > 0) {
    logViolations(accessibilityScanResults.violations);
  }

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

このテストでは、違反が検出された場合に詳細情報を出力してから、テストを失敗させています。これにより、CI 環境でも問題箇所を素早く特定できるのです。

具体例

実践例 1:フォームの ARIA 検証

フォームは、アクセシビリティにおいて特に重要な要素です。ここでは、フォームコンポーネントに対する包括的な検証を実装します。

検証対象のフォーム HTML

html<form id="contact-form">
  <div class="form-group">
    <label for="name">お名前</label>
    <input
      type="text"
      id="name"
      name="name"
      aria-required="true"
      aria-invalid="false"
      aria-describedby="name-help"
    />
    <span id="name-help" class="help-text">
      姓と名を入力してください
    </span>
  </div>

  <div class="form-group">
    <label for="email">メールアドレス</label>
    <input
      type="email"
      id="email"
      name="email"
      aria-required="true"
      aria-invalid="false"
    />
  </div>

  <button type="submit" aria-label="お問い合わせを送信">
    送信
  </button>
</form>

このフォームには、適切な label 要素と ARIA 属性が設定されています。これらが正しく機能しているかを検証していきます。

フォーム全体のアクセシビリティ検証

typescripttest('お問い合わせフォームのアクセシビリティ検証', async ({
  page,
}) => {
  await page.goto('https://example.com/contact');

  // フォーム領域のみを検証
  const accessibilityScanResults = await new AxeBuilder({
    page,
  })
    .include('#contact-form')
    .withTags(['wcag2a', 'wcag2aa'])
    .analyze();

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

このテストでは、お問い合わせフォーム全体に対して、WCAG レベル A と AA の基準で検証を行っています。

個別の ARIA 属性チェック

axe-core の自動検証に加えて、個別の ARIA 属性が正しく設定されているかをプログラムで確認することもできます。

typescripttest('フォーム入力欄の ARIA 属性検証', async ({ page }) => {
  await page.goto('https://example.com/contact');

  // 必須項目に aria-required が設定されているか
  const nameInput = page.locator('#name');
  const ariaRequired = await nameInput.getAttribute(
    'aria-required'
  );
  expect(ariaRequired).toBe('true');

  // aria-describedby が正しく参照されているか
  const ariaDescribedby = await nameInput.getAttribute(
    'aria-describedby'
  );
  expect(ariaDescribedby).toBe('name-help');

  // 参照先の要素が実際に存在するか
  const helpText = page.locator(`#${ariaDescribedby}`);
  await expect(helpText).toBeVisible();
});

このコードでは、aria-requiredaria-describedby などの属性が正しく設定されているか、そして参照先の要素が実際に存在するかを確認しています。

エラー状態の ARIA 検証

typescripttest('フォームエラー状態の ARIA 検証', async ({ page }) => {
  await page.goto('https://example.com/contact');

  // 送信ボタンをクリック(バリデーションエラーを発生させる)
  await page.click('button[type="submit"]');

  // エラー状態になった入力欄の検証
  const nameInput = page.locator('#name');
  const ariaInvalid = await nameInput.getAttribute(
    'aria-invalid'
  );
  expect(ariaInvalid).toBe('true');

  // エラーメッセージが aria-describedby で参照されているか
  const ariaDescribedby = await nameInput.getAttribute(
    'aria-describedby'
  );
  expect(ariaDescribedby).toContain('name-error');

  // エラーメッセージが表示されているか
  const errorMessage = page.locator('#name-error');
  await expect(errorMessage).toBeVisible();

  // axe による再検証
  const accessibilityScanResults = await new AxeBuilder({
    page,
  })
    .include('#contact-form')
    .analyze();

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

このテストでは、バリデーションエラーが発生した際に、aria-invalid 属性が適切に更新され、エラーメッセージが正しく関連付けられているかを検証しています。動的な状態変化も含めた包括的な検証が可能です。

実践例 2:モーダルダイアログの検証

モーダルダイアログは、アクセシビリティにおいて特別な配慮が必要なコンポーネントです。キーボード操作やフォーカス管理、ARIA 属性の適切な使用が求められます。

モーダルの実装と検証のフロー

以下の図は、モーダルダイアログのアクセシビリティ検証で確認すべきポイントを示しています。

mermaidflowchart TB
  open["モーダルを開く"] --> focus_trap["フォーカストラップ確認"]
  focus_trap --> role_check["role='dialog' 確認"]
  role_check --> aria_modal["aria-modal='true' 確認"]
  aria_modal --> aria_label["aria-labelledby 確認"]
  aria_label --> esc_key["ESC キー動作確認"]
  esc_key --> close["モーダルを閉じる"]
  close --> focus_return["元の要素へフォーカス復帰"]
  focus_return --> axe_scan["axe-core 検証"]

これらのステップを順番に検証していくことで、アクセシブルなモーダル実装を保証できます。

モーダル HTML の例

html<button id="open-modal" aria-haspopup="dialog">
  詳細を表示
</button>

<div
  id="my-modal"
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
  hidden
>
  <div class="modal-content">
    <h2 id="modal-title">詳細情報</h2>
    <p id="modal-description">こちらが詳細な説明文です。</p>
    <button id="close-modal" aria-label="モーダルを閉じる">
      閉じる
    </button>
  </div>
</div>

このモーダルには、role="dialog"aria-modal="true"、そして aria-labelledbyaria-describedby による適切なラベリングが施されています。

モーダルの基本的な ARIA 検証

typescripttest('モーダルダイアログの ARIA 属性検証', async ({
  page,
}) => {
  await page.goto('https://example.com');

  // モーダルを開く
  await page.click('#open-modal');

  // モーダルが表示されるまで待機
  await page.waitForSelector('#my-modal:not([hidden])');

  const modal = page.locator('#my-modal');

  // role="dialog" が設定されているか
  const role = await modal.getAttribute('role');
  expect(role).toBe('dialog');

  // aria-modal="true" が設定されているか
  const ariaModal = await modal.getAttribute('aria-modal');
  expect(ariaModal).toBe('true');

  // aria-labelledby が設定されているか
  const ariaLabelledby = await modal.getAttribute(
    'aria-labelledby'
  );
  expect(ariaLabelledby).toBeTruthy();

  // ラベル要素が存在するか
  const labelElement = page.locator(`#${ariaLabelledby}`);
  await expect(labelElement).toBeVisible();
});

このテストでは、モーダルの基本的な ARIA 属性が適切に設定されているかを確認しています。

フォーカス管理の検証

typescripttest('モーダルのフォーカス管理検証', async ({ page }) => {
  await page.goto('https://example.com');

  // 開くボタンにフォーカスを当てる
  await page.focus('#open-modal');

  // モーダルを開く
  await page.keyboard.press('Enter');
  await page.waitForSelector('#my-modal:not([hidden])');

  // モーダル内の最初のフォーカス可能な要素にフォーカスが移動したか
  const focusedElement = await page.evaluate(() => {
    return document.activeElement?.id;
  });

  // モーダル内の要素にフォーカスがあることを確認
  expect(focusedElement).toMatch(/modal-/);

  // ESC キーでモーダルを閉じる
  await page.keyboard.press('Escape');
  await page.waitForSelector('#my-modal[hidden]');

  // 元の開くボタンにフォーカスが戻ったか
  const returnedFocusElement = await page.evaluate(() => {
    return document.activeElement?.id;
  });

  expect(returnedFocusElement).toBe('open-modal');
});

このテストでは、モーダルを開いた際にフォーカスが適切に移動し、閉じた際に元の位置に戻るかを検証しています。アクセシビリティにおいて、フォーカス管理は極めて重要です。

axe-core によるモーダル全体の検証

typescripttest('モーダル表示時の axe 検証', async ({ page }) => {
  await page.goto('https://example.com');

  // モーダルを開く
  await page.click('#open-modal');
  await page.waitForSelector('#my-modal:not([hidden])');

  // モーダルが開いた状態で axe を実行
  const accessibilityScanResults = await new AxeBuilder({
    page,
  })
    .include('#my-modal')
    .withTags(['wcag2a', 'wcag2aa', 'best-practice'])
    .analyze();

  // 違反がある場合は詳細を出力
  if (accessibilityScanResults.violations.length > 0) {
    console.log('モーダルのアクセシビリティ違反:');
    logViolations(accessibilityScanResults.violations);
  }

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

このテストでは、モーダルが開いた状態で axe-core による包括的な検証を実行しています。ベストプラクティスのタグも含めることで、より厳格なチェックが可能になります。

実践例 3:カスタムルールの追加

プロジェクト固有の要件に対応するため、axe-core にカスタムルールを追加することもできます。

カスタムルールの設定

typescript/**
 * カスタム axe ルール: すべてのボタンに明示的なラベルを要求
 */
const customRules = {
  rules: [
    {
      id: 'button-explicit-label',
      enabled: true,
      impact: 'serious',
      selector: 'button',
      evaluate: (node: any) => {
        // aria-label または aria-labelledby があるか
        const hasAriaLabel =
          node.hasAttribute('aria-label');
        const hasAriaLabelledby = node.hasAttribute(
          'aria-labelledby'
        );
        // テキストコンテンツがあるか
        const hasTextContent =
          node.textContent.trim().length > 0;

        return (
          hasAriaLabel ||
          hasAriaLabelledby ||
          hasTextContent
        );
      },
      metadata: {
        description:
          'すべてのボタンには明示的なラベルが必要です',
        help: 'ボタンに aria-label、aria-labelledby、またはテキストコンテンツを設定してください',
      },
    },
  ],
};

このカスタムルールは、すべてのボタン要素に対して、何らかの形でラベルが設定されているかをチェックします。

カスタムルールを使用したテスト

typescripttest('カスタムルールを含むアクセシビリティ検証', async ({
  page,
}) => {
  await page.goto('https://example.com');

  // カスタムルールを設定して axe を実行
  const accessibilityScanResults = await new AxeBuilder({
    page,
  })
    .configure(customRules)
    .analyze();

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

カスタムルールを configure() メソッドで設定することで、プロジェクト独自の検証基準を適用できます。

実践例 4:複数ページの一括検証

実際のプロジェクトでは、複数のページを効率的に検証したい場合があります。テストケースをループで回すことで、保守性の高いテストを作成できます。

検証対象ページの定義

typescript/**
 * 検証対象のページリスト
 */
const pagesToTest = [
  { url: '/', name: 'トップページ' },
  { url: '/about', name: '会社概要' },
  { url: '/services', name: 'サービス一覧' },
  { url: '/contact', name: 'お問い合わせ' },
  { url: '/faq', name: 'よくある質問' },
];

検証したいページを配列で定義しておくことで、テストコードの重複を避けられます。

ループによる一括検証

typescripttest.describe('全ページのアクセシビリティ検証', () => {
  for (const pageInfo of pagesToTest) {
    test(`${pageInfo.name}の検証`, async ({ page }) => {
      // ページに移動
      await page.goto(`https://example.com${pageInfo.url}`);

      // ページが完全に読み込まれるまで待機
      await page.waitForLoadState('networkidle');

      // axe を実行
      const accessibilityScanResults = await new AxeBuilder(
        { page }
      )
        .withTags([
          'wcag2a',
          'wcag2aa',
          'wcag21a',
          'wcag21aa',
        ])
        .analyze();

      // 違反があれば詳細を出力
      if (accessibilityScanResults.violations.length > 0) {
        console.log(
          `\n${pageInfo.name}のアクセシビリティ違反:`
        );
        logViolations(accessibilityScanResults.violations);
      }

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

このテストでは、定義したページリストをループで回し、各ページに対して同じ検証を実行しています。新しいページを追加する場合も、配列に追加するだけで済むため、保守性が高いのです。

実践例 5:継続的インテグレーションへの組み込み

最後に、作成したテストを CI/CD パイプラインに組み込む方法を見ていきます。

GitHub Actions の設定例

yamlname: Accessibility Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  accessibility-test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

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

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Install Playwright browsers
        run: yarn playwright install --with-deps chromium

      - name: Run accessibility tests
        run: yarn playwright test --grep "アクセシビリティ"

      - name: Upload test results
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: accessibility-report
          path: playwright-report/

この GitHub Actions の設定では、コードがプッシュまたはプルリクエストされた際に、自動的にアクセシビリティテストが実行されます。テストが失敗した場合は、レポートがアーティファクトとして保存されるのです。

package.json のスクリプト設定

json{
  "scripts": {
    "test:a11y": "playwright test --grep 'アクセシビリティ'",
    "test:a11y:headed": "playwright test --grep 'アクセシビリティ' --headed",
    "test:a11y:debug": "playwright test --grep 'アクセシビリティ' --debug"
  }
}

これらのスクリプトを定義しておくことで、ローカル環境でも CI 環境でも同じコマンドでテストを実行できます。

まとめ

Playwright と axe-core を組み合わせることで、アクセシビリティの自動検証を効率的に実現できます。手動での検証に比べて、時間的コストを大幅に削減できるだけでなく、検証漏れを防ぎ、継続的にアクセシビリティを担保することが可能になりました。

特に、ARIA 属性の検証においては、単に属性が存在するかだけでなく、参照先の要素が実際に存在するか、フォーカス管理が適切に行われているかなど、動的な振る舞いまで含めた包括的なテストを実装できることがわかりました。

以下が、本記事で解説した主なポイントです。

#ポイントメリット
1@axe-core/playwright による統合Playwright テスト内で簡単に axe を実行できる
2WCAG レベルに応じた検証プロジェクトの要件に合わせた柔軟な検証が可能
3特定要素に絞った検証コンポーネント単位での検証が可能
4カスタムルールの追加プロジェクト固有の要件に対応できる
5CI/CD への組み込み継続的にアクセシビリティを保証できる

アクセシビリティは、一度対応すれば終わりではなく、継続的に維持していく必要があります。Playwright と axe-core を活用した自動テストを開発フローに組み込むことで、すべてのユーザーにとって使いやすい Web サイトを提供し続けることができるのです。

ぜひ、本記事で紹介した実装例を参考に、あなたのプロジェクトにもアクセシビリティ自動検証を導入してみてください。最初は小さなテストから始めて、徐々に範囲を広げていくことをお勧めします。

関連リンク