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 adalah, tentu saja, menjelajahi halaman web. Mengotomatiskan tugas ini pada dasarnya sama dengan mengotomatiskan interaksi dengan halaman web.
Di Puppeteer, hal ini dilakukan dengan mengkueri 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 penting dari pengalaman Puppeteer. Hingga saat ini, pemilih di Puppeteer terbatas pada pemilih CSS dan XPath yang, meskipun sangat efektif secara ekspresif, dapat memiliki kelemahan dalam mempertahankan interaksi browser dalam skrip.
Pemilih sintaksis vs. semantik
Pemilih CSS bersifat sintaksis; pemilih ini terkait erat dengan cara kerja internal representasi tekstual hierarki DOM dalam arti bahwa pemilih ini merujuk ke ID dan nama class dari DOM. Oleh karena itu, elemen ini menyediakan alat integral bagi developer web untuk mengubah atau menambahkan gaya ke elemen di halaman, tetapi dalam konteks tersebut, developer memiliki kontrol penuh atas halaman dan hierarki DOM-nya.
Di sisi lain, skrip Puppeteer adalah pengamat eksternal halaman, sehingga saat pemilih CSS digunakan dalam konteks ini, skrip tersebut akan memperkenalkan asumsi tersembunyi tentang cara halaman diimplementasikan yang tidak dapat dikontrol oleh skrip Puppeteer.
Akibatnya, skrip tersebut dapat menjadi rapuh dan rentan terhadap perubahan kode sumber. Misalnya, seseorang menggunakan skrip Puppeteer untuk pengujian otomatis pada 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 terkait erat dengan versi halaman web ini. Jika elemen kemudian ditambahkan di atas tombol, pemilih ini tidak akan berfungsi lagi.
Hal ini bukan hal baru bagi penulis pengujian: Pengguna Puppeteer sudah mencoba memilih pemilih yang tahan terhadap perubahan tersebut. Dengan Puppetaria, kami memberi pengguna alat baru dalam upaya ini.
Puppeteer kini dilengkapi dengan pengendali kueri alternatif berdasarkan kueri hierarki aksesibilitas, bukan mengandalkan pemilih CSS. Filosofi yang mendasarinya adalah jika elemen konkret yang ingin kita pilih tidak berubah, node aksesibilitas yang sesuai juga tidak boleh berubah.
Kami menamai pemilih tersebut "pemilih ARIA" dan mendukung kueri untuk label aksesibilitas dan peran yang dihitung dari hierarki aksesibilitas. Dibandingkan dengan pemilih CSS, properti ini bersifat semantik. Peran ini tidak terkait dengan properti sintaksis DOM, melainkan deskriptor untuk cara 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 mengacu pada label aksesibilitas elemen:
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 tersebut memang 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 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 (label aksesibilitas adalah deskriptor semantik).
- Mendorong praktik yang baik untuk menetapkan properti aksesibilitas ke elemen.
Bagian selanjutnya dari artikel ini membahas detail tentang cara kami menerapkan project Puppetaria.
Proses desain
Latar belakang
Seperti yang dijelaskan di atas, kita ingin mengaktifkan kueri elemen berdasarkan label aksesibilitas dan perannya. Ini adalah properti hierarki aksesibilitas, yang merupakan pasangan dari hierarki DOM biasa, yang digunakan oleh perangkat seperti pembaca layar untuk menampilkan halaman web.
Dari spesifikasi untuk menghitung label aksesibilitas, jelas bahwa menghitung nama untuk sebuah elemen bukanlah tugas yang mudah. Jadi, sejak awal kami memutuskan untuk menggunakan kembali infrastruktur Chromium yang ada untuk hal ini.
Cara kami menerapkan fitur ini
Meskipun kita membatasi diri untuk menggunakan hierarki aksesibilitas Chromium, ada beberapa cara untuk menerapkan kueri ARIA di Puppeteer. Untuk mengetahui alasannya, mari kita lihat cara 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 kembalikan hasilnya" melalui antarmuka yang tidak bergantung pada bahasa.
Front-end 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 akan menangani perutean perintah ke tempat yang tepat.
Tindakan Puppeteer seperti membuat kueri, mengklik, dan mengevaluasi ekspresi dilakukan dengan memanfaatkan perintah CDP seperti Runtime.evaluate yang mengevaluasi JavaScript langsung dalam konteks halaman dan menampilkan hasilnya. Tindakan Puppeteer lainnya seperti mengemulasi defisiensi penglihatan warna, mengambil screenshot, atau mengambil rekaman aktivitas menggunakan CDP untuk berkomunikasi langsung dengan proses rendering Blink.

Hal ini memberi kita dua jalur untuk mengimplementasikan fungsi kueri; kita dapat:
- Menulis logika kueri dalam JavaScript dan memasukkannya ke halaman menggunakan
Runtime.evaluate, atau - Gunakan endpoint CDP yang dapat mengakses dan mengkueri hierarki aksesibilitas secara langsung dalam proses Blink.
Kami menerapkan 3 prototipe:
- Penjelajahan DOM JS - berdasarkan injeksi JavaScript ke halaman
- Penjelajahan AXTree Puppeteer - berdasarkan penggunaan akses CDP yang ada ke hierarki aksesibilitas
- CDP DOM traversal - menggunakan endpoint CDP baru yang dibuat khusus untuk mengkueri hierarki aksesibilitas
Penjelajahan DOM JS
Prototipe ini melakukan penjelajahan penuh DOM dan menggunakan element.computedName serta element.computedRole, yang dibatasi pada flag peluncuran ComputedAccessibilityInfo, untuk mengambil nama dan peran setiap elemen selama penjelajahan.
Penjelajahan AXTree Puppeteer
Di sini, kita mengambil hierarki aksesibilitas lengkap melalui CDP dan melintasinya di Puppeteer. Node aksesibilitas yang dihasilkan kemudian dipetakan ke node DOM.
CDP DOM traversal
Untuk prototipe ini, kami menerapkan endpoint CDP baru khusus untuk mengkueri hierarki aksesibilitas. Dengan cara ini, kueri dapat dilakukan 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. Tolok ukur dijalankan dalam 3 konfigurasi berbeda yang bervariasi dalam ukuran halaman dan apakah caching elemen aksesibilitas diaktifkan atau tidak.
Cukup jelas bahwa ada kesenjangan performa yang cukup besar antara mekanisme kueri yang didukung CDP dan dua mekanisme lainnya yang diterapkan hanya di Puppeteer, dan perbedaan relatifnya tampaknya meningkat secara signifikan seiring dengan ukuran halaman. Sangat menarik melihat prototipe penjelajahan DOM JS merespons dengan sangat baik saat mengaktifkan caching aksesibilitas. Jika caching 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 traversal DOM JS, kami meminta label aksesibilitas dan peran untuk setiap elemen selama traversal. Jadi, jika penyimpanan dalam cache dinonaktifkan, Chromium akan menghitung dan menghapus hierarki aksesibilitas untuk setiap elemen yang kami 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 caching, karena hierarki aksesibilitas kemudian dipertahankan di seluruh panggilan CDP, tetapi peningkatan performanya relatif lebih kecil.
Meskipun mengaktifkan caching terlihat diinginkan di sini, hal ini memerlukan biaya penggunaan memori tambahan. Untuk skrip Puppeteer yang misalnya merekam file rekaman aktivitas, hal ini dapat menimbulkan masalah. Oleh karena itu, kami memutuskan untuk tidak mengaktifkan caching hierarki aksesibilitas secara default. Pengguna dapat mengaktifkan sendiri penyimpanan ke dalam cache dengan mengaktifkan domain Aksesibilitas CDP.
Tolok ukur suite pengujian DevTools
Tolok ukur sebelumnya menunjukkan bahwa penerapan mekanisme kueri kami di lapisan CDP memberikan peningkatan performa dalam skenario pengujian unit klinis.
Untuk melihat apakah perbedaannya cukup signifikan sehingga terlihat dalam skenario yang lebih realistis saat menjalankan suite pengujian lengkap, kami mem-patch suite pengujian end-to-end DevTools untuk menggunakan prototipe berbasis JavaScript dan CDP, lalu membandingkan runtime-nya. Dalam tolok ukur 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 yang sebenarnya adalah 113 per eksekusi 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 terlihat dalam total runtime. Data terlalu bising untuk menyimpulkan sesuatu yang spesifik, tetapi jelas bahwa kesenjangan performa antara kedua prototipe juga terlihat dalam skenario ini.
Endpoint CDP baru
Berdasarkan tolok ukur di atas, dan karena pendekatan berbasis tanda peluncuran umumnya tidak diinginkan, kami memutuskan untuk melanjutkan penerapan perintah CDP baru untuk mengkueri hierarki aksesibilitas. Sekarang, kita harus mencari tahu antarmuka endpoint baru ini.
Untuk kasus penggunaan kita di Puppeteer, kita memerlukan endpoint untuk menggunakan RemoteObjectIds sebagai argumen dan, agar kita dapat menemukan elemen DOM yang sesuai setelahnya, endpoint tersebut harus menampilkan daftar objek yang berisi backendNodeIds untuk elemen DOM.
Seperti yang terlihat dalam diagram di bawah, kami mencoba beberapa pendekatan yang memenuhi antarmuka ini. Dari sini, kami menemukan bahwa ukuran objek yang ditampilkan, yaitu apakah kami menampilkan node aksesibilitas penuh atau hanya backendNodeIds, tidak membuat perbedaan yang terlihat. Di sisi lain, kami menemukan bahwa menggunakan NextInPreOrderIncludingIgnored yang ada adalah pilihan yang buruk untuk menerapkan logika traversal di sini, karena hal itu menghasilkan perlambatan yang nyata.
Kesimpulan
Sekarang, dengan endpoint CDP yang sudah ada, kami menerapkan pengendali kueri di sisi Puppeteer. Pekerjaan berat di sini adalah merestrukturisasi kode penanganan kueri untuk memungkinkan kueri 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 bagaimana pengguna mengadopsinya ke dalam skrip pengujian mereka, dan kami tidak sabar untuk mendengar ide Anda tentang cara kami dapat membuat fitur ini lebih bermanfaat.
Mendownload saluran pratinjau
Pertimbangkan untuk menggunakan 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 mutakhir, dan membantu Anda menemukan masalah di situs sebelum pengguna Anda menemukannya.
Hubungi tim Chrome DevTools
Gunakan opsi berikut untuk membahas fitur baru, update, atau hal lain yang terkait 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.