Skip to content

Selektor Otomasi Tiba-tiba Gagal Saat UI Berubah Bahasa

Adityo Guni Waluyo

Profil browser ber-locale id-ID merender Tanya Qwen dan Salin, dan selector exact match langsung gagal. Pola regex alternation plus penguncian locale menyelesaikannya.

Ringkasan

Skrip otomasi gua error gara-gara profil browser kebetulan pakai locale id-ID, jadi tombolnya dirender "Tanya Qwen" bukan "Ask Qwen", dan selector exact match langsung gagal. Solusinya pakai regex alternation kayak (?:Ask|Tanya) biar dua bahasa kecakup, plus kunci locale browser. Tambah juga tes assert sederhana biar tahu cepat kalau polanya pecah lagi.

Saya sedang menjalankan skrip otomasi pagi itu seperti biasa untuk memproses pipeline artikel. Tiba-tiba, prosesnya berhenti di tengah jalan. Log konsol menunjukkan error bahwa elemen tombol tidak ditemukan, diikuti laporan palsu bahwa sesi pengguna sudah logout. Padahal saya yakin tidak ada perubahan kode di sisi saya sejak terakhir kali skrip ini berjalan lancar. Saya awalnya menduga aplikasi target baru saja merilis update besar yang mengubah struktur DOM atau kelas CSS mereka secara menyeluruh.

Saya menghabiskan waktu lumayan lama untuk ngecek inspeksi elemen secara manual: mencari perubahan selector yang mungkin terlewat, memeriksa network tab untuk redirect yang tidak terduga, bahkan menjalankan ulang skrip dengan timeout lebih panjang. Tidak ada yang berhasil. Semua petunjuk mengarah pada kegagalan standar, tapi akar masalahnya tetap tersembunyi di balik lapisan abstraksi yang tidak saya duga.

Setelah membuka browser dengan mode headless dimatikan dan menjalankan skrip sambil mengamati, saya akhirnya melihat kenyataan yang berbeda. Antarmuka tidak berubah strukturnya sama sekali. Atribut HTML tetap, kelas CSS masih sama, tetapi teks yang ditampilkan di layar berbeda. Alih-alih tombol berlabel "Ask Qwen" atau "Copy" seperti yang saya harapkan, yang muncul adalah "Tanya Qwen" dan "Salin".

Ternyata, profil browser yang saya pakai untuk otomasi terkonfigurasi dengan locale id-ID. Situs target membaca preferensi bahasa dari profil tersebut dan memutuskan merender antarmuka dalam bahasa Indonesia. Selector lama saya hanya cocok dengan string bahasa Inggris, sehingga langkah kirim atau salin langsung gagal total. Ini bukan bug pada alat otomasinya, melainkan konsekuensi alami dari mengandalkan teks statis di antarmuka yang dinamis.

Mengapa Selector Exact Match Rentan Pecah

Di sinilah pemahaman tentang bagaimana alat otomasi membaca accessibility tree menjadi penting. Saat mengambil aria snapshot, hasilnya bukan HTML mentah, melainkan representasi YAML dari pohon aksesibilitas halaman. Format dasarnya seperti ini: - role "name" [attribute=value] [3].

Sebagai ilustrasi, sebelum perubahan locale, snapshot menampilkan baris seperti - textbox "Ask Qwen" [e9]. Setelah profil browser beralih ke locale Indonesia, baris yang sama berubah menjadi - textbox "Tanya Qwen" [e9].

Bagian name di sini adalah accessible name elemen, teks yang dibaca oleh screen reader. Aturan pencocokannya ketat. String yang diapit tanda kutip biasa dicari sebagai nilai yang tepat sama persis atau exact value; jika ingin fleksibel, kita pakai pola regular expression yang diapit garis miring seperti /pattern/ [3].

Karena selector lama saya melakukan exact match terhadap "Ask Qwen", sistem langsung gagal begitu nama aksesibelnya berubah menjadi "Tanya Qwen". Banyak developer salah kira bahwa struktur HTML yang sama berarti teks di dalamnya juga akan tetap sama. Padahal aplikasi modern sering menyesuaikan bahasa secara otomatis berdasarkan sinyal dari browser itu sendiri. Mengandalkan teks statis tanpa mempertimbangkan kemungkinan perubahan bahasa adalah resep kegagalan yang sulit ditebak, terutama saat mengotomasi aplikasi pihak ketiga yang tidak kita kendalikan.

Solusi: Pola Alternation dan Emulasi Locale

Ada dua lapisan perbaikan yang saya terapkan. Saya nggak mau kejadian yang sama terulang cuma karena settingan browser yang berubah diam-diam atau update kecil dari penyedia layanan.

Lapisan pertama, mengubah pola pencocokan label agar menerima dua bahasa sekaligus memakai alternation regex. Pola untuk textbox tanya saya ubah menjadi (?:Ask|Tanya) Qwen. Saya pakai non-capturing group (?:...) agar mesin regex tidak menyimpan hasil match yang tidak perlu. Hal yang sama saya terapkan pada aksi lain: (?:Copy|Salin) untuk tombol salin dan (?:Send|Kirim) untuk kirim. Dengan begini, skrip tetap menemukan elemen yang tepat, entah antarmuka sedang merender bahasa Inggris atau Indonesia.

# accept both UI locales: en-US "Ask Qwen" / id-ID "Tanya Qwen"
ref = find_ref(s, r'- textbox "(?:Ask|Tanya) Qwen"[^\[]*\[(e\d+)\]') or \
      find_ref(s, r'- textbox "(?!Search)[^"]*"[^\[]*\[(e\d+)\]')

Lapisan kedua, mengontrol sumber masalahnya langsung: locale browser. Bahasa antarmuka browser ditentukan properti navigator.language, yang mengikuti format standar BCP 47 seperti en-US atau id-ID [2].

Locale bisa dikunci lewat emulasi context saat inisialisasi browser. Misalnya new_context(locale='de-DE') memaksa browser berperilaku seolah berada di lingkungan berbahasa Jerman [4]. Dengan mengunci locale di level profil, variabel yang bisa mengubah teks UI diam-diam hilang. Saya pribadi lebih memilih mengunci locale ke satu bahasa konsisten, biasanya Inggris, untuk semua skrip produksi. Supaya perilaku aplikasi selalu bisa diprediksi dan saya tidak perlu menulis regex yang makin rumit untuk mencakup lima atau enam bahasa sekaligus.

Self-Check untuk Mencegah Regresi

Mengubah selector saja tidak cukup memberi rasa aman jangka panjang. Saya butuh jaminan pola baru ini benar-benar bekerja di kedua kondisi bahasa tanpa harus menjalankan seluruh skrip otomasi utama yang berat setiap kali ada perubahan kecil.

Solusinya file tools/test_qwen_labels.py: 12 pernyataan assert yang menguji pola regex terhadap snapshot aksesibilitas dua locale, tanpa dependensi eksternal. Tes ini mengecek kecocokan pola pada snapshot bahasa Inggris, kecocokan pada bahasa Indonesia, memastikan pola tidak cocok dengan teks acak, dan memverifikasi non-capturing group tidak mengganggu hasil match. Konteks browser yang terisolasi per sesi otomasi [1] membuat hasil tes ini stabil, bebas dari sisa state sesi sebelumnya. Arah ini sejalan dengan rekomendasi resmi Playwright: locator yang tahan banting mengutamakan atribut user-facing dan kontrak eksplisit seperti role, lalu accessible name dipakai untuk mempertajam sasaran [5].

COPY_RE = r'- button "(?:Copy|Salin)"[^\[]*\[(e\d+)\]'
SNAP_ID = ('- textbox "Cari Obrolan" [e3]\n- textbox "Tanya Qwen" [e9]\n'
           '- button "Kirim" [e13]\n- button "Salin" [e12]')
assert find_ref(SNAP_ID, COPY_RE) is None  # tombol salin, bukan textbox
assert 'textbox "Tanya Qwen"' in SNAP_ID   # _ui_alive() tetap hidup

Jika ada perubahan mendadak di sisi aplikasi target, tes lokal ini gagal lebih dulu. Peringatan dini sebelum skrip produksi benar-benar rusak di tengah malam saat tidak ada yang memantau log. Dengan kombinasi pola regex yang toleran dan kontrol locale yang ketat, skrip otomasi jadi jauh lebih stabil dan nggak panik tanpa alasan yang jelas.

Sumber

Artikel terkait