アクセシビリティテスト
はじめに
Playwright は、アプリケーションのアクセシビリティに関する様々な問題をテストするために使用できます。
これにより検出できる問題の例をいくつか挙げます。
- 背景色とのコントラストが低いため、視覚障碍を持つユーザーにとって読みにくいテキスト
- スクリーンリーダーが識別できるラベルのない UI コントロールとフォーム要素
- 支援技術を混乱させる可能性のある重複した ID を持つインタラクティブ要素
以下の例では、Playwright テストの一部として axe アクセシビリティテストエンジンを実行するためのサポートを追加する、com.deque.html.axe-core/playwright
Maven パッケージに依存しています。axe アクセシビリティテストエンジン。
免責事項
自動化されたアクセシビリティテストは、欠落または無効なプロパティなど、一般的なアクセシビリティの問題を検出できます。しかし、多くのアクセシビリティの問題は、手動テストによってのみ発見できます。自動テスト、手動アクセシビリティ評価、およびインクルーシブユーザテストの組み合わせを使用することをお勧めします。
手動評価には、Accessibility Insights for Web をお勧めします。これは、WCAG 2.1 AA カバレッジについてウェブサイトを評価する手順を説明する、無料でオープンソースの開発ツールです。
アクセシビリティテストの例
アクセシビリティテストは、他の Playwright テストとまったく同様に機能します。アクセシビリティスキャンとアサーションを既存のテストケースに統合するか、またはそれらのための個別のテストケースを作成できます。
以下の例では、いくつかの基本的なアクセシビリティテストのシナリオを示します。
例 1: ページ全体のスキャン
この例では、ページ全体を自動検出可能なアクセシビリティ違反についてテストする方法を示します。このテストは
com.deque.html.axe-core/playwright
パッケージをインポートします。- 通常の JUnit 5
@Test
構文を使用してテストケースを定義します。 - 通常の Playwright 構文を使用してブラウザを開き、テスト対象のページに移動します。
AxeBuilder.analyze()
を呼び出して、ページに対してアクセシビリティスキャンを実行します。- 通常の 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
クラスで Builder パターンを使用することで、これらのオプションを指定できます。
たとえば、AxeBuilder.include()
を使用して、アクセシビリティスキャンをページの特定の部分のみに対して実行するように制限できます。
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 ルールのみが含まれています。その動作に一致させるには、タグ wcag2a
、wcag2aa
、wcag21a
、および 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 つの特定のテストで 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-core のドキュメント で、axe のルールの完全なリストを見つけることができます。
AxeResults accessibilityScanResults = new AxeBuilder(page)
.disableRules(Arrays.asList("duplicate-id"))
.analyze();
assertEquals(Collections.emptyList(), accessibilityScanResults.getViolations());
既知の問題を特定するための違反フィンガープリントの使用
既知の問題のより詳細なセットを許可したい場合は、次のパターンを使用できます。
- いくつかの既知の違反を見つけることが期待されるアクセシビリティスキャンを実行します。
- 違反を「違反フィンガープリント」オブジェクトに変換します。
- フィンガープリントのセットが予期されるものと同等であることをアサートします。
このアプローチは、わずかに複雑さと脆弱性が増すという代償を払って、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
構成を共有するのに適した方法です。役立つ可能性のあるシナリオには、次のようなものがあります。
- すべてのテスト間で共通のルールセットを使用する
- 多くの異なるページに表示される共通要素で既知の違反を抑制する
- 多くのスキャンに対してスタンドアロンのアクセシビリティレポートを一貫して添付する
次の例は、いくつかの共通の AxeBuilder
構成を含む新しいフィクスチャを使用して、テストランナーの例 から TestFixtures
クラスを拡張する方法を示しています。
フィクスチャの作成
この例のフィクスチャは、共有の 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 統合 を参照してください。