無障礙功能測試
簡介
Playwright 可用於測試您的應用程式是否存在多種類型的無障礙功能問題。
以下是一些可以偵測到的問題範例
- 由於與背景的色彩對比度不佳,導致有視覺障礙的使用者難以閱讀的文字
- 沒有螢幕閱讀器可以識別的標籤的 UI 控制項和表單元素
- 具有重複 ID 的互動式元素,可能會混淆輔助技術
以下範例依賴 com.deque.html.axe-core/playwright
Maven 套件,該套件新增了執行 axe 無障礙功能測試引擎 作為 Playwright 測試一部分的支援。
免責聲明
自動化無障礙功能測試可以偵測到一些常見的無障礙功能問題,例如遺失或無效的屬性。但是,許多無障礙功能問題只能透過手動測試來發現。我們建議結合使用自動化測試、手動無障礙功能評估和包含性使用者測試。
對於手動評估,我們推薦 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 的自動檢查 僅包含 axe 規則,這些規則測試是否違反 WCAG A 和 AA 成功準則;若要符合該行為,您將使用標籤 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 標籤」章節 中找到 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 規則的完整清單 可以在 axe-core
的文件中找到。
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
類別。
建立夾具
此範例夾具會建立一個 AxeBuilder
物件,該物件已預先設定共用的 withTags()
和 exclude()
組態。
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());
}
}
請參閱實驗性的 JUnit 整合,以自動初始化 Playwright 物件和更多功能。