Tiga Interaksi yang Diuji Tanpa Menulis Satu Baris Data
Menguji validasi form kosong, tombol kembali, dan filter tanpa menulis satu baris data; plus dua jebakan locator Playwright dan portal Radix.
Ringkasan
Testing form editor CMS ternyata nggak harus selalu siapin data; tiga interaksi paling rawan—validasi form kosong, tombol kembali, dan filter—bisa diuji tanpa nyentuh database sama sekali. Dua kegagalan locator juga muncul: tombol icon-only tanpa role butuh locator aria-label, dan select berbasis portal nggak bisa pakai selectOption. Test tanpa mutasi kayak gini bisa jalan di environment mana pun tanpa risiko dan cleanup.
Suite test untuk form editor CMS yang dipakai lima module berbeda baru punya satu level: smoke test yang cuma memastikan halaman terbuka. Saya berhenti di layar editor kosong itu dengan pertanyaan yang ternyata salah: data apa yang harus saya buat dulu supaya bisa menguji interaksinya?
Dugaan awal saya, menguji interaksi selalu berarti menyiapkan data. Klik tombol simpan untuk memunculkan pesan validasi, ya harus ada isian dulu. Membatalkan edit, ya harus ada baris yang bisa diedit. Mengubah filter, ya harus ada data yang bisa disaring. Logikanya terdengar rapi sampai saya coba satu per satu dan menemukan bahwa tiga interaksi yang paling sering rusak justru bisa diuji pada form yang benar-benar kosong, tanpa menyentuh database sama sekali.
Tiga itu: pesan validasi saat form kosong disubmit, tombol kembali yang harus mengembalikan ke daftar, dan perubahan filter yang mengubah daftar yang terlihat. Level ini saya sebut L2 di suite saya — di atas smoke test, di bawah vertical test (L3) yang benar-benar menulis dan membersihkan data. Karena modul CMS editor ini dipakai lima module, saya mengambil satu module paling kecil sebagai representatif: kalau pola interaksinya lolos di sana, module lain tinggal menyalin struktur testnya.
Dua jebakan di locator dan DOM portal
Dua kali test gagal bukan karena aplikasinya rusak, tapi karena asumsi saya tentang DOM-nya salah.
Yang pertama: tombol kembali di form itu icon-only, punya aria-label="Kembali ke daftar", tapi tidak punya role button. Playwright tetap menolak locator standar.
// ini tidak menemukan apa pun
await page.getByRole('button', { name: 'Kembali ke daftar' }).click();getByRole mencocokkan role ARIA eksplisit maupun implisit, bukan teks yang kebetulan ada di atribut [5]. Kalau elemennya tidak punya role button, permintaan getByRole('button', ...) tidak menemukan elemen apa pun dan test berhenti di timeout. Solusinya: cari lewat atributnya sendiri.
await page.locator('[aria-label="Kembali ke daftar"]').click();
await expect(page).toHaveURL('/admin/artikel');
await expect(page.getByTestId('tambah-entri')).toBeAttached();Baris terakhir itu yang menjaga test tetap jujur: tombol tambah di halaman daftar harus masih ada setelah kembali, artinya navigasinya benar-benar selesai, bukan setengah jalan.
Yang kedua lebih menyebalkan. Filter di halaman daftar memakai komponen Select ala shadcn, daftar opsi yang muncul dari sebuah tombol pemicu [7]. Opsinya tidak berada di dalam biasa, melainkan di-render lewat portal ke document body [4]. Jadi selectOption() pasti gagal, method itu hanya bekerja pada elemen native dan melempar error kalau targetnya bukan [8].
// gagal: elemennya bukan <select> native
await page.getByTestId('filter-kategori').selectOption('Tutorial');Pola yang benar: klik trigger-nya, tunggu konten portal muncul, lalu klik option berdasarkan role di dalamnya.
await page.getByRole('combobox', { name: 'Kategori' }).click();
await page.getByRole('option', { name: 'Tutorial' }).click();
await expect(page.getByRole('option')).toHaveCount(0);
await expect(page.getByTestId('baris-daftar')).toHaveCount(2);Begitu paham bahwa Radix memindahkan subtree-nya ke document body, kegagalannya berhenti terasa acak. Yang salah bukan timing test, tapi model DOM di kepala saya.
Tes yang menyentuh nol baris
Sisa dua interaksi berjalan tanpa kejutan begitu lokatornya benar. Submit form kosong cukup memakai expect(...).toBeVisible() untuk memastikan pesan validasi muncul tanpa pernah membuat record. Filter cukup membandingkan jumlah data-testid baris daftar sebelum dan sesudah klik option, lagi-lagi tanpa insert apa pun.
Ini yang membuat L2 layak dipisah sebagai level sendiri. Test yang tidak melakukan mutasi bisa dijalankan terhadap environment mana pun — lokal, staging, bahkan produksi kalau terpaksa — tanpa risiko meninggalkan jejak dan tanpa skrip cleanup. Test L3 yang menulis data memang lebih kuat, tapi harganya: fixture, teardown, dan kepercayaan bahwa cleanup-nya benar-benar jalan. Kalau tiga interaksi paling rapuh sudah tervalidasi di L2, jalur L3 tinggal fokus pada alur yang benar-benar butuh data.
Saya sempat berpikir suite ini kurang "nyata" karena tidak ada yang ditulis. Setelah dua kegagalan locator di atas muncul lebih dulu di L2 dan bukan di L3 yang lambat, keraguan itu hilang. Test yang membaca sudah menemukan cukup banyak masalah selama belum ada yang menyentuh database.
Sources
4. Select - Radix UI Primitives 5. Locators - Playwright 7. Select - shadcn/ui 8. Locator - Playwright