無障礙測試
簡介
Playwright 可以用來測試應用程式的多種類型的無障礙問題。
一些此方法可以捕捉到的問題範例如下:
- 由於與背景的顏色對比差,視障用戶難以閱讀的文字
- 屏幕閱讀器無法識別的無標籤 UI 控件和表單元素
- 具有重複 ID 的互動元素,這會混淆輔助技術
以下範例依賴 com.deque.html.axe-core/playwright
Maven 套件,該套件增加了在您的 Playwright 測試中執行 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()
之前使用 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 規則;要匹配該行為,您需要使用標籤 wcag2a
、wcag2aa
、wcag21a
和 wcag21aa
。
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());
使用違規指紋來特定已知問題
如果你希望允許更細緻的已知問題集,你可以使用以下模式:
- 執行無障礙掃描,預期會發現一些已知的違規
- 將違規轉換為“違規指紋”物件
- 斷言指紋集等同於預期的指紋
這種方法避免了使用 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());
}