Puppeteer dan pendekatannya terhadap pemilih
Puppeteer adalah library otomatisasi browser untuk Node: library ini memungkinkan Anda mengontrol browser menggunakan JavaScript API yang sederhana dan modern.
Tugas browser yang paling penting tentu saja adalah menjelajahi halaman web. Mengotomatiskan tugas ini pada dasarnya setara dengan mengotomatiskan interaksi dengan halaman web.
Di Puppeteer, hal ini dicapai dengan membuat kueri elemen DOM menggunakan pemilih berbasis string dan melakukan tindakan seperti mengklik atau mengetik teks pada elemen. Misalnya, skrip yang membuka developer.google.com, menemukan kotak penelusuran, dan menelusuri puppetaria
dapat terlihat seperti ini:
(async () => {
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://developers.google.com/', { waitUntil: 'load' });
// Find the search box using a suitable CSS selector.
const search = await page.$('devsite-search > form > div.devsite-search-container');
// Click to expand search box and focus it.
await search.click();
// Enter search string and press Enter.
await search.type('puppetaria');
await search.press('Enter');
})();
Oleh karena itu, cara elemen diidentifikasi menggunakan pemilih kueri adalah bagian yang menentukan pengalaman Puppeteer. Hingga saat ini, pemilih di Puppeteer telah dibatasi untuk pemilih CSS dan XPath yang, meskipun secara ekspresif sangat canggih, dapat memiliki kelemahan untuk mempertahankan interaksi browser dalam skrip.
Pemilih sintaksis vs. semantik
Pemilih CSS bersifat sintaksis; pemilih CSS terikat erat dengan cara kerja bagian dalam representasi tekstual hierarki DOM dalam arti bahwa pemilih CSS mereferensikan ID dan nama class dari DOM. Dengan demikian, jenis alat ini menyediakan alat integral bagi developer web untuk memodifikasi atau menambahkan gaya ke elemen di halaman, namun dalam konteks tersebut developer memiliki kontrol penuh atas halaman dan hierarki DOM-nya.
Di sisi lain, skrip Puppeteer adalah pengamat eksternal halaman, jadi saat pemilih CSS digunakan dalam konteks ini, skrip ini memperkenalkan asumsi tersembunyi tentang bagaimana halaman diterapkan yang tidak dapat dikontrol oleh skrip Puppeteer.
Efeknya adalah skrip tersebut dapat menjadi rapuh dan rentan terhadap perubahan kode sumber. Misalnya, seseorang menggunakan skrip Puppeteer untuk pengujian otomatis bagi aplikasi web yang berisi node <button>Submit</button>
sebagai turunan ketiga dari elemen body
. Satu cuplikan dari kasus pengujian mungkin terlihat seperti ini:
const button = await page.$('body:nth-child(3)'); // problematic selector
await button.click();
Di sini, kita menggunakan pemilih 'body:nth-child(3)'
untuk menemukan tombol kirim, tetapi pemilih ini terikat erat dengan versi halaman web ini. Jika elemen ditambahkan di atas tombol nanti, pemilih ini tidak akan berfungsi lagi.
Hal ini bukan berita baru bagi penulis pengujian: Pengguna Puppeteer sudah mencoba memilih pemilih yang tahan terhadap perubahan tersebut. Dengan Puppetaria, kami memberi pengguna alat baru dalam misi ini.
Puppeteer kini dilengkapi dengan pengendali kueri alternatif berdasarkan pembuatan kueri hierarki aksesibilitas, bukan mengandalkan pemilih CSS. Filosofi yang mendasari di sini adalah bahwa jika elemen konkret yang ingin kita pilih tidak berubah, maka node aksesibilitas yang sesuai juga tidak akan berubah.
Kami menamai pemilih tersebut "pemilih ARIA" dan mendukung kueri untuk nama dan peran yang dapat diakses yang dikomputasi dari hierarki aksesibilitas. Dibandingkan dengan pemilih CSS, properti ini bersifat semantik. Artikel ini tidak terikat pada properti sintaksis DOM, melainkan deskripsi tentang bagaimana halaman diamati melalui teknologi pendukung seperti pembaca layar.
Dalam contoh skrip pengujian di atas, kita dapat menggunakan pemilih aria/Submit[role="button"]
untuk memilih tombol yang diinginkan, dengan Submit
merujuk pada nama elemen yang dapat diakses:
const button = await page.$('aria/Submit[role="button"]');
await button.click();
Sekarang, jika nanti kita memutuskan untuk mengubah konten teks tombol dari Submit
menjadi Done
, pengujian akan gagal lagi, tetapi dalam hal ini hal itu diinginkan; dengan mengubah nama tombol, kita mengubah konten halaman, bukan presentasi visualnya atau cara strukturnya di DOM. Pengujian kami akan memperingatkan kami tentang perubahan tersebut untuk memastikan bahwa perubahan tersebut disengaja.
Kembali ke contoh yang lebih besar dengan kotak penelusuran, kita dapat memanfaatkan pengendali aria
baru dan mengganti
const search = await page.$('devsite-search > form > div.devsite-search-container');
dengan
const search = await page.$('aria/Open search[role="button"]');
untuk menemukan kotak penelusuran.
Secara lebih umum, kami yakin bahwa penggunaan pemilih ARIA tersebut dapat memberikan manfaat berikut kepada pengguna Puppeteer:
- Membuat pemilih dalam skrip pengujian lebih tahan terhadap perubahan kode sumber.
- Membuat skrip pengujian lebih mudah dibaca (nama yang dapat diakses adalah deskripsi semantik).
- Motivasi praktik yang baik untuk menetapkan properti aksesibilitas ke elemen.
Bagian lain dalam artikel ini membahas detail tentang cara kami menerapkan project Puppetaria.
Proses desain
Latar belakang
Seperti yang dimotivasi di atas, kita ingin mengaktifkan elemen kueri berdasarkan nama dan peran yang dapat diakses. Ini adalah properti hierarki aksesibilitas, yang merupakan duplikat dari hierarki DOM biasa, yang digunakan oleh perangkat seperti pembaca layar untuk menampilkan halaman web.
Dari melihat spesifikasi untuk menghitung nama yang dapat diakses, jelas bahwa menghitung nama untuk elemen adalah tugas yang tidak mudah, jadi sejak awal kami memutuskan bahwa kami ingin menggunakan kembali infrastruktur Chromium yang ada untuk ini.
Cara kami menerapkannya
Meskipun membatasi diri untuk menggunakan hierarki aksesibilitas Chromium, ada beberapa cara yang dapat kita gunakan untuk menerapkan kueri ARIA di Puppeteer. Untuk mengetahui alasannya, mari kita lihat bagaimana Puppeteer mengontrol browser.
Browser mengekspos antarmuka proses debug melalui protokol yang disebut Chrome DevTools Protocol (CDP). Hal ini mengekspos fungsi seperti "muat ulang halaman" atau "jalankan bagian JavaScript ini di halaman dan tampilkan hasilnya" melalui antarmuka yang tidak bergantung pada bahasa.
Frontend DevTools dan Puppeteer menggunakan CDP untuk berkomunikasi dengan browser. Untuk menerapkan perintah CDP, ada infrastruktur DevTools di dalam semua komponen Chrome: di browser, di perender, dan sebagainya. CDP menangani pemilihan rute perintah ke tempat yang tepat.
Tindakan pupeteer seperti membuat kueri, mengklik, dan mengevaluasi ekspresi dilakukan dengan memanfaatkan perintah CDP seperti Runtime.evaluate
yang mengevaluasi JavaScript langsung di konteks halaman dan menyerahkan hasilnya. Tindakan Puppeteer lainnya seperti mengemulasi kekurangan penglihatan warna, mengambil screenshot, atau mengambil rekaman aktivitas menggunakan CDP untuk berkomunikasi langsung dengan proses rendering Blink.
Hal ini memberi kita dua jalur untuk menerapkan fungsi kueri; kita dapat:
- Tulis logika kueri kita di JavaScript dan masukkan logika kueri tersebut ke dalam halaman menggunakan
Runtime.evaluate
, atau - Gunakan endpoint CDP yang dapat mengakses dan membuat kueri hierarki aksesibilitas secara langsung dalam proses Blink.
Kami menerapkan 3 prototipe:
- Pencarian DOM JS - berdasarkan memasukkan JavaScript ke dalam halaman
- Puppeteer AXTree traversal - berdasarkan penggunaan akses CDP yang ada ke hierarki aksesibilitas
- Traversal DOM CDP - menggunakan endpoint CDP baru yang dibuat khusus untuk membuat kueri hierarki aksesibilitas
Traversal DOM JS
Prototipe ini melakukan penelusuran penuh DOM dan menggunakan element.computedName
dan element.computedRole
, yang dibatasi pada tanda peluncuran ComputedAccessibilityInfo
, untuk mengambil nama dan peran untuk setiap elemen selama penelusuran.
Puppeteer AXTree traversal
Di sini, kita mengambil hierarki aksesibilitas lengkap melalui CDP dan menelusurinya di Puppeteer. Node aksesibilitas yang dihasilkan kemudian dipetakan ke node DOM.
Traversal DOM CDP
Untuk prototipe ini, kami menerapkan endpoint CDP baru khusus untuk membuat kueri hierarki aksesibilitas. Dengan cara ini, kueri dapat terjadi di backend melalui implementasi C++, bukan dalam konteks halaman melalui JavaScript.
Tolok ukur pengujian unit
Gambar berikut membandingkan total runtime kueri empat elemen sebanyak 1.000 kali untuk 3 prototipe. Benchmark dijalankan dalam 3 konfigurasi berbeda yang memvariasikan ukuran halaman dan apakah caching elemen aksesibilitas diaktifkan atau tidak.
Sangat jelas bahwa ada kesenjangan performa yang cukup besar antara mekanisme kueri yang didukung CDP dan dua mekanisme lainnya yang hanya diterapkan di Puppeteer, dan perbedaan relatifnya tampaknya meningkat secara dramatis dengan ukuran halaman. Sangat menarik untuk melihat bahwa prototipe traversal DOM JS merespons dengan sangat baik untuk mengaktifkan penyimpanan dalam cache aksesibilitas. Jika cache dinonaktifkan, hierarki aksesibilitas akan dihitung sesuai permintaan dan menghapus hierarki setelah setiap interaksi jika domain dinonaktifkan. Mengaktifkan domain akan membuat Chromium meng-cache hierarki yang dihitung.
Untuk penelusuran DOM JS, kita meminta nama dan peran yang dapat diakses untuk setiap elemen selama penelusuran, sehingga jika penyimpanan dalam cache dinonaktifkan, Chromium akan menghitung dan menghapus hierarki aksesibilitas untuk setiap elemen yang kita kunjungi. Di sisi lain, untuk pendekatan berbasis CDP, hierarki hanya dihapus di antara setiap panggilan ke CDP, yaitu untuk setiap kueri. Pendekatan ini juga mendapatkan manfaat dari pengaktifan penyimpanan dalam cache, karena hierarki aksesibilitas kemudian dipertahankan di seluruh panggilan CDP, tetapi peningkatan performanya relatif lebih kecil.
Meskipun mengaktifkan cache tampaknya lebih disukai di sini, tindakan ini memiliki biaya penggunaan memori tambahan. Untuk skrip Puppeteer yang misalnya mencatat file rekaman aktivitas, hal ini dapat menjadi masalah. Oleh karena itu, kami memutuskan untuk tidak mengaktifkan penyimpanan dalam cache hierarki aksesibilitas secara default. Pengguna dapat mengaktifkan penyimpanan cache sendiri dengan mengaktifkan Domain aksesibilitas CDP.
Benchmark rangkaian pengujian DevTools
Benchmark sebelumnya menunjukkan bahwa menerapkan mekanisme kueri kami di lapisan CDP memberikan peningkatan performa dalam skenario pengujian unit klinis.
Untuk melihat apakah perbedaannya cukup jelas sehingga dapat terlihat dalam skenario yang lebih realistis saat menjalankan rangkaian pengujian lengkap, kami mem-patch rangkaian pengujian menyeluruh DevTools untuk menggunakan prototipe berbasis JavaScript dan CDP, lalu membandingkan runtime-nya. Dalam benchmark ini, kami mengubah total 43 pemilih dari [aria-label=…]
menjadi pengendali kueri kustom aria/…
, yang kemudian kami terapkan menggunakan setiap prototipe.
Beberapa pemilih digunakan beberapa kali dalam skrip pengujian, sehingga jumlah eksekusi pengendali kueri aria
sebenarnya adalah 113 per pengoperasian suite. Jumlah total pilihan kueri adalah 2.253, sehingga hanya sebagian kecil pilihan kueri yang terjadi melalui prototipe.
Seperti yang terlihat pada gambar di atas, ada perbedaan yang jelas dalam total runtime. Data terlalu berisik untuk menyimpulkan sesuatu yang spesifik, tetapi jelas bahwa kesenjangan performa antara kedua prototipe juga terlihat dalam skenario ini.
Endpoint CDP baru
Mengingat tolok ukur di atas, dan karena pendekatan berbasis flag peluncuran tidak diinginkan secara umum, kami memutuskan untuk melanjutkan dengan menerapkan perintah CDP baru untuk membuat kueri hierarki aksesibilitas. Sekarang, kita harus mencari tahu antarmuka endpoint baru ini.
Untuk kasus penggunaan di Puppeteer, kita memerlukan endpoint untuk menggunakan RemoteObjectIds
sebagai argumen dan, agar kita dapat menemukan elemen DOM yang sesuai setelahnya, endpoint harus menampilkan daftar objek yang berisi backendNodeIds
untuk elemen DOM.
Seperti yang terlihat pada bagan di bawah, kami mencoba beberapa pendekatan untuk memuaskan antarmuka ini. Dari hal ini, kami mendapati bahwa ukuran objek yang ditampilkan, yaitu apakah kita menampilkan node aksesibilitas lengkap atau hanya backendNodeIds
, tidak membuat perbedaan yang jelas. Di sisi lain, kami menemukan bahwa menggunakan NextInPreOrderIncludingIgnored
yang ada merupakan pilihan yang buruk untuk menerapkan logika traversal di sini, karena cara tersebut menghasilkan perlambatan yang kentara.
Merangkum semuanya
Sekarang, dengan endpoint CDP yang sudah ada, kita telah menerapkan pengendali kueri di sisi Puppeteer. Pekerjaan utama di sini adalah menyusun ulang kode penanganan kueri agar kueri dapat diselesaikan langsung melalui CDP, bukan membuat kueri melalui JavaScript yang dievaluasi dalam konteks halaman.
Apa langkah selanjutnya?
Pengendali aria
baru dikirimkan dengan Puppeteer v5.4.0 sebagai pengendali kueri bawaan. Kami menantikan cara pengguna mengadopsinya ke dalam skrip pengujian mereka, dan kami tidak sabar untuk mendengar ide Anda tentang cara kami dapat membuatnya lebih berguna.
Mendownload saluran pratinjau
Sebaiknya gunakan Chrome Canary, Dev, atau Beta sebagai browser pengembangan default Anda. Saluran pratinjau ini memberi Anda akses ke fitur DevTools terbaru, memungkinkan Anda menguji API platform web canggih, dan membantu Anda menemukan masalah di situs sebelum pengguna melakukannya.
Hubungi tim Chrome DevTools
Gunakan opsi berikut untuk membahas fitur baru, update, atau apa pun yang berkaitan dengan DevTools.
- Kirim masukan dan permintaan fitur kepada kami di crbug.com.
- Laporkan masalah DevTools menggunakan Opsi lainnya > Bantuan > Laporkan masalah DevTools di DevTools.
- Tweet di @ChromeDevTools.
- Berikan komentar di video YouTube Yang baru di DevTools atau video YouTube Tips DevTools.