Checklist CI untuk aksesibilitas frontend yang efektif bukan berarti semua pemeriksaan harus dijalankan di setiap langkah. Pendekatan yang sehat adalah membagi guardrail ke beberapa lapisan: cek cepat saat developer menulis kode, validasi yang sedikit lebih dalam sebelum push, lalu audit terpilih di CI berdasarkan risiko perubahan. Dengan cara ini, tim mendapatkan umpan balik cepat tanpa membuat pipeline lambat atau penuh false positive.

Dalam banyak diskusi komunitas developer, isu utamanya sering bukan sekadar alat apa yang dipakai, tetapi bagaimana menjaga proses kerja tim tetap waras saat standar kualitas bertambah. Untuk aksesibilitas, masalah umum adalah dua ekstrem: tidak ada cek sama sekali, atau semua hal dijadikan blocker sejak hari pertama. Artikel ini fokus pada jalan tengah yang praktis untuk tim web modern JavaScript: guardrail lokal + CI, severity gating, baseline pelanggaran lama, review PR yang terarah, dan rollout bertahap.

Mengapa checklist CI untuk aksesibilitas frontend sering gagal di tim

Banyak implementasi gagal bukan karena alatnya buruk, melainkan karena scope dan timing-nya salah. Beberapa pola yang sering terjadi:

  • Semua cek dijalankan di setiap commit, termasuk audit browser penuh. Hasilnya lambat dan diabaikan.
  • Semua temuan langsung memblokir merge, padahal proyek sudah punya utang aksesibilitas lama.
  • Tidak ada pemisahan severity, sehingga isu minor diperlakukan sama dengan kegagalan keyboard navigation atau missing form label.
  • Audit hanya di CI, sehingga feedback datang terlalu lambat dan konteks perbaikan sudah hilang.
  • Terlalu bergantung pada skor tunggal, misalnya skor Lighthouse, tanpa melihat jenis masalah yang sebenarnya berdampak.

Tujuan yang lebih realistis adalah: mencegah regresi aksesibilitas baru, memperbaiki isu kritis secepat mungkin, dan mengurangi utang lama secara bertahap.

Prinsip desain workflow: cepat di lokal, selektif di CI, ketat pada regresi baru

Untuk tim frontend modern JavaScript, pembagian berikut biasanya paling masuk akal:

1. Pre-commit: hanya cek yang sangat cepat

Jalankan linting yang bisa mendeteksi masalah dari source code atau JSX/TSX tanpa perlu browser penuh. Fokus di sini adalah feedback dalam hitungan detik.

  • ESLint dengan plugin aksesibilitas untuk JSX/template
  • Format dan type check ringan bila memang sudah menjadi standar tim
  • Scope file yang berubah saja

2. Pre-push: cek integrasi ringan

Pre-push cocok untuk tes yang lebih mahal daripada linting, tetapi masih cukup cepat dijalankan lokal. Misalnya smoke test halaman utama dengan Playwright dan audit axe di beberapa route penting.

  • Render halaman target
  • Jalankan audit otomatis terhadap komponen atau halaman yang berubah
  • Batasi jumlah skenario agar tidak mengganggu flow kerja

3. CI pipeline: pemeriksaan bertingkat berdasarkan risiko

CI sebaiknya tidak menjadi tempat pertama menemukan masalah sederhana. Perannya adalah validasi konsisten di environment terkontrol, terutama untuk pull request dan branch utama.

  • Stage cepat: lint + unit test + a11y lint
  • Stage menengah: Playwright + axe pada halaman kritis
  • Stage terjadwal atau selective: Lighthouse/audit lebih luas, tidak selalu memblokir PR

Pembagian ini bekerja karena biaya setiap jenis cek berbeda. Linting murah dan deterministik, sedangkan audit berbasis browser lebih mahal dan kadang lebih rentan terhadap noise.

Komponen inti checklist CI untuk aksesibilitas frontend

Linting aksesibilitas di level source code

Linting adalah guardrail paling murah untuk menangkap kesalahan umum sebelum aplikasi berjalan di browser. Contoh masalah yang biasanya bisa dideteksi:

  • Elemen interaktif tanpa label yang dapat dibaca
  • Penggunaan atribut ARIA yang tidak valid
  • Elemen klik yang bukan button/link tanpa dukungan keyboard yang benar
  • Input form tanpa label yang terasosiasi
  • Gambar penting tanpa alternatif teks

Untuk stack React/JSX, tim biasanya memakai ESLint dengan aturan aksesibilitas JSX. Untuk framework lain, prinsipnya sama: pilih linter yang memahami template atau markup yang digunakan tim.

# contoh skrip package.json yang sederhana dan aman dijalankan lokal
{
  "scripts": {
    "lint": "eslint .",
    "lint:a11y": "eslint src --ext .js,.jsx,.ts,.tsx"
  }
}

Kapan jadi blocker? Hampir selalu ya. Jika lint rule stabil dan jelas, sebaiknya kegagalan lint aksesibilitas memblokir merge karena biayanya rendah dan false positive relatif lebih sedikit dibanding audit dinamis.

Audit otomatis dengan axe di browser

Setelah linting, lapisan berikutnya adalah audit DOM yang benar-benar dirender. Di sini alat seperti axe berguna karena bisa menemukan isu yang tidak terlihat dari source code saja, misalnya struktur heading yang bermasalah, kontras tertentu, atau relasi ARIA yang rusak setelah komponen dirender.

Pendekatan yang praktis adalah menjalankan axe lewat end-to-end test runner seperti Playwright. Dengan begitu, tim tidak perlu membuat sistem audit terpisah; browser test dan audit aksesibilitas berbagi setup yang sama.

// contoh konsep test Playwright + axe
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('halaman checkout tidak memiliki pelanggaran a11y kritis', async ({ page }) => {
  await page.goto('/checkout');

  const results = await new AxeBuilder({ page })
    .include('main')
    .analyze();

  const seriousOrCritical = results.violations.filter(v =>
    ['serious', 'critical'].includes(v.impact || '')
  );

  expect(seriousOrCritical).toEqual([]);
});

Contoh di atas sengaja memfilter serious dan critical. Ini penting untuk severity gating: tidak semua temuan harus memblokir pipeline, terutama pada fase awal adopsi.

Playwright untuk skenario pengguna, bukan sekadar screenshot

Playwright tidak otomatis menjamin aksesibilitas, tetapi sangat cocok untuk memverifikasi alur yang sering rusak dalam UI modern:

  • Fokus berpindah ke modal saat modal terbuka
  • Urutan tab tetap masuk akal
  • Escape menutup dialog
  • Komponen custom dapat dioperasikan via keyboard
  • Pesan error form muncul dan terkait dengan field yang benar

Ini melengkapi axe. Audit otomatis bagus untuk aturan umum, sedangkan Playwright berguna untuk behavior yang sering menjadi sumber bug nyata bagi pengguna keyboard atau screen reader.

test('focus masuk ke modal setelah tombol dibuka', async ({ page }) => {
  await page.goto('/settings');
  await page.getByRole('button', { name: 'Buka preferensi' }).click();

  await expect(page.getByRole('dialog', { name: 'Preferensi' })).toBeVisible();
  await expect(page.getByRole('dialog', { name: 'Preferensi' })).toBeFocused();
});

Jika API spesifik berbeda di project Anda, pertahankan prinsip utamanya: uji perilaku fokus dan keyboard secara eksplisit, jangan diasumsikan benar hanya karena komponen terlihat benar.

Lighthouse sebagai audit tambahan, bukan satu-satunya pagar kualitas

Lighthouse bisa berguna untuk menambah sinyal, terutama untuk halaman publik yang penting. Namun untuk workflow tim, ada beberapa batasan:

  • Skor tunggal mudah disalahartikan
  • Hasil bisa berubah karena kondisi environment
  • Tidak semua masalah aksesibilitas kompleks tertangkap
  • Tidak cocok dijadikan blocker untuk semua PR kecil

Karena itu, Lighthouse lebih aman dipakai sebagai:

  • Audit terjadwal di branch utama
  • Cek tambahan untuk halaman prioritas tinggi
  • Pelengkap tren kualitas, bukan satu sumber kebenaran

Contoh alur lokal + CI yang tidak berat

Pre-commit

Tujuan pre-commit adalah menghentikan kesalahan yang murah dideteksi. Jangan jalankan browser test penuh di tahap ini.

  • ESLint termasuk aturan aksesibilitas
  • Hanya file yang berubah
  • Opsional: test komponen ringan bila cepat dan stabil
# contoh konsep dengan file yang berubah
npx eslint --cache src

Jangan menaruh Lighthouse atau suite Playwright penuh di pre-commit. Developer akan mencari cara melewati hook jika waktu tunggu terlalu lama.

Pre-push

Pre-push adalah tempat yang lebih masuk akal untuk smoke test aksesibilitas lokal.

  • Jalankan 2-5 skenario Playwright paling kritis
  • Audit axe pada halaman yang paling sering disentuh atau paling penting secara bisnis
  • Gunakan filter changed paths bila repository cukup besar

Jika tim merasa pre-push masih mengganggu, jadikan ini opsional untuk branch lokal tetapi wajib di CI PR. Yang penting adalah jangan menggandakan biaya secara tidak perlu.

Pipeline CI pull request

Pemisahan job akan memudahkan debugging dan memungkinkan eksekusi paralel.

jobs:
  install:
    steps:
      - checkout
      - install dependencies
      - cache dependencies

  lint_a11y:
    needs: install
    steps:
      - run lint
      - run lint:a11y

  test_unit:
    needs: install
    steps:
      - run unit tests

  a11y_smoke:
    needs: install
    steps:
      - start app
      - run Playwright smoke tests
      - run axe checks on critical routes
      - upload report artifact

  lighthouse_report:
    needs: install
    if: changed public pages or nightly build
    steps:
      - start app
      - run lighthouse on selected pages
      - upload report artifact

Struktur di atas sengaja generik agar bisa diterapkan di GitHub Actions, GitLab CI, CircleCI, atau sistem lain. Intinya bukan nama platform, tetapi pemisahan biaya dan tujuan tiap job.

Aturan gagal/lolos yang realistis

Tanpa aturan yang jelas, tim akan bingung kapan PR harus diblokir dan kapan cukup diberi catatan. Berikut model yang cukup aman untuk banyak tim:

Rule 1: lint aksesibilitas selalu blocker

  • Jika aturan lint terbukti stabil, gagal = merge ditolak
  • Pengecualian harus jarang dan terdokumentasi

Rule 2: axe memblokir hanya untuk severity tertentu

  • critical/serious pada route yang diuji = blocker
  • moderate/minor = warning atau tiket perbaikan

Rule 3: baseline lama tidak langsung memblokir

  • Temuan yang sudah ada sebelum PR tidak otomatis menggagalkan build
  • Regresi baru pada area yang berubah harus gagal

Rule 4: Lighthouse lebih cocok sebagai threshold longgar atau non-blocking report

  • Gunakan untuk halaman prioritas, bukan semua halaman
  • Jangan pakai skor sebagai satu-satunya alasan menolak PR kecil

Catatan: Severity gating bekerja karena tim memisahkan masalah yang benar-benar menghambat penggunaan dari masalah yang perlu dibenahi tetapi tidak harus menghentikan rilis hari itu juga.

Baseline pelanggaran: cara berhenti mewarisi utang tanpa memblokir semua PR

Jika aplikasi sudah lama berjalan, hampir pasti ada pelanggaran aksesibilitas yang belum selesai. Memblokir semua PR sampai seluruh backlog beres biasanya tidak realistis. Solusi yang lebih sehat adalah baseline.

Cara kerja baseline

  1. Jalankan audit pada branch utama dan simpan daftar pelanggaran yang sudah diketahui.
  2. Di PR, bandingkan hasil baru dengan baseline tersebut.
  3. Build gagal jika ada pelanggaran baru atau jika jumlah pelanggaran pada route yang berubah bertambah.
  4. Perbaikan yang mengurangi baseline harus diterima sebagai kemajuan, walaupun belum nol.

Pendekatan ini membantu tim memfokuskan energi pada regresi baru tanpa pura-pura menghapus semua utang teknis sekaligus.

Hal yang perlu diperhatikan

  • Baseline harus diperbarui secara sadar, bukan otomatis setiap build.
  • Simpan baseline per halaman atau per komponen penting jika memungkinkan.
  • Jangan masukkan issue yang berasal dari environment test yang tidak stabil.

Kesalahan umum adalah menjadikan baseline sebagai tempat menyembunyikan masalah baru. Karena itu, update baseline idealnya melalui review PR khusus atau approval dari maintainer frontend.

Mengurangi false positive tanpa menurunkan standar

False positive adalah alasan paling umum mengapa tim mulai mengabaikan guardrail. Beberapa strategi yang biasanya efektif:

Batasi scope audit

Audit seluruh halaman, termasuk area third-party, iklan, atau widget eksternal, sering menghasilkan noise. Lebih baik gunakan scope yang relevan, misalnya container main atau region aplikasi yang memang dimiliki tim.

Pastikan state UI stabil sebelum audit

Audit yang dijalankan saat skeleton loading, modal belum terbuka penuh, atau data belum selesai dimuat akan menghasilkan hasil yang tidak konsisten. Tunggu elemen utama siap sebelum memanggil axe atau Lighthouse.

Jangan audit komponen yang memang sengaja hidden

Beberapa komponen offscreen atau lazy-rendered bisa memicu temuan yang tidak relevan jika belum berada dalam state yang benar. Uji state yang nyata dipakai pengguna.

Dokumentasikan pengecualian yang sah

Bila ada rule yang memang tidak relevan untuk pola tertentu, dokumentasikan alasan dan ruang lingkupnya. Hindari menonaktifkan rule secara global hanya karena satu kasus sulit.

Pisahkan bug produk dari keterbatasan alat

Tidak semua isu aksesibilitas bisa diukur otomatis. Sebaliknya, tidak semua output alat harus dianggap bug produk tanpa verifikasi. Review teknis singkat tetap dibutuhkan untuk kasus ambigu.

Review PR: guardrail manusia tetap dibutuhkan

Checklist CI untuk aksesibilitas frontend tidak lengkap tanpa review PR yang fokus. Otomasi bisa menangkap banyak hal, tetapi belum cukup untuk menilai kualitas interaksi secara utuh.

Tambahkan checklist singkat di template PR untuk perubahan UI:

  • Apakah komponen baru dapat dioperasikan dengan keyboard?
  • Apakah nama tombol, link, dan input jelas?
  • Apakah error form tersambung ke field terkait?
  • Apakah dialog, menu, atau popover mengelola fokus dengan benar?
  • Apakah perubahan ini menyentuh halaman prioritas yang wajib diuji dengan axe/Playwright?

Checklist review sebaiknya pendek. Jika terlalu panjang, reviewer akan melewatinya. Fokus pada pola yang paling sering rusak di codebase Anda.

Rollout bertahap untuk tim yang belum punya guardrail aksesibilitas

Jika tim mulai dari nol, jangan langsung menyalakan semua blocker. Rollout bertahap biasanya lebih berhasil:

Tahap 1: observability dulu

  • Aktifkan lint aksesibilitas sebagai warning
  • Tambahkan audit axe di CI sebagai report artifact
  • Pilih 3-5 route paling penting untuk diukur

Tahap 2: blocker untuk masalah murah dan jelas

  • Naikkan lint aksesibilitas menjadi blocking
  • Gagal jika ada temuan serious/critical baru dari axe pada route prioritas

Tahap 3: baseline dan pengurangan utang

  • Simpan baseline issue lama
  • Blokir regresi baru, bukan seluruh backlog
  • Buat target triwulanan untuk mengurangi baseline

Tahap 4: perluas coverage secara selektif

  • Tambahkan flow keyboard dan focus management di Playwright
  • Jalankan Lighthouse pada halaman publik tertentu
  • Perluas route hanya jika sinyal tetap berkualitas

Rollout seperti ini bekerja karena tim belajar dari data nyata: durasi job, rasio false positive, pola bug yang berulang, dan area aplikasi yang paling rawan.

Contoh kebijakan sederhana yang bisa langsung dipakai

Aturan lokal
- Pre-commit: eslint + a11y lint pada file berubah
- Pre-push: smoke Playwright + axe untuk route kritis

Aturan PR CI
- Gagal jika lint:a11y gagal
- Gagal jika ada violation axe dengan impact serious/critical yang baru
- Warning jika ada moderate/minor
- Lighthouse hanya report untuk PR biasa
- Lighthouse bisa blocking untuk halaman publik prioritas jika tim sudah siap

Aturan main branch
- Jalankan audit lebih luas secara terjadwal
- Review tren dan kurangi baseline secara bertahap

Kesalahan umum yang perlu dihindari

  • Mengukur terlalu banyak route sejak awal. Mulai dari halaman penting.
  • Menggunakan skor sebagai pengganti analisis. Lihat jenis masalah, bukan hanya angka.
  • Memblokir seluruh PR karena utang lama. Gunakan baseline.
  • Mencampur semua cek ke satu job besar. Sulit didiagnosis dan lebih lambat.
  • Tidak menguji keyboard dan fokus. Banyak bug aksesibilitas nyata ada di sini, bukan hanya di atribut ARIA.
  • Menonaktifkan rule terlalu cepat. Verifikasi dulu apakah masalahnya memang false positive atau bug sungguhan.

Penutup

Checklist CI untuk aksesibilitas frontend yang baik bukan tentang menambah sebanyak mungkin alat, melainkan menempatkan cek yang tepat di tahap yang tepat. Linting menangkap masalah murah lebih awal, axe di browser mencegah regresi DOM yang nyata, Playwright memverifikasi perilaku keyboard dan fokus, sedangkan Lighthouse memberi sinyal tambahan tanpa harus membebani setiap PR.

Jika Anda ingin mulai tanpa memperlambat tim, urutan paling aman adalah: aktifkan a11y lint di lokal, jalankan smoke axe di CI untuk route kritis, terapkan severity gating, lalu pakai baseline agar utang lama tidak menghentikan semua pekerjaan baru. Dari situ, perluas coverage hanya ketika sinyal tetap berguna dan pipeline tetap cepat.