Test Playwright yang Andal - Membuat Kegagalan Browser Menjelaskan Penyebabnya
Sebuah browser test lolos di laptop, gagal di continuous integration, lalu lolos lagi ketika dicoba ulang. Apakah aplikasinya rusak, test-nya terlalu tidak sabar, atau layanan eksternal sempat menghilang? Menambahkan jeda yang lebih panjang mungkin menghilangkan hasil merah, tetapi tidak menjawab pertanyaan itu.
Browser automation yang andal bukan sekadar membuat setiap proses berwarna hijau, melainkan membuat setiap kegagalan bermakna. Playwright menyediakan locator, pemeriksaan actionability, assertion yang mencoba ulang, browser context yang terisolasi, dan trace untuk tujuan tersebut. Namun, tidak satu pun fitur itu dapat menyelamatkan test dengan ekspektasi yang salah atau data yang tidak terkendali. Test tetap membutuhkan kontrak yang jelas dengan interface.
Panduan ini membahas kontrak tersebut untuk proyek web kecil. Contohnya menggunakan Playwright Test dengan Node.js, tetapi bersifat ilustratif dan bukan laporan hasil dari aplikasi tertentu.
Pertama, kenali apa yang sebenarnya tidak stabil
Istilah "flaky" sering dipakai sebagai satu wadah untuk beberapa masalah yang berbeda. Memisahkannya akan mengubah respons yang tepat:
- Defect pada produk: sebuah tombol kadang tertutup overlay, request berpacu dengan navigasi, atau data yang disimpan benar-benar tidak konsisten.
- Defect pada test: script menargetkan tombol ketiga hanya karena kebetulan berada di urutan ketiga, memeriksa state sebelum UI diperbarui, atau bergantung pada test sebelumnya.
- Defect pada environment: test server kehabisan resource, proses browser crash, atau DNS tidak tersedia.
- Dependency yang tidak terkendali: test menunggu halaman, API, banner, atau akun pihak ketiga yang state-nya tidak dapat ditentukan oleh proyek.
Menaikkan timeout secara menyeluruh memperlakukan keempatnya sebagai "terlalu lambat". Cara itu praktis, tetapi buktinya lemah. Pertanyaan awal yang lebih berguna adalah: kondisi apa yang dapat diamati dan diharapkan oleh test, serta bukti apa yang menjelaskan mengapa kondisi itu tidak ada?
Pilih locator sebagai kontrak interface
Dokumentasi locator Playwright merekomendasikan atribut yang menghadap pengguna dan kontrak eksplisit. Locator dievaluasi ketika sebuah action atau assertion menggunakannya. Karena itu, locator dapat menemukan elemen terkini setelah re-render alih-alih terus memegang DOM node yang lebih lama.
Untuk form biasa, test dapat berbicara dengan istilah yang sama seperti pembaca:
await page.getByLabel('Email address').fill('[email protected]');
await page.getByRole('button', { name: 'Subscribe' }).click();
await expect(page.getByRole('status')).toHaveText('Subscription saved');
Locator tombol tersebut memakai role dan accessible name. Panduan W3C tentang accessible name menjelaskan bahwa nama menyampaikan tujuan sebuah elemen dan membedakannya dari elemen serupa. Panduan itu juga merekomendasikan teks yang terlihat dan teknik penamaan HTML native jika memungkinkan. Hal tersebut membuat role locator bernilai lebih dari sekadar syntax: ketika label yang terlihat berubah, test kemungkinan ikut menyadari perubahan yang terlihat oleh pengguna.
Namun, hal ini tidak menjadikan test berbasis role sebagai accessibility audit. Test tersebut hanya menguji satu bagian dari semantic accessibility yang diekspos halaman.
Ketika test ID menjadi kontrak yang lebih jelas
Locator yang menghadap pengguna tidak selalu bebas ambiguitas. Interface terjemahan mungkin sengaja memakai label berbeda, control yang hanya berupa icon memerlukan penamaan cermat, dan dua control yang valid mungkin memiliki teks sama. Playwright menjelaskan test ID sebagai kontrak eksplisit yang tahan perubahan, sambil mengingatkan bahwa test ID tidak menghadap pengguna.
<button data-testid="save-profile">Save</button>
await page.getByTestId('save-profile').click();
Trade-off-nya perlu dinyatakan dengan jelas. Test ID dapat tetap stabil saat copy dan layout berubah, yang berguna ketika perubahan itu tidak relevan bagi skenario. Sebaliknya, test ID tidak akan mengungkap bahwa label yang terlihat menjadi menyesatkan. Tidak ada pemenang universal; locator harus sesuai dengan perilaku yang dijanjikan untuk dilindungi oleh test.
Rangkaian CSS atau XPath yang panjang biasanya menjadi kontrak yang lebih buruk karena mengodekan container, class, dan posisi yang tidak dipedulikan pengguna. Method seperti nth() juga dapat diam-diam menunjuk item berbeda setelah sebuah list berubah. Semuanya tersedia untuk kasus yang tidak biasa, tetapi kemudahan tidak boleh disamakan dengan identitas.
Biarkan actionability menggantikan tebakan waktu
Sebelum click() biasa pada locator, Playwright menjalankan pemeriksaan actionability yang terdokumentasi. Playwright menunggu locator menghasilkan tepat satu elemen serta memastikan elemen itu terlihat, stabil, aktif, dan dapat menerima event. Jika kondisi tersebut tidak terpenuhi sebelum timeout yang berlaku, action akan gagal.
Perilaku itu membuat jeda tetap seperti waitForTimeout(2000) menjadi default yang buruk. Dua detik mungkin terlalu lama pada satu proses, tetapi masih terlalu singkat pada proses lain. Lebih penting lagi, berlalunya waktu jarang menjadi kondisi bisnis yang dibutuhkan test.
Menunggu response, URL, state tombol, pesan status, atau outcome lain yang dapat diamati akan menjelaskan alasan penantian. Cara itu juga memungkinkan operasi selesai segera setelah kondisi terpenuhi.
Opsi force perlu diperlakukan dengan kehati-hatian serupa. Opsi ini menonaktifkan pemeriksaan actionability yang tidak esensial untuk action yang mendukungnya. Hal itu dapat membantu pada interaksi yang sengaja dibuat tidak biasa, tetapi memaksa click dengan sembarangan bisa menyembunyikan fakta bahwa pengguna nyata tidak dapat melakukan click karena elemen lain menutupi target.
Periksa outcome, bukan satu momen
Action dan assertion menyelesaikan dua sisi berbeda dalam sebuah skenario. Click menyatakan apa yang dilakukan pengguna; assertion menyatakan apa yang pada akhirnya harus ditampilkan aplikasi. Assertion asynchronous khusus web milik Playwright memeriksa kondisi berulang kali sampai lolos atau mencapai assertion timeout.
// Timing-sensitive snapshot: it checks only the current moment.
expect(await page.getByText('Subscription saved').isVisible()).toBe(true);
// Web-first assertion: it waits for the observable outcome.
await expect(page.getByText('Subscription saved')).toBeVisible();
Bentuk kedua tidak memperlemah ekspektasi. Bentuk itu tetap gagal jika pesan tidak pernah muncul; ia hanya mengakui bahwa browser dan aplikasi berjaringan memperbarui diri secara asynchronous. await yang hilang juga penting karena assertion yang mencoba ulang bersifat asynchronous.
Timeout tetap diperlukan sebagai batas. Timeout mencegah test menunggu selamanya. Namun, timeout yang lebih besar bukanlah strategi sinkronisasi dengan sendirinya. Jika suatu kondisi rutin membutuhkan waktu jauh lebih lama daripada dugaan, periksa apakah aplikasi, environment, atau ekspektasinya yang bertanggung jawab sebelum menaikkan batas.
Isolasi state dan kendalikan boundary
Locator yang kokoh tidak dapat memperbaiki data yang terkontaminasi. Panduan best practices Playwright merekomendasikan agar test tetap terisolasi, dengan cookie, local storage, session storage, dan data yang independen. Isolasi mencegah satu kegagalan membuat test berikutnya ikut merah dan membuat satu skenario lebih mudah direproduksi.
Hal itu tidak mengharuskan seluruh aplikasi dibangun ulang untuk setiap assertion. Setup yang terkendali dapat membuat akun atau record tepat sesuai kebutuhan skenario. Sifat yang penting adalah kepemilikan: sebuah test tidak boleh bergantung pada test lain yang harus berjalan lebih dahulu.
Prinsip yang sama berlaku pada network boundary. Jika checkout test dimaksudkan untuk memverifikasi halaman konfirmasi milik proyek, payment sandbox pihak ketiga yang live mungkin menambah variasi yang tidak berhubungan dengan halaman tersebut. Playwright merekomendasikan agar test tidak menguji dependency pihak ketiga dan mengendalikan response yang diharapkan bila memungkinkan. Integration test terpisah dapat memeriksa kontrak eksternal pada frekuensi yang sesuai.
Ada kontra-argumen: terlalu banyak mocking dapat menghasilkan test yang sangat stabil untuk sistem khayalan. Kekhawatiran itu valid. Perbedaan yang berguna bukan "mock semuanya" melawan "jangan mock apa pun," melainkan boundary mana yang diklaim dicakup oleh setiap test. Suite kecil dapat menggabungkan UI test terkendali yang cepat dengan lebih sedikit integration check end-to-end.
Gunakan retry untuk mengumpulkan bukti, bukan menghapus kegagalan
Retry dapat menjawab apakah sebuah kegagalan berulang dan dapat memicu diagnostic yang lebih kaya. Retry yang hijau tidak membuktikan bahwa kegagalan pertama tidak berbahaya. Menerimanya secara otomatis tanpa penyelidikan dapat mengubah defect produk yang intermittent menjadi background noise.
Trace Viewer Playwright dapat menampilkan action, DOM snapshot, lokasi source, log, console message, dan network request, bergantung pada konfigurasi trace. Dokumentasi resmi menyajikan on-first-retry sebagai opsi CI dan retain-on-failure sebagai alternatif saat retry dinonaktifkan:
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: 1,
use: {
trace: 'on-first-retry',
},
});
Merekam setiap trace memungkinkan, tetapi dokumentasi memperingatkan bahwa proses itu berat dari sisi performance. Kebijakan retensi yang tepat bergantung pada ukuran suite, penyimpanan CI, privasi, dan kebutuhan debugging. Trace dapat memuat konten halaman dan detail network, sehingga akses serta retensinya patut diperlakukan secermat artifact test lain.
Tinjauan kegagalan yang praktis
Ketika muncul kegagalan intermittent, tinjauan singkat dapat memberi informasi lebih banyak daripada langsung mengubah timeout:
- Baca error pertama dan kenali outcome yang terlihat oleh pengguna serta diharapkan oleh test.
- Periksa apakah locator menjelaskan elemen yang dituju secara unik.
- Periksa trace di sekitar action pertama yang gagal, bukan hanya screenshot terakhir.
- Cari overlay, request yang masih pending, console error, redirect tak terduga, dan data yang berubah.
- Jalankan test yang gagal secara terpisah untuk mengungkap dependency pada shared state.
- Klasifikasikan penyebab sebelum memutuskan untuk mengubah application code, setup, locator, assertion, environment, atau timeout.
Keandalan adalah kejelasan di tengah variasi
Tidak ada strategi selector yang dapat menjamin suite sempurna. Interface berubah, environment gagal, dan sebagian perilaku terdistribusi memang benar-benar nondeterministic. Fitur waiting dan retrying Playwright mengurangi timing race yang dapat dihindari, tetapi tidak dapat memutuskan apa yang seharusnya dilakukan produk.
Karena itu, test yang andal mempunyai tujuan sederhana: menjelaskan perilaku yang bermakna, memiliki state yang dibutuhkannya, menunggu kondisi yang dapat diamati, dan meninggalkan cukup bukti ketika kenyataan berbeda. Pertanyaan setelah kegagalan seharusnya bukan sekadar, "Bagaimana membuat ini hijau?" Pertanyaannya adalah, "Apa yang diajarkan hasil ini tentang interface atau test tersebut?"
