Skip to main content

無障礙測試

簡介

Playwright 可以用來測試應用程式的多種類型的無障礙問題。

一些此方法可以捕捉到的問題範例如下:

  • 由於與背景的顏色對比差,視障用戶難以閱讀的文字
  • 屏幕閱讀器無法識別的無標籤 UI 控件和表單元素
  • 具有重複 ID 的互動元素,這會混淆輔助技術

以下範例依賴 com.deque.html.axe-core/playwright Maven 套件,該套件增加了在您的 Playwright 測試中執行 axe 無障礙測試引擎 的支援。

免責聲明

自動化無障礙測試可以檢測一些常見的無障礙問題,例如缺少或無效的屬性。但許多無障礙問題只能通過手動測試發現。我們建議結合自動化測試、手動無障礙評估和包容性用戶測試。

對於手動評估,我們推薦 Accessibility Insights for Web,這是一個免費且開放原始碼的開發工具,能夠引導你評估網站的 WCAG 2.1 AA 覆蓋範圍。

範例無障礙測試

無障礙測試的工作方式與其他 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 類別的 Builder 模式來指定這些選項。

例如,你可以使用 AxeBuilder.include() 將無障礙掃描限制為僅針對頁面的某一特定部分執行。

AxeBuilder.analyze() 將在您呼叫它時掃描頁面當前的狀態。要掃描基於 UI 互動顯示的頁面部分,請在呼叫 analyze() 之前使用 Locators 與頁面互動:

@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 標準特別要求的“最佳實踐”規則。

您可以限制無障礙掃描僅執行那些被「標記」為對應特定 WCAG 成功標準的規則,方法是使用 AxeBuilder.withTags()。例如,Accessibility Insights for Web's Automated Checks 只包含測試 WCAG A 和 AA 成功標準違規的 axe 規則;要匹配該行為,您需要使用標籤 wcag2awcag2aawcag21awcag21aa

請注意,自動化測試無法檢測所有類型的 WCAG 違規

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

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

您可以在 axe API 文件的 "Axe-core Tags" 部分 找到 axe-core 支援的規則標籤的完整列表。

處理已知問題

在將無障礙測試添加到應用程式時,一個常見的問題是“如何抑制已知的違規?”以下範例展示了一些您可以使用的技術。

排除單個元素從掃描中

如果你的應用程式包含一些已知問題的特定元素,你可以使用 AxeBuilder.exclude() 將它們排除在掃描範圍之外,直到你能夠修復這些問題。

這通常是最簡單的選項,但它有一些重要的缺點:

  • exclude() 將排除指定的元素 及其所有後代。避免將其用於包含許多子元素的元件。
  • exclude() 將防止 所有 規則針對指定的元素執行,而不僅僅是針對已知問題的規則。

這裡是一個在特定測試中排除一個元素不被掃描的範例:

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

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

如果相關元素在許多頁面中重複使用,考慮使用測試裝置來在多個測試中重用相同的 AxeBuilder 配置。

停用個別掃描規則

如果您的應用程式包含許多特定規則的現有違規行為,您可以使用 AxeBuilder.disableRules() 暫時停用個別規則,直到您能夠修復這些問題。

您可以在您想要抑制的違規項的 id 屬性中找到要傳遞給 disableRules() 的規則 ID。axe-core 的文件中可以找到 axe 的完整規則列表

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

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

使用違規指紋來特定已知問題

如果你希望允許更細緻的已知問題集,你可以使用以下模式:

  1. 執行無障礙掃描,預期會發現一些已知的違規
  2. 將違規轉換為“違規指紋”物件
  3. 斷言指紋集等同於預期的指紋

這種方法避免了使用 AxeBuilder.exclude() 的缺點,但代價是稍微增加了複雜性和脆弱性。

這裡是一個僅基於規則 ID 和指向每個違規的 "target" 選擇器使用指紋的範例:

@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 配置的好方法。一些可能有用的情境包括:

  • 在所有測試中使用一組通用規則
  • 抑制在許多不同頁面中出現的常見元素中的已知違規
  • 一致地附加獨立的無障礙報告以進行多次掃描

以下範例展示了從 Test Runners 範例 擴展 TestFixtures 類別,並新增一個包含一些常見 AxeBuilder 配置的新 fixture。

建立一個 fixture

此範例裝置建立一個 AxeBuilder 物件,該物件預先配置了共享的 withTags()exclude() 配置。

class AxeTestFixtures extends TestFixtures {
AxeBuilder makeAxeBuilder() {
return new AxeBuilder(page)
.withTags(['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());
}
}

請參閱實驗性 JUnit integration 以自動初始化 Playwright 物件及更多。