Masalah terbesar dalam aksesibilitas bukan selalu komponen yang terlihat rusak, melainkan alur yang secara visual tampak normal tetapi gagal total saat dipakai dengan keyboard, pembaca layar, atau fokus yang berpindah secara dinamis. Inilah alasan strategi uji aksesibilitas tidak boleh berhenti di audit otomatis satu kali atau pemeriksaan warna semata.
Sudut pandang ini biasanya menjadi nyata ketika tim bekerja dengan pengguna tunanetra. Dari luar, sebuah form bisa tampak sempurna: label ada, tombol ada, validasi muncul. Tetapi saat dibaca screen reader, urutan fokus meloncat, error tidak diumumkan, dialog tidak mengambil fokus, atau tombol hanya terbaca sebagai “button” tanpa nama yang jelas. Artikel ini mengubah pengalaman tersebut menjadi workflow verifikasi teknis yang bisa diterapkan tim produk secara konsisten.
Mengapa bug aksesibilitas sering lolos meski UI tampak normal
Mayoritas pipeline pengujian aplikasi web masih berat di validasi visual dan fungsi dasar: komponen muncul, klik bekerja, request berhasil. Pendekatan itu tidak cukup untuk aksesibilitas karena banyak cacat terjadi di lapisan semantik dan interaksi:
- Nama aksesibel tidak ada atau tidak tepat, sehingga screen reader tidak memberi konteks.
- Urutan fokus salah, membuat pengguna keyboard tersesat.
- Status dinamis tidak diumumkan, misalnya hasil pencarian, error validasi, atau loading selesai.
- Komponen kustom meniru elemen native secara visual, tetapi gagal memberi role, state, dan perilaku keyboard yang benar.
- Dialog, menu, tab, combobox bekerja saat diklik mouse, namun tidak stabil saat dinavigasi dengan tombol panah, Tab, Escape, atau Enter.
Karena itu, pengujian aksesibilitas harus diperlakukan sebagai gabungan antara verifikasi semantik, perilaku interaksi, dan pengalaman penggunaan nyata, bukan sekadar pemeriksaan atribut HTML.
Test pyramid untuk aksesibilitas
Pendekatan yang paling praktis adalah membangun test pyramid khusus aksesibilitas. Tujuannya bukan memindahkan semua verifikasi ke level end-to-end, tetapi menempatkan jenis pemeriksaan yang tepat di layer yang tepat.
1. Dasar: linting dan automation
Lapisan terbawah menangkap cacat yang murah diperbaiki dan mudah diulang. Ini mencakup:
- Linting markup dan JSX/templating untuk pola yang jelas salah.
- Audit otomatis terhadap DOM hasil render.
- Pemeriksaan rule yang bisa dinilai mesin, seperti relasi label-input, alt text kosong yang salah pakai, elemen interaktif tanpa nama, atau heading structure yang buruk.
Lapisan ini cepat, cocok dijalankan di pre-commit atau CI, dan efektif mencegah regresi dasar. Namun automation tidak cukup untuk memverifikasi apakah komponen benar-benar nyaman digunakan.
2. Tengah: integration test untuk perilaku aksesibel
Di level ini, fokusnya bukan hanya apakah elemen ada, tetapi apakah perilaku dan semantiknya benar saat dipakai. Contohnya:
- Dialog mengambil fokus saat dibuka dan mengembalikannya saat ditutup.
- Error validasi dikaitkan ke field yang tepat.
- Autocomplete mengumumkan hasil dan mendukung navigasi keyboard.
- State seperti
expanded,selected, ataudisabledberubah sesuai interaksi.
Integration test adalah tempat yang ideal untuk menguji komponen dinamis yang sering lolos dari linting.
3. Puncak: exploratory test dengan keyboard dan screen reader
Lapisan teratas harus manual dan terarah. Tujuannya menangkap hal yang tidak bisa disimpulkan penuh oleh alat otomatis, misalnya:
- Apakah nama dan instruksi terdengar natural saat dibacakan?
- Apakah urutan fokus masuk akal dari perspektif tugas pengguna?
- Apakah perubahan konten dinamis diumumkan pada waktu yang tepat, tidak terlalu lambat atau terlalu sering?
- Apakah ada jebakan fokus, duplikasi pembacaan, atau konteks yang hilang?
Exploratory test tidak perlu dilakukan pada setiap komponen di setiap commit, tetapi wajib untuk alur kritikal dan komponen kompleks.
Workflow verifikasi teknis yang bisa dipakai tim produk
Jika pengalaman dengan klien tunanetra mengajarkan satu hal, itu adalah: jangan menunggu audit besar di akhir sprint. Aksesibilitas perlu masuk ke workflow harian.
Langkah 1: identifikasi alur kritikal
Mulai dari alur yang bernilai bisnis dan berisiko tinggi:
- Login dan reset password
- Pencarian dan filter
- Checkout atau pembayaran
- Form pendaftaran
- Upload file
- Navigasi utama dan menu akun
Setiap alur ini harus punya definisi pass/fail aksesibilitas yang eksplisit, bukan asumsi umum seperti “sudah dites”.
Langkah 2: tetapkan kontrak aksesibilitas per komponen
Untuk komponen UI yang sering dipakai, dokumentasikan kontrak minimal:
- Role: misalnya dialog, button, textbox, checkbox.
- Nama aksesibel: dari label,
aria-label, atauaria-labelledby. - Perilaku keyboard: Tab, Shift+Tab, Enter, Space, Escape, tombol panah.
- State dinamis: expanded/collapsed, selected, invalid, busy.
- Manajemen fokus: fokus awal, perpindahan, dan pengembalian fokus.
Tanpa kontrak ini, tim sulit menulis test yang konsisten.
Langkah 3: jalankan verifikasi berlapis
- Linting dan audit otomatis di lokal dan CI.
- Integration test untuk komponen interaktif dan alur kritikal.
- Exploratory test manual untuk rilis atau perubahan besar pada UI dinamis.
Dengan struktur ini, bug dasar ditangkap cepat, sementara bug perilaku yang lebih halus diuji dengan konteks yang sesuai.
Contoh implementasi: linting dan automation
Contoh berikut menggunakan kombinasi yang umum: pengujian DOM dengan query berbasis role/nama aksesibel dan audit otomatis aksesibilitas. Prinsipnya lebih penting daripada tool tertentu: uji dari sudut pandang aksesibel, bukan selector visual yang rapuh.
// Contoh integration test yang memverifikasi semantik dan perilaku dasar
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { axe } from 'jest-axe'
import LoginForm from './LoginForm'
test('form login memiliki label yang benar dan bebas pelanggaran dasar', async () => {
const { container } = render(<LoginForm />)
expect(screen.getByRole('textbox', { name: /email/i })).toBeInTheDocument()
expect(screen.getByLabelText(/password/i)).toBeInTheDocument()
expect(screen.getByRole('button', { name: /masuk/i })).toBeInTheDocument()
const results = await axe(container)
expect(results).toHaveNoViolations()
})
test('error validasi diumumkan dan terhubung ke field', async () => {
const user = userEvent.setup()
render(<LoginForm />)
await user.click(screen.getByRole('button', { name: /masuk/i }))
const email = screen.getByRole('textbox', { name: /email/i })
const alert = screen.getByRole('alert')
expect(alert).toHaveTextContent(/email wajib diisi/i)
expect(email).toHaveAttribute('aria-invalid', 'true')
})Mengapa pendekatan ini bekerja? Karena query seperti getByRole dan getByLabelText memaksa komponen bisa ditemukan dengan cara yang mirip teknologi bantu. Jika test hanya mencari .btn-primary atau #email-input, cacat aksesibilitas bisa lolos meski test hijau.
Sinyal pass/fail yang jelas di lapisan automation
- Pass: elemen interaktif punya role dan nama yang benar, audit otomatis tidak menemukan pelanggaran dasar, field invalid punya state yang dapat dibaca.
- Fail: button tanpa nama, input tidak punya label yang dapat diasosiasikan, heading terputus tanpa struktur, atau muncul pelanggaran audit yang relevan.
Catatan: audit otomatis sangat berguna, tetapi tidak bisa memastikan pengalaman screen reader benar-benar masuk akal. Ia menjawab “ada pola yang salah?”, bukan “apakah alur ini nyaman dipakai?”.
Integration test untuk komponen dinamis
Area yang paling sering menghasilkan cacat tak terlihat adalah komponen dinamis: modal, drawer, dropdown, combobox, toast, validasi async, infinite list. Di sinilah integration test memberi nilai besar.
Contoh: modal harus mengelola fokus
test('dialog mengambil fokus saat dibuka dan mengembalikannya saat ditutup', async () => {
const user = userEvent.setup()
render(<SettingsPage />)
const openButton = screen.getByRole('button', { name: /ubah preferensi/i })
await user.click(openButton)
const dialog = screen.getByRole('dialog', { name: /preferensi/i })
expect(dialog).toBeInTheDocument()
expect(screen.getByRole('button', { name: /simpan/i })).toHaveFocus()
await user.keyboard('{Escape}')
expect(openButton).toHaveFocus()
})Test ini penting karena dialog yang tampil dengan benar secara visual tetap bisa rusak total jika fokus tetap tertinggal di belakang layar atau hilang setelah ditutup.
Contoh: hasil pencarian async harus muncul dengan status yang dapat dipahami
test('hasil pencarian muncul setelah input dan status loading tidak memutus navigasi', async () => {
const user = userEvent.setup()
render(<SearchBox />)
const input = screen.getByRole('textbox', { name: /cari produk/i })
await user.type(input, 'kursi')
expect(screen.getByText(/memuat hasil/i)).toBeInTheDocument()
const results = await screen.findByRole('list', { name: /hasil pencarian/i })
expect(results).toBeInTheDocument()
})Di sini yang diuji bukan hanya data muncul, tetapi apakah perubahan status punya representasi yang bisa diinterpretasi pengguna non-visual.
Sumber flaky test pada pengujian aksesibilitas dinamis
Pengujian aksesibilitas sering menjadi flaky bukan karena konsepnya salah, melainkan karena aplikasi modern banyak melakukan render ulang, animasi, dan update asynchronous. Sumber masalah yang umum:
1. Selector terlalu visual atau terlalu spesifik
Test yang bergantung pada class CSS, urutan child, atau struktur DOM internal mudah rusak saat refactor. Untuk aksesibilitas, lebih stabil memakai:
getByRoledengan nama aksesibelgetByLabelTextfindByRoleuntuk konten async
Jika elemen sulit dicari dengan cara ini, sering kali itu tanda semantik komponen memang belum benar.
2. Timing tidak sinkron dengan update UI
Komponen dinamis sering menunggu debounce, fetch, transisi, atau state update bertahap. Masalah umum:
- Assertion dijalankan sebelum elemen benar-benar tersedia
- Animasi menyembunyikan elemen secara visual tapi masih ada di DOM
- Live region diperbarui setelah test selesai memeriksa hasil
Strategi stabilisasi:
- Gunakan query async seperti
findByRoleuntuk elemen yang muncul belakangan. - Tunggu perubahan yang bermakna, bukan menambahkan delay tetap.
- Kurangi ketergantungan pada animasi dalam test environment jika memungkinkan.
- Pastikan mock network mengembalikan data deterministik.
3. ID dinamis dan nama yang berubah-ubah
Jika nama aksesibel dibentuk dari data yang tidak stabil, test menjadi rapuh. Solusinya:
- Gunakan label yang konsisten untuk kontrol utama.
- Hindari menggantungkan assertion pada teks yang sangat volatil jika yang ingin diuji sebenarnya role atau state.
- Jika perlu, pisahkan test semantik dari test copywriting.
4. Fokus berpindah karena side effect yang tidak eksplisit
Komponen yang mengautofokuskan input, merender portal, atau memicu re-render parent bisa menyebabkan fokus tidak stabil. Debugging yang efektif:
- Assert fokus pada titik penting setelah aksi pengguna.
- Log elemen aktif saat test gagal.
- Periksa apakah trap focus atau restore focus dijalankan lebih dari sekali.
// Contoh debugging sederhana
screen.debug()
console.log(document.activeElement)Kapan otomatisasi tidak cukup
Ini bagian yang sering disalahpahami. Otomatisasi sangat membantu, tetapi ada batas yang tidak bisa ditembus penuh oleh tool.
Kasus yang butuh exploratory test manual
- Kualitas pembacaan screen reader: nama, deskripsi, dan urutan informasi bisa formalnya valid tetapi tetap membingungkan.
- Alur tugas multi-langkah: misalnya checkout, wizard, atau dashboard kompleks.
- Komponen ARIA kompleks: tree view, grid, combobox, roving tabindex, menu bertingkat.
- Live region: notifikasi bisa terlalu sering diumumkan atau justru tidak terdengar pada momen penting.
- Interaksi keyboard tingkat lanjut: panah, Home/End, Escape, typeahead, dan kombinasi state.
Dalam kondisi ini, test otomatis hanya memberi jaring pengaman awal. Verifikasi manual tetap diperlukan untuk memastikan pengalaman nyata.
Protokol exploratory test yang praktis
- Cabut mouse dan navigasikan alur hanya dengan keyboard.
- Pastikan fokus selalu terlihat dan berpindah logis.
- Ulangi alur yang sama dengan screen reader pada halaman utama yang berubah.
- Catat frasa yang dibacakan untuk kontrol, error, heading, dan status dinamis.
- Tandai bagian yang “secara teknis valid” tetapi membingungkan secara penggunaan.
Sinyal gagal manual yang jelas:
- Tidak bisa mencapai kontrol penting dengan keyboard.
- Fokus masuk ke elemen tersembunyi atau keluar dari dialog aktif.
- Error muncul visual, tetapi screen reader tidak memberi tahu.
- Tombol atau link dibacakan tanpa nama yang bermakna.
- Pengguna kehilangan konteks setelah update async.
Regression prevention di CI
Strategi uji aksesibilitas akan gagal jika hanya bergantung pada ingatan reviewer. Perlu pagar pembatas di CI agar regresi tertangkap konsisten.
Checklist minimum di pipeline
- Jalankan linting aksesibilitas pada file yang berubah.
- Jalankan audit otomatis pada komponen atau halaman kritikal.
- Eksekusi integration test yang memverifikasi role, label, state, dan fokus.
- Tolak merge jika pelanggaran baru muncul tanpa pengecualian yang terdokumentasi.
- Simpan baseline hasil audit dengan hati-hati jika tim memang memakai model perbandingan.
Prinsip penting saat memasang gate CI
- Jangan mulai dari terlalu luas: mulai dari alur kritikal dan komponen bersama.
- Bedakan severity: tidak semua temuan harus langsung memblokir rilis, tetapi bug pada form, navigasi, dan autentikasi biasanya layak jadi blocker.
- Dokumentasikan pengecualian: jika ada pelanggaran yang belum bisa diperbaiki, catat alasan, dampak, dan tenggat perbaikannya.
Checklist rilis untuk menangkap cacat yang tak terlihat
Sebelum rilis, tim produk bisa memakai checklist singkat berikut:
Checklist teknis
- Semua kontrol interaktif utama bisa ditemukan dengan role dan nama yang benar.
- Setiap input penting punya label yang terasosiasi.
- Error validasi memiliki teks yang jelas dan state invalid pada field terkait.
- Dialog, drawer, dan menu mengelola fokus dengan benar.
- Konten dinamis penting memiliki mekanisme pengumuman yang sesuai.
- Navigasi keyboard tidak terjebak, tidak meloncat tanpa alasan, dan tidak melewati aksi penting.
- Komponen kustom tidak menggantikan elemen native tanpa alasan kuat.
Checklist proses
- Audit otomatis dijalankan di CI dan hasilnya ditinjau.
- Integration test alur kritikal lulus.
- Exploratory test keyboard selesai untuk alur prioritas.
- Minimal satu pemeriksaan screen reader dilakukan untuk perubahan besar pada interaksi atau struktur halaman.
- Regresi yang ditunda dicatat sebagai utang teknis yang bisa dilacak, bukan catatan lisan.
Kesimpulan
Strategi uji aksesibilitas yang efektif bukan soal memilih antara automation atau manual, tetapi merancang kombinasi yang tepat. Linting dan audit otomatis menangkap cacat dasar dengan cepat. Integration test memastikan semantik, fokus, dan state dinamis benar di level komponen dan alur. Exploratory test dengan keyboard dan screen reader menutup celah yang tidak bisa dilihat oleh UI biasa maupun alat otomatis.
Jika tim pernah menerima umpan balik dari pengguna tunanetra, jadikan itu bukan sekadar insiden, melainkan input untuk membangun kontrak komponen, test pyramid, dan gate CI yang bisa diulang. UI bisa terlihat baik-baik saja, tetapi kualitas aksesibilitas baru terbukti ketika alur penting tetap dapat dipahami dan dioperasikan tanpa penglihatan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!