メインコンテンツにスキップ

アクセシビリティテスト

はじめに

Playwright を使用して、アプリケーションの多くの種類のアクセシビリティ問題をテストできます。

これを検出できる問題の例をいくつか示します。

  • 背景との色のコントラストが低いために、視覚障害のあるユーザーが読みにくいテキスト
  • スクリーンリーダーが識別できるラベルのないUIコントロールおよびフォーム要素
  • 支援技術を混乱させる可能性のある、IDが重複しているインタラクティブな要素

以下の例は、com.deque.html.axe-core/playwright Maven パッケージに依存しています。このパッケージは、Playwright テストの一部としてaxe アクセシビリティテストエンジンを実行するためのサポートを追加します。

免責事項

自動化されたアクセシビリティテストは、プロパティの欠落や無効なプロパティなど、一般的なアクセシビリティ問題の一部を検出できます。しかし、多くのアクセシビリティ問題は手動テストによってのみ発見できます。自動テスト、手動によるアクセシビリティ評価、および包括的なユーザーテストを組み合わせて使用することをお勧めします。

手動評価には、WCAG 2.1 AA のカバレッジについてウェブサイトを評価する手順を案内する、無料のオープンソース開発ツールであるAccessibility Insights for Web をお勧めします。

アクセシビリティテストの例

アクセシビリティテストは、他の Playwright テストと同様に機能します。個別のテストケースを作成することも、既存のテストケースにアクセシビリティのスキャンとアサーションを統合することもできます。

以下の例は、いくつかの基本的なアクセシビリティテストシナリオを示しています。

例 1: ページ全体をスキャンする

この例は、自動的に検出可能なアクセシビリティ違反についてページ全体をテストする方法を示しています。テストでは

  1. com.deque.html.axe-core/playwright パッケージをインポートする
  2. 通常の JUnit 5 @Test 構文を使用してテストケースを定義する
  3. 通常の Playwright 構文を使用してブラウザを開き、テスト対象のページに移動する
  4. AxeBuilder.analyze() を呼び出して、ページに対してアクセシビリティスキャンを実行する
  5. 通常の JUnit 5 テストアサーションを使用して、返されたスキャン結果に違反がないことを検証する
import com.deque.html.axecore.playwright.*; // 1
import com.deque.html.axecore.utilities.axeresults.*;

import org.junit.jupiter.api.*;
import com.microsoft.playwright.*;

import static org.junit.jupiter.api.Assertions.*;

public class HomepageTests {
@Test // 2
void shouldNotHaveAutomaticallyDetectableAccessibilityIssues() throws Exception {
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();

page.navigate("https://your-site.com/"); // 3

AxeResults accessibilityScanResults = new AxeBuilder(page).analyze(); // 4

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations()); // 5
}
}

例 2: axe を設定してページの特定の部分をスキャンする

com.deque.html.axe-core/playwright は、axe の多くの構成オプションをサポートしています。これらのオプションは、AxeBuilder クラスでビルダーパターンを使用することで指定できます。

たとえば、AxeBuilder.include() を使用して、アクセシビリティスキャンをページの特定の 1 つの部分のみに制限できます。

AxeBuilder.analyze() は、呼び出し時にページを現在の状態でスキャンします。UIインタラクションに基づいて表示されるページのセクションをスキャンするには、analyze() を呼び出す前にロケーターを使用してページを操作します。

public class HomepageTests {
@Test
void navigationMenuFlyoutShouldNotHaveAutomaticallyDetectableAccessibilityViolations() throws Exception {
page.navigate("https://your-site.com/");

page.locator("button[aria-label=\"Navigation Menu\"]").click();

// It is important to waitFor() the page to be in the desired
// state *before* running analyze(). Otherwise, axe might not
// find all the elements your test expects it to scan.
page.locator("#navigation-menu-flyout").waitFor();

AxeResults accessibilityScanResults = new AxeBuilder(page)
.include(Arrays.asList("#navigation-menu-flyout"))
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());
}
}

例 3: WCAG 違反をスキャンする

デフォルトでは、axe は広範囲のアクセシビリティルールに対してチェックを実行します。これらのルールの一部はWeb Content Accessibility Guidelines (WCAG) の特定の成功基準に対応しており、その他はWCAGのいずれの基準にも具体的に要求されていない「ベストプラクティス」ルールです。

AxeBuilder.withTags() を使用して、特定のWCAG成功基準に対応すると「タグ付け」されたルールのみを実行するようにアクセシビリティスキャンを制限できます。たとえば、Accessibility Insights for Web の自動チェックには、WCAG A および AA 成功基準の違反をテストする axe ルールのみが含まれます。その動作に合わせるには、wcag2awcag2aawcag21a、および wcag21aa のタグを使用します。

注意点として、自動テストではすべての種類の WCAG 違反を検出することはできません

AxeResults accessibilityScanResults = new AxeBuilder(page)
.withTags(Arrays.asList("wcag2a", "wcag2aa", "wcag21a", "wcag21aa"))
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());

axe-core がサポートするルールタグの完全なリストは、axe API ドキュメントの「Axe-core Tags」セクションで確認できます。

既知の課題の対処

アプリケーションにアクセシビリティテストを追加する際によくある質問は、「既知の違反をどのように抑制するのか?」です。以下の例は、使用できるいくつかの手法を示しています。

個々の要素をスキャンから除外する

アプリケーションに既知の問題がある特定の要素がいくつか含まれている場合は、AxeBuilder.exclude() を使用して、問題を修正できるまでそれらをスキャンから除外できます。

これは通常最も簡単なオプションですが、いくつかの重要な欠点があります。

  • exclude() は、指定された要素とそのすべての子孫を除外します。多くの子を持つコンポーネントでは使用を避けてください。
  • exclude() は、指定された要素に対して、既知の問題に対応するルールだけでなく、すべてのルールが実行されるのを防ぎます。

以下は、特定のテストで1つの要素をスキャンから除外する例です。

AxeResults accessibilityScanResults = new AxeBuilder(page)
.exclude(Arrays.asList("#element-with-known-issue"))
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());

問題の要素が多くのページで繰り返し使用されている場合は、同じ AxeBuilder 設定を複数のテストで再利用するためにテストフィクスチャを使用することを検討してください。

個々のスキャンルールを無効にする

アプリケーションに特定のルールの既存の違反が多数含まれている場合は、AxeBuilder.disableRules() を使用して、問題を修正できるまで個々のルールを一時的に無効にできます。

disableRules() に渡すルールIDは、抑制したい違反の id プロパティで見つけることができます。axe のルールの完全なリストは、axe-core のドキュメントで確認できます。

AxeResults accessibilityScanResults = new AxeBuilder(page)
.disableRules(Arrays.asList("duplicate-id"))
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());

既知の課題を特定するための違反フィンガープリントの使用

より詳細な既知の課題を許可したい場合は、以下のパターンを使用できます。

  1. 既知の違反が見つかることが予想されるアクセシビリティスキャンを実行する
  2. 違反を「違反フィンガープリント」オブジェクトに変換する
  3. フィンガープリントのセットが予期されるものと等価であることをアサートする

このアプローチは、AxeBuilder.exclude() を使用するデメリットを回避できますが、複雑さと脆さがわずかに増します。

以下は、ルールIDと各違反を指す「ターゲット」セレクターのみに基づいたフィンガープリントの例です。

public class HomepageTests {
@Test
shouldOnlyHaveAccessibilityViolationsMatchingKnownFingerprints() throws Exception {
page.navigate("https://your-site.com/");

AxeResults accessibilityScanResults = new AxeBuilder(page).analyze();

List<ViolationFingerprint> violationFingerprints = fingerprintsFromScanResults(accessibilityScanResults);

assertEquals(Arrays.asList(
new ViolationFingerprint("aria-roles", "[span[role=\"invalid\"]]"),
new ViolationFingerprint("color-contrast", "[li:nth-child(2) > span]"),
new ViolationFingerprint("label", "[input]")
), violationFingerprints);
}

// You can make your "fingerprint" as specific as you like. This one considers a violation to be
// "the same" if it corresponds the same Axe rule on the same element.
//
// Using a record type makes it easy to compare fingerprints with assertEquals
public record ViolationFingerprint(String ruleId, String target) { }

public List<ViolationFingerprint> fingerprintsFromScanResults(AxeResults results) {
return results.getViolations().stream()
// Each violation refers to one rule and multiple "nodes" which violate it
.flatMap(violation -> violation.getNodes().stream()
.map(node -> new ViolationFingerprint(
violation.getId(),
// Each node contains a "target", which is a CSS selector that uniquely identifies it
// If the page involves iframes or shadow DOMs, it may be a chain of CSS selectors
node.getTarget().toString()
)))
.collect(Collectors.toList());
}
}

共通の axe 設定のためのテストフィクスチャの使用

TestFixtures クラスは、共通の AxeBuilder 設定を多くのテストで共有するための良い方法です。これが役立つシナリオには、次のようなものがあります。

  • すべてのテストで共通のルールセットを使用する
  • 多くの異なるページに共通して表示される要素で既知の違反を抑制する
  • 多くのスキャンに対して、スタンドアロンのアクセシビリティレポートを一貫して添付する

以下の例は、テストランナーの例から TestFixtures クラスを拡張し、共通の AxeBuilder 設定を含む新しいフィクスチャを追加する方法を示しています。

フィクスチャの作成

このフィクスチャの例では、共有の withTags() および exclude() 設定であらかじめ構成された AxeBuilder オブジェクトを作成します。

class AxeTestFixtures extends TestFixtures {
AxeBuilder makeAxeBuilder() {
return new AxeBuilder(page)
.withTags(new String[]{"wcag2a", "wcag2aa", "wcag21a", "wcag21aa"})
.exclude("#commonly-reused-element-with-known-issue");
}
}

フィクスチャの使用

フィクスチャを使用するには、以前の例の new AxeBuilder(page) を新しく定義された makeAxeBuilder フィクスチャに置き換えます。

public class HomepageTests extends AxeTestFixtures {
@Test
void exampleUsingCustomFixture() throws Exception {
page.navigate("https://your-site.com/");

AxeResults accessibilityScanResults = makeAxeBuilder()
// Automatically uses the shared AxeBuilder configuration,
// but supports additional test-specific configuration too
.include("#specific-element-under-test")
.analyze();

assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());
}
}

Playwright オブジェクトなどを自動的に初期化するには、実験的なJUnit 統合を参照してください。