Automated a11y Testing (axe, Lighthouse) — UI/UX

Automated Accessibility Testing Automated tools mendeteksi ~30-40% masalah aksesibilitas. Sisanya butuh manual testing. Tapi 30-40% itu menangkap low-hanging…

Automated accessibility testing artinya menyuruh program memeriksa halaman dengan daftar aturan yang bisa dicek mesin: gambar tanpa alt, kontras kurang, kolom tanpa label, bahasa yang gak disebut. Bayangin spellchecker di Google Docs. Dia langsung menggarisbawahi "kamr", tapi diam saja saat kamu menulis "kamar ini menghadap laut" padahal kamarnya menghadap bukit. Ejaannya benar, isinya salah. Alat otomatis seperti axe dan Lighthouse adalah spellchecker untuk aksesibilitas: cepat, gak pernah capek, dan buta terhadap makna. Tim aksesibilitas pemerintah Inggris (GOV.UK) memakai perumpamaan yang sama.

🧪 Tebakan cepat: Setelah lima perbaikan, halaman pesan Bu Sri dapat 0 violation di axe dan skor 100 di Lighthouse. Tombol "Pesan kamar, bayar di tempat" masih <div onclick>. Bisa Pak Hadi (58), yang memesan pakai keyboard, menekan tombol itu? Jawabannya ada di bagian "Skor 100 bukan berarti accessible".

Halaman yang kita uji

Kita pakai halaman pesan Kamar Teras yang sama dengan Lab di halaman ini, dan sengaja menanam 9 masalah. Semua angka di pelajaran ini berasal dari menjalankan alatnya sungguhan di halaman itu: axe-core 4.14.0 dan Lighthouse 13.5.0 di Chromium 147.

Masalah Kriteria WCAG Ketahuan oleh
<html> tanpa lang 3.1.1 axe, Lighthouse
foto kamar tanpa alt 1.1.1 axe, Lighthouse
tombol hati (favorit) cuma berisi ikon, tanpa nama 4.1.2 axe, Lighthouse
kolom Nomor WhatsApp tanpa <label> 4.1.2, 1.3.1 axe, Lighthouse
catatan "Gratis batal..." #94a3b8 di atas putih 1.4.3 axe, Lighthouse
tombol Pesan dari <div onclick> 2.1.1 cek keyboard
peta rute dengan alt="gambar" 1.1.1 orang yang membaca alt-nya
:focus { outline: none; } 2.4.7 cek keyboard
kartu width: 480px 1.4.10 cek lebar 320px

Lima ketahuan mesin, empat cuma ketahuan orang.

axe: aturan yang dijalankan di halaman

axe-core adalah kumpulan aturan open source buatan Deque. Versi 4.14 punya 105 aturan, 70 di antaranya bertanda WCAG level A atau AA. Kamu bisa memakainya lewat ekstensi axe DevTools (tab "axe" di DevTools, lalu scan), lewat Lighthouse (yang menjalankan sebagian aturan axe), atau dari kode, misalnya @axe-core/playwright. Di halaman yang sudah memuat axe-core:

const results = await axe.run();
for (const v of results.violations) {
  console.log(`${v.id} (${v.impact}): ${v.help}`);
  for (const node of v.nodes) console.log(`  ${node.target.join(' ')}`);
}
console.log(`${results.violations.length} violations`);

Hasilnya di halaman Bu Sri, persis seperti yang tercetak:

button-name (critical): Buttons must have discernible text
  .fav
color-contrast (serious): Elements must meet minimum color contrast ratio thresholds
  .note
html-has-lang (serious): <html> element must have a lang attribute
  html
image-alt (critical): Images must have alternative text
  img[height="150"]
label (critical): Form elements must have labels
  #wa
5 violations

Satu contoh yang sering disalahpahami: id ganda. Mendeteksinya pekerjaan mesin, cukup membandingkan nilai id. Tapi di axe-core 4.14, aturan duplicate-id sudah deprecated dan mati secara bawaan, karena WCAG 2.2 menghapus kriteria 4.1.1 (Parsing). Dua <p id="total"> gak dilaporkan, kecuali kamu menyalakannya: axe.run({ rules: { 'duplicate-id': { enabled: true } } }). Kalau id ganda itu dipakai aria-describedby atau <label for>, aturan duplicate-id-aria menangkapnya, sebagai needs review.

Lighthouse: skor dari aturan axe yang diberi bobot

Lighthouse (DevTools, tab Lighthouse, kategori Accessibility) menjalankan sebagian aturan axe, memberi tiap audit bobot dari impact-nya, lalu menghitung bobot audit yang lolos dibagi bobot audit yang berlaku. Gak ada nilai setengah: satu tombol tanpa nama membuat seluruh audit button-name gagal.

Audit yang gagal di halaman Bu Sri Bobot
image-alt, button-name, label 10 masing-masing
html-has-lang, color-contrast 7 masing-masing

Halaman awal: 18 audit berlaku dengan total bobot 136, yang gagal berbobot 44, jadi 92 / 136 = 68. Setelah lima perbaikan, dua audit baru ikut berlaku (html-lang-valid dan aria-hidden-focus, 7 masing-masing), semuanya lolos: 150 / 150 = 100. Di Lab, skornya naik bertahap 68, 74, 81, 89, 95, 100, sama dengan Lighthouse sungguhan.

Skor 100 bukan berarti accessible

Di halaman yang skornya 100 itu, Tab berhenti di 9 tempat: empat link navigasi, tombol hati, tiga tombol malam, dan kolom WhatsApp. Tombol Pesan gak pernah kena, karena <div> gak bisa difokus. Jadi jawaban tebakannya: gak bisa. Gak ada aturan axe yang memeriksa onclick di <div>, dan Lighthouse menaruh "Interactive controls are keyboard focusable" di daftar Additional items to manually check dengan bobot 0. Itu jawaban tebakannya. Lighthouse sendiri menulis di laporannya: "Automatic detection can only detect a subset of issues and does not guarantee the accessibility of your web app, so manual testing is also encouraged."

Berapa besar bagian yang ditangkap mesin? Tergantung cara menghitungnya:

Angka "30 sampai 40%" yang sering dikutip ada di antara keduanya. Intinya sama: alat otomatis menangkap masalah yang paling sering dan paling mudah ditangkap, di setiap build, tanpa capek. Sisanya tetap butuh orang.

eslint-plugin-jsx-a11y: cek sejak di editor

axe memeriksa halaman yang sudah jadi. Untuk React, eslint-plugin-jsx-a11y memeriksa JSX-nya, jadi garis merah muncul di editor sebelum halamannya dibuka.

// eslint.config.mjs
import jsxA11y from 'eslint-plugin-jsx-a11y';

export default [
  { files: ['**/*.{js,jsx}'], ...jsxA11y.flatConfigs.recommended },
];

Di komponen KartuKamar.jsx dengan <img src="/kamar-teras.jpg" /> (baris 5), <img src="/peta.png" alt="gambar" /> (baris 6), <div className="book" onClick={pesan}> (baris 7), dan catatan berwarna #94a3b8, npx eslint KartuKamar.jsx (plugin 6.10.2) mencetak:

5:7  error  img elements must have an alt prop, either with meaningful text, or an empty string for decorative images  jsx-a11y/alt-text
7:7  error  Visible, non-interactive elements with click handlers must have at least one keyboard listener  jsx-a11y/click-events-have-key-events
7:7  error  Avoid non-native interactive elements. ...  jsx-a11y/no-static-element-interactions

Linter melihat onClick di <div>, yang lolos dari axe. Tapi dia gak tahu warna akhirnya (kontras gak dicek), dan img-redundant-alt cuma mencari kata Inggris "image", "photo", "picture", jadi alt="gambar" lolos. Tiap alat melihat lapisan yang berbeda.

Di CI: gagal kalau ada violation baru

Pasang axe di tes Playwright, lalu jalankan di CI (misalnya GitHub Actions) setiap ada pull request. Situs lama biasanya sudah punya utang violation; simpan daftarnya di a11y-baseline.json, dan buat tes gagal cuma kalau muncul violation yang baru.

// tests/a11y.spec.js
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import baseline from './a11y-baseline.json' with { type: 'json' };

test('halaman pesan Kamar Teras', async ({ page }) => {
  await page.goto('/kamar/teras');
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
    .analyze();
  const found = results.violations.flatMap((v) => v.nodes.map((n) => `${v.id} ${n.target.join(' ')}`));
  const baru = found.filter((f) => !baseline.includes(f));
  expect(baru).toEqual([]);
});

Cek manual yang wajib menyusul

Setelah axe bersih, lakukan tiga cek ini. Masing-masing menemukan masalah di halaman Bu Sri yang skornya 100:

⚠️ Jebakan yang sering ketemu

TL;DR: Alat otomatis itu spellchecker aksesibilitas: menangkap yang bisa dicek mesin, buta terhadap makna. axe-core (Deque, 105 aturan di 4.14) melaporkan violations dengan id, impact (minor sampai critical), help, dan selector target; incomplete berarti needs review. Di halaman Bu Sri, axe menemukan 5 dari 9 masalah: html-has-lang, image-alt, button-name, label, color-contrast (2,56:1). Id ganda bisa dicek mesin, tapi duplicate-id mati secara bawaan sejak WCAG 2.2 menghapus 4.1.1. Lighthouse menghitung bobot audit yang lolos dibagi yang berlaku (68 jadi 100), tanpa nilai setengah, dan menyebut sendiri bahwa skornya gak menjamin halaman accessible: tombol Pesan dari <div onclick> tetap gak bisa di-Tab di halaman yang skornya 100. Cakupan alat: GOV.UK 2017, satu alat 17 sampai 41%; Deque 2021, 57,38% dari jumlah masalah tapi cuma 16 dari 50 kriteria AA, karena masalah yang paling sering (kontras) paling mudah dicek. eslint-plugin-jsx-a11y menangkap onClick di <div> sejak di editor. Di CI, @axe-core/playwright dengan baseline membuat build gagal cuma untuk violation baru. Setelah itu wajib cek manual: keyboard (2.1.1, 2.4.3, 2.4.7), zoom 200% (1.4.4) dan lebar 320 CSS px (1.4.10), dan screen reader.

Yang akan kamu pelajari