Strategi uji kompatibilitas UI untuk port X11 ke platform baru harus berfokus pada perilaku yang mudah rusak tetapi sulit terlihat dari sekadar build yang sukses: input keyboard dan pointer, fokus window, clipboard, resize, shortcut, rendering dasar, dan timing event. Saat aplikasi X11 lama dipindahkan ke platform baru—misalnya ke runtime atau shell baru yang menerjemahkan semantik windowing ke sistem modern—regresi paling mahal biasanya muncul pada interaksi kecil yang dipakai pengguna setiap hari.
Masalahnya bukan hanya “apakah UI tampil”, tetapi apakah perilakunya tetap konsisten saat event loop, sistem fokus, model input, dan pipeline rendering berubah. Karena itu, strategi yang efektif bukan bertumpu pada satu jenis test, melainkan kombinasi kontrak perilaku lintas platform, test pyramid yang realistis, smoke test manual yang singkat tetapi tajam, snapshot yang dipakai secara selektif, dan CI yang memverifikasi beberapa target meski tanpa hardware lengkap.
Mengapa port UI X11 sering gagal di detail kecil
X11 memberi model yang berbeda dari banyak platform modern. Aplikasi lama sering mengasumsikan hal-hal seperti:
- fokus keyboard dapat berpindah dengan aturan tertentu yang tidak identik dengan sistem target,
- koordinat pointer dan event motion datang dengan frekuensi atau urutan yang dapat diprediksi,
- clipboard atau selection bekerja dengan semantik ala X11,
- resize dapat memicu rangkaian expose/configure event tertentu,
- shortcut keyboard dibaca dari keycode atau keysym yang tidak langsung cocok dengan platform baru,
- rendering inkremental aman karena compositor dan refresh model serupa.
Dalam port ke platform baru, bug sering bukan crash total, melainkan gejala seperti:
- tekanan tombol hilang saat fokus berubah cepat,
- shortcut tertentu tidak aktif bila layout keyboard berbeda,
- paste bekerja untuk teks pendek tetapi gagal saat data datang asinkron,
- window tampak benar saat dibuka tetapi rusak setelah resize berulang,
- double-click atau drag salah terdeteksi karena timing event bergeser,
- snapshot visual berubah karena anti-aliasing atau font fallback, bukan karena bug fungsional.
Karena itu, tujuan pengujian bukan sekadar menyalin test lama, tetapi mengunci kontrak perilaku yang benar-benar penting bagi pengguna.
Mendefinisikan kontrak perilaku lintas platform
Sebelum menulis banyak test, tulis dulu behavioral contract: daftar perilaku yang harus tetap benar walau implementasi backend berubah. Ini membantu memisahkan apa yang memang kompatibilitas inti dan apa yang hanya detail implementasi.
Apa itu kontrak perilaku
Kontrak perilaku adalah spesifikasi kecil, dapat diuji, dan netral platform. Fokusnya pada input dan output yang terlihat, bukan pada API internal platform. Misalnya:
- Fokus: setelah window aktif menerima input, tombol dikirim ke widget aktif; setelah dialog modal muncul, input tidak lagi masuk ke view sebelumnya.
- Clipboard: operasi copy menghasilkan teks yang dapat dipaste kembali ke field target tanpa karakter hilang atau urutan berubah.
- Resize: setelah ukuran viewport berubah, konten di-layout ulang dan area gambar tidak memakai ukuran lama.
- Shortcut: kombinasi save, copy, paste, select all, dan escape menghasilkan aksi yang sama terlepas dari representasi keycode di backend.
- Timing event: drag dimulai hanya setelah pointer move melampaui ambang tertentu atau state tombol memang aktif.
Contoh format kontrak yang berguna
Dokumen kontrak tidak perlu formal berlebihan. Yang penting cukup presisi untuk diterjemahkan menjadi unit test, integration test, atau smoke test manual.
Perilaku: fokus keyboard setelah modal dibuka
Pra-kondisi:
- Window utama aktif
- Text field A sedang fokus
Aksi:
- Buka dialog modal lewat shortcut
- Ketik "abc"
Ekspektasi:
- Text field A tidak berubah
- Input masuk ke kontrol pertama di dialog, atau diabaikan jika dialog belum siap
- Menekan Escape menutup dialog dan fokus kembali ke elemen sebelumnyaKontrak seperti ini lebih tahan lama dibanding test yang terlalu dekat ke detail toolkit atau event backend tertentu.
Test pyramid untuk port UI event-driven
Pada aplikasi UI yang dipindahkan dari X11 ke platform baru, test pyramid tetap relevan, tetapi proporsinya perlu disesuaikan. Jangan menaruh semua beban pada end-to-end UI test karena paling lambat dan paling mudah flaky.
1. Unit test untuk adaptor dan translasi event
Lapisan paling bawah sebaiknya menguji fungsi yang menerjemahkan event platform baru menjadi model internal aplikasi. Ini area bernilai tinggi karena banyak regresi input lahir di sini.
Contoh yang layak diuji secara unit:
- mapping keycode/key symbol ke aksi internal,
- normalisasi modifier seperti Shift, Control, Option/Alt, Command,
- konversi koordinat pointer dari ruang layar ke ruang konten,
- deteksi click, double-click, drag, long-press bila ada logika internal,
- state machine fokus atau pointer capture.
Bila arsitektur masih bercampur antara toolkit dan logika aplikasi, buat lapisan adaptor tipis agar translasi event dapat diuji tanpa UI penuh.
// Pseudocode: test translasi event keyboard ke aksi internal
func testShortcutCopy() {
let evt = PlatformKeyEvent(key: .c, modifiers: [.controlOrCommand])
let action = InputTranslator.translate(evt)
assert(action == .copy)
}
func testEscapeClosesModalOnlyWhenModalActive() {
let state = UIState(modalOpen: true)
let evt = PlatformKeyEvent(key: .escape, modifiers: [])
let action = InputTranslator.translate(evt, state: state)
assert(action == .closeModal)
}Nilai utama unit test di sini adalah menjaga kompatibilitas saat backend input berubah atau saat Anda memperbaiki bug platform-spesifik.
2. Integration test untuk kontrak perilaku
Lapisan tengah sebaiknya memverifikasi bahwa event loop, state UI, dan rendering dasar bekerja bersama. Test ini tidak perlu selalu membuka UI penuh dengan interaksi manusia; cukup jalankan aplikasi dalam mode test harness bila memungkinkan.
Target integration test yang efektif:
- fokus berpindah antarkontrol sesuai urutan tab,
- resize memicu relayout dan redraw,
- copy/paste round-trip pada field teks,
- dialog modal memblokir input ke view belakang,
- shortcut menghasilkan command yang benar pada state yang tepat.
Jika port Anda memakai layer kompatibilitas X11 atau server rendering terpisah, integration test dapat mengunci batas antarproses: kapan window dibuat, kapan ready menerima input, bagaimana notifikasi ukuran dikirim, dan kapan frame pertama dianggap selesai.
3. End-to-end UI test secukupnya
UI end-to-end tetap penting, tetapi pilih skenario yang paling representatif dan paling rentan regresi:
- buka aplikasi dan tampilkan window utama,
- fokus field lalu ketik teks,
- copy/paste,
- resize window atau viewport,
- buka dan tutup dialog,
- jalankan satu shortcut kritikal,
- verifikasi aplikasi tidak hang setelah rangkaian event itu.
Jangan mencoba menguji semua kombinasi UI lewat end-to-end. Gunakan lapisan ini untuk memastikan integrasi antarsubsistem benar-benar hidup.
Area regresi yang wajib diuji
Input keyboard dan shortcut
Pada port X11, asumsi lama tentang key symbol dan modifier sering pecah. Uji minimal:
- huruf, angka, backspace, enter, escape, tab, panah, delete,
- shortcut umum: copy, paste, cut, select all, save, close, undo bila ada,
- kombinasi modifier tunggal dan gabungan,
- perilaku saat key repeat aktif,
- layout keyboard berbeda jika aplikasi dipakai lintas locale.
Kesalahan umum adalah menguji shortcut hanya pada keyboard US dan mengikat logika ke keycode fisik, padahal yang dibutuhkan adalah aksi logis.
Fokus dan active window
Bug fokus terasa kecil, tetapi efeknya besar. Verifikasi:
- elemen awal yang menerima fokus saat window muncul,
- urutan tab, shift-tab, dan fokus saat modal dibuka/ditutup,
- klik pada area nonaktif tidak membuat input masuk ke elemen salah,
- setelah aplikasi kehilangan fokus dan aktif lagi, state input tetap konsisten.
Jika platform baru memiliki konsep scene, space, atau lifecycle yang berbeda dari desktop biasa, uji skenario suspend/resume atau reactivation bila relevan.
Clipboard dan selection
X11 historically memiliki perbedaan antara clipboard dan selection. Platform baru mungkin tidak. Maka kontraknya harus berdasarkan perilaku pengguna, bukan semantik internal lama.
- copy dari field A lalu paste ke field B,
- copy string panjang dan karakter non-ASCII,
- paste berulang setelah fokus pindah,
- paste ketika sumber aplikasi sudah tidak aktif.
Hindari test yang mengasumsikan detail implementasi clipboard backend kecuali aplikasi memang bergantung padanya.
Rendering dan resize
Untuk rendering, yang penting bukan setiap piksel identik pada semua target. Yang lebih penting:
- konten tampil lengkap tanpa area kosong yang salah,
- resize tidak meninggalkan frame lama atau clipping salah,
- layer overlay, cursor, atau selection highlight tetap sinkron,
- redraw terjadi setelah invalidasi yang relevan.
Banyak bug resize berasal dari race antara notifikasi ukuran baru, layout, dan render frame berikutnya.
Timing event dan urutan event
Ini sumber flaky test paling umum. Misalnya, sistem target mengirim focus event setelah frame pertama, atau pointer up tiba sesudah callback tertentu. Uji:
- click vs drag,
- double-click threshold,
- key repeat,
- event yang datang saat redraw berlangsung,
- urutan fokus → input → render untuk interaksi awal.
Kalau Anda tidak menguji timing sama sekali, banyak bug baru akan lolos sampai pengguna nyata menemukannya.
Smoke test manual yang efektif dan singkat
Untuk port UI ke platform baru, smoke test manual masih sangat berguna, terutama bila hardware target terbatas. Kuncinya adalah membuatnya pendek, berulang, dan fokus pada risiko tertinggi.
Daftar smoke test 5-10 menit
- Buka aplikasi dan pastikan window atau surface utama muncul tanpa artefak besar.
- Verifikasi fokus awal; ketik teks pada kontrol utama.
- Jalankan copy, paste, select all.
- Gunakan tab dan shift-tab untuk pindah fokus.
- Buka dialog atau menu; pastikan input tidak bocor ke view belakang.
- Resize atau ubah skala viewport beberapa kali.
- Coba satu drag sederhana dan satu double-click.
- Aktifkan shortcut penting.
- Background/foreground aplikasi bila platform mendukung, lalu uji input lagi.
- Tutup aplikasi dengan jalur normal.
Daftar ini sengaja pendek agar benar-benar dijalankan pada setiap build kandidat, bukan hanya saat demo besar.
Apa yang dicatat saat smoke test
Jangan hanya menulis “lulus/gagal”. Catat konteks minimal:
- target platform dan mode eksekusi,
- backend rendering atau shell yang dipakai,
- apakah input datang dari keyboard fisik, virtual, atau simulator,
- langkah terakhir sebelum bug muncul,
- apakah bug stabil atau intermiten.
Data kecil seperti ini sangat membantu membedakan bug logika dari bug timing atau bug perangkat tertentu.
Snapshot test: kapan aman dipakai, kapan menyesatkan
Snapshot visual berguna, tetapi pada port UI lintas platform ia mudah menghasilkan false positive. Font fallback, anti-aliasing, skala pixel, dan compositor dapat mengubah tampilan tanpa mengubah perilaku fungsional.
Snapshot yang aman dipakai
Gunakan snapshot untuk area yang cukup stabil dan deterministik:
- layout statis komponen tanpa animasi,
- state kosong, state error, state selected,
- struktur visual yang penting seperti toolbar, panel, atau dialog sederhana,
- rendering widget custom dengan font dan skala yang dikendalikan.
Jika memungkinkan, snapshot diambil dari lapisan rendering internal atau view subtree tertentu, bukan seluruh layar.
Snapshot yang sebaiknya dihindari
- animasi, cursor, blink caret, dan elemen berbasis waktu,
- konten yang tergantung font sistem, locale, atau densitas perangkat,
- surface penuh yang dipengaruhi compositor eksternal,
- hasil render setelah event asinkron tanpa sinkronisasi eksplisit.
Teknik agar snapshot lebih stabil
- pakai font test yang tetap bila memungkinkan,
- matikan animasi dan caret blink dalam mode test,
- set ukuran viewport tetap,
- tunggu kondisi siap yang eksplisit, bukan sleep tetap,
- snapshot per komponen atau region, bukan keseluruhan desktop.
Prinsip praktis: snapshot cocok untuk regresi layout dan rendering statis, bukan sebagai alat utama untuk membuktikan fokus, clipboard, shortcut, atau timing event.
Mengurangi flaky test pada aplikasi UI/event-driven
Flaky test hampir pasti muncul saat mem-port aplikasi event-driven. Penyebab utamanya biasanya race condition, sinkronisasi yang lemah, dan ketergantungan pada waktu absolut.
1. Ganti sleep dengan kondisi siap
Alih-alih menunggu 500 ms lalu mengirim input, tunggu sinyal yang benar-benar berarti, misalnya:
- window sudah dibuat,
- frame pertama selesai dirender,
- fokus aktif sudah diberikan,
- clipboard callback selesai,
- event queue idle setelah aksi tertentu.
// Pseudocode: tunggu kondisi siap, bukan sleep tetap
waitUntil(timeout: 2.0) {
app.mainWindow.isVisible && app.mainWindow.hasFocusedElement
}
sendKeys("abc")
assert(textField.value == "abc")Pendekatan ini bekerja karena Anda menyinkronkan test dengan state aplikasi, bukan dengan tebakan durasi mesin.
2. Buat event injection deterministik
Jika harness test dapat mengirim event ke lapisan internal, lebih baik daripada bergantung penuh pada automasi OS. Event injection internal memungkinkan Anda:
- mengontrol urutan event,
- mengisolasi bug translasi dari bug automasi eksternal,
- merekam log event sebelum dan sesudah translasi.
Namun, tetap simpan sejumlah kecil test yang lewat jalur input nyata untuk memverifikasi integrasi end-to-end.
3. Pisahkan assert perilaku dari assert visual
Satu test yang mencoba memverifikasi fokus, teks, dan screenshot sekaligus akan sulit didiagnosis saat gagal. Pisahkan:
- test perilaku untuk state internal atau output logis,
- snapshot untuk layout atau tampilan statis.
Dengan begitu, sumber kegagalan lebih jelas dan rerun tidak perlu mahal.
4. Rekam trace event saat test gagal
Untuk bug fokus dan timing, log event jauh lebih berguna daripada screenshot. Simpan trace seperti:
- waktu event masuk,
- jenis event,
- target fokus saat itu,
- hasil translasi aksi,
- perubahan state modal/selection/window size.
[00.000] window-created id=main
[00.120] first-frame-rendered id=main
[00.130] focus-changed target=textFieldA
[00.140] key-down key=V modifiers=Command
[00.141] action=paste
[00.170] clipboard-read completed length=12
[00.175] text-changed target=textFieldA length=12Trace semacam ini sering cukup untuk menemukan race tanpa harus membuka debugger grafis.
5. Kendalikan sumber nondeterminisme
- nonaktifkan animasi nonesensial di mode test,
- bekukan jam atau gunakan test clock bila logika bergantung waktu,
- gunakan data fixture lokal, bukan resource jaringan,
- kunci ukuran viewport, locale, dan skala jika memungkinkan,
- hindari dependensi pada urutan thread kecuali memang diuji.
Workflow verifikasi yang praktis untuk port X11
Tim sering gagal karena testing dilakukan terlambat, setelah fitur port terlihat “cukup jalan”. Lebih aman membuat workflow verifikasi bertahap sejak awal.
Tahap 1: baseline perilaku di platform lama
Sebelum port terlalu jauh, rekam baseline dari aplikasi asli atau backend lama:
- video pendek interaksi inti,
- trace event untuk skenario penting,
- daftar shortcut dan perilaku fokus,
- contoh hasil rendering untuk state tertentu.
Anda tidak perlu mengidealkan platform lama; cukup dokumentasikan perilaku yang memang dipakai pengguna.
Tahap 2: harness test lintas backend
Bila memungkinkan, jalankan test kontrak yang sama terhadap backend lama dan backend baru. Tujuannya bukan menuntut piksel sama, tetapi mendeteksi perbedaan perilaku yang tidak disengaja.
Contoh abstraksi harness:
interface UITestDriver {
launch()
focus(elementId)
typeText(text)
pressShortcut(name)
resize(width, height)
copy()
paste()
snapshot(regionId)
getValue(elementId)
}Dengan driver semacam ini, satu skenario dapat dipakai ulang pada beberapa target.
Tahap 3: gate CI berbasis risiko
Jangan menjadikan semua test sebagai syarat setiap commit bila biaya terlalu tinggi. Buat lapisan gate:
- per commit: build, unit test adaptor input, integration test cepat, lint, static analysis, snapshot terbatas;
- per pull request: smoke automation yang membuka aplikasi dan menjalankan alur inti;
- nightly: matriks target lebih luas, skenario resize berulang, trace event, snapshot tambahan;
- release candidate: smoke test manual di target paling mendekati produksi.
Ini menjaga feedback tetap cepat tanpa mengorbankan cakupan risiko utama.
Checklist CI untuk build verifikasi multi-target
Berikut checklist yang realistis untuk port UI lintas platform, termasuk saat hardware target terbatas.
Checklist pipeline
- Build semua target yang didukung dari source yang sama.
- Jalankan unit test untuk translasi input, shortcut, fokus, dan utilitas geometry.
- Jalankan integration test headless atau semi-headless bila backend mendukung.
- Jalankan smoke automation pada minimal satu environment representatif.
- Simpan artifact screenshot, trace event, dan log aplikasi untuk test gagal.
- Pastikan mode test mematikan animasi nonesensial dan fitur nondeterministik.
- Verifikasi symbol/debug info tersedia untuk analisis crash.
- Jalankan static analysis atau sanitizer jika kompatibel dengan target build.
- Bedakan failure karena lingkungan test dari failure fungsional dengan label yang jelas.
- Publikasikan matriks hasil per target, bukan hanya status lulus/gagal tunggal.
Contoh struktur job CI
jobs:
- build-core
- unit-input-translation
- unit-focus-state
- integration-ui-contracts
- snapshot-stable-components
- smoke-main-flow-simulator
- nightly-resize-stress
- nightly-event-trace-diffFormat job bisa berbeda tergantung sistem CI, tetapi pemisahan tanggung jawab seperti ini memudahkan diagnosis.
Matriks uji yang realistis tanpa hardware penuh
Sering kali tim tidak punya akses konstan ke seluruh perangkat target, terutama untuk platform baru. Solusinya adalah membuat matriks uji berlapis, bukan mencoba mereplikasi produksi 100% di setiap commit.
Dimensi matriks yang paling berguna
- Backend/platform: backend lama, backend baru, simulator bila ada.
- Mode rendering: software path vs accelerated path jika aplikasinya memiliki keduanya.
- Input mode: keyboard fisik, input sintetis/harness, pointer emulasi.
- Viewport: ukuran kecil, default, dan resize ekstrim yang masih valid.
- Locale/layout: minimal satu layout default dan satu yang berpotensi memengaruhi shortcut atau input teks.
Contoh matriks minimum
Tanpa hardware penuh, matriks berikut sudah cukup berguna:
- Setiap commit: build backend lama + backend baru, unit dan integration test cepat.
- Setiap PR: simulator atau desktop harness untuk alur input, fokus, clipboard, resize.
- Nightly: kombinasi viewport dan locale tambahan, snapshot stabil, resize stress, event trace diff.
- Mingguan atau sebelum rilis: satu sesi smoke manual pada hardware nyata atau environment paling mendekati target.
Jangan memaksa simulator untuk memvalidasi semua hal. Beberapa bug input, performa, dan lifecycle memang hanya muncul di perangkat nyata. Karena itu, gunakan simulator untuk frekuensi tinggi dan hardware nyata untuk validasi risiko akhir.
Kesalahan umum saat menguji port UI lama
- Terlalu mengandalkan screenshot penuh sehingga test sering gagal karena perbedaan rendering kecil yang tidak relevan.
- Tidak mendefinisikan kontrak perilaku, akibatnya tim berdebat apakah suatu perubahan adalah bug atau penyesuaian platform.
- Mengikat test ke timing tetap dengan sleep, lalu menyalahkan CI saat flaky.
- Menguji hanya jalur mouse atau hanya jalur keyboard, padahal regresi fokus dan shortcut sering terpisah.
- Tidak membandingkan dengan baseline platform lama, sehingga kompatibilitas menurun tanpa disadari.
- Menaruh semua verifikasi pada perangkat nyata yang langka, sehingga feedback terlalu lambat.
Penutup
Port aplikasi UI lama seperti X11 ke platform baru hampir selalu menimbulkan mismatch semantik pada input, fokus, clipboard, resize, shortcut, rendering, dan urutan event. Strategi uji kompatibilitas UI yang efektif bukan mengejar cakupan visual sebesar mungkin, tetapi mengunci kontrak perilaku lintas platform lalu menurunkannya ke unit test adaptor, integration test untuk state dan event loop, serta end-to-end dan smoke test secukupnya.
Jika Anda hanya mengambil satu langkah, mulailah dari daftar perilaku inti yang benar-benar dipakai pengguna: fokus, ketik, copy/paste, resize, shortcut, dan dialog modal. Setelah itu, bangun harness yang dapat menjalankan skenario yang sama pada backend lama dan baru, gunakan snapshot hanya untuk area stabil, dan hilangkan flaky test dengan sinkronisasi berbasis kondisi siap. Dengan pendekatan itu, port UI tidak hanya “jalan”, tetapi juga tetap dapat dipercaya saat dipakai nyata.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!