Puppetaria: skrip Puppeteer yang mengutamakan aksesibilitas

Johan Bay
Johan Bay

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, menjelajahi halaman web. Mengotomatiskan tugas ini pada dasarnya setara dengan mengotomatiskan interaksi dengan halaman web.

Di Puppeteer, hal ini dicapai dengan membuat kueri untuk elemen DOM menggunakan pemilih berbasis string dan melakukan tindakan seperti mengklik atau mengetik teks pada elemen. Misalnya, skrip yang terbuka akan membuka developer.google.com, menemukan kotak penelusuran, dan penelusuran untuk puppetaria akan 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 merupakan bagian penting dari pengalaman Puppeteer. Sampai sekarang, pemilih di Puppeteer telah dibatasi pada pemilih CSS dan XPath yang, meskipun secara ekspresif sangat kuat, dapat memiliki kelemahan untuk mempertahankan interaksi browser dalam skrip.

Pemilih sintaksis vs. semantik

Pemilih CSS bersifat sintaksis; keduanya terikat erat dengan cara kerja bagian dalam dari representasi tekstual hierarki DOM sebagaimana referensi tersebut 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 bisa menjadi rapuh dan rentan terhadap perubahan kode sumber. Misalkan, misalnya, instance menggunakan skrip Puppeteer untuk pengujian otomatis bagi aplikasi web yang berisi node <button>Submit</button> sebagai turunan ketiga 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 alat ini terikat erat dengan versi halaman web ini. Jika suatu elemen kemudian ditambahkan di atas tombol, pemilih ini tidak akan berfungsi lagi.

Ini bukanlah berita untuk penulis uji coba: Pengguna Puppeteer sudah mencoba memilih pemilih yang andal 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 "ARIA selector" dan mendukung pembuatan kueri untuk nama yang dapat diakses yang dikomputasi dan peran 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 kasus ini memang diinginkan; dengan mengubah nama tombol, kita mengubah konten halaman, bukan presentasi visualnya atau bagaimana hal itu disusun dalam DOM. Pengujian harus memberikan peringatan 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 pencarian!

Secara lebih umum, kami meyakini bahwa menggunakan pemilih ARIA tersebut dapat memberikan manfaat berikut bagi pengguna Puppeteer:

  • Membuat pemilih dalam skrip pengujian lebih tahan terhadap perubahan kode sumber.
  • Membuat skrip pengujian lebih mudah dibaca (nama yang dapat diakses adalah deskriptor semantik).
  • Memotivasi praktik yang baik untuk menetapkan properti aksesibilitas ke elemen.

Bagian selanjutnya dari artikel ini membahas detail tentang cara kami mengimplementasikan proyek Puppetaria.

Proses desain

Latar belakang

Seperti yang termotivasi di atas, kita ingin mengaktifkan kueri elemen berdasarkan nama dan peran yang dapat diakses. Ini adalah properti hierarki aksesibilitas, ganda dari hierarki DOM biasa, yang digunakan oleh perangkat seperti pembaca layar untuk menampilkan halaman web.

Dari melihat spesifikasi untuk menghitung nama aksesibilitas, sudah jelas bahwa menghitung nama untuk suatu elemen adalah tugas yang sulit. Jadi, dari awal kami memutuskan bahwa kami ingin menggunakan kembali infrastruktur Chromium yang ada untuk ini.

Pendekatan yang kami lakukan untuk menerapkannya

Bahkan membatasi diri kita sendiri untuk menggunakan pohon aksesibilitas Chromium, ada beberapa cara yang dapat kita lakukan untuk menerapkan kueri ARIA di Puppeteer. Untuk mengetahui alasannya, mari kita lihat bagaimana Puppeteer mengontrol browser.

Browser menampilkan antarmuka proses debug melalui protokol yang disebut Protokol Chrome DevTools (CDP). Hal ini mengekspos fungsi seperti "muat ulang halaman" atau "jalankan bagian JavaScript ini di halaman dan serahkan kembali hasilnya" melalui antarmuka yang tidak terikat dengan 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 seterusnya. CDP menangani {i>routing<i} 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.

CDP

Langkah ini memberi kita dua jalur untuk mengimplementasikan 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 langsung dalam proses Blink.

Kami mengimplementasikan 3 prototipe:

  • JS DOM traversal - 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 traversal penuh DOM dan menggunakan element.computedName dan element.computedRole, yang dibatasi pada flag peluncuran ComputedAccessibilityInfo, untuk mengambil nama dan peran setiap elemen selama traversal.

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 mengimplementasikan endpoint CDP baru khusus untuk membuat kueri hierarki aksesibilitas. Dengan cara ini, kueri dapat terjadi pada {i>back-end<i} melalui implementasi C++, bukan dalam konteks halaman melalui JavaScript.

Tolok ukur pengujian unit

Gambar berikut membandingkan total runtime kueri empat elemen sebanyak 1000 kali untuk 3 prototipe. Benchmark dijalankan dalam 3 konfigurasi berbeda yang memvariasikan ukuran halaman dan apakah cache elemen aksesibilitas diaktifkan atau tidak.

Benchmark: Total runtime yang membuat kueri empat elemen sebanyak 1.000 kali

Sangat jelas bahwa ada kesenjangan performa yang cukup besar antara mekanisme kueri yang didukung CDP dan dua mekanisme lainnya yang hanya diimplementasikan di Puppeteer, dan perbedaan relatifnya tampaknya meningkat secara dramatis seiring dengan ukuran halaman. Agak menarik melihat bahwa prototipe traversal DOM JS merespons dengan sangat baik terhadap pengaktifan 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 pohon yang dihitung.

Untuk traversal DOM JS, kami meminta nama dan peran yang dapat diakses untuk setiap elemen selama traversal, jadi jika penyimpanan cache dinonaktifkan, Chromium akan menghitung dan menghapus hierarki aksesibilitas untuk setiap elemen yang kita kunjungi. Di sisi lain, untuk pendekatan berbasis CDP, pohon hanya dihapus di antara setiap panggilan ke CDP, yaitu untuk setiap kueri. Pendekatan ini juga mendapatkan manfaat dari pengaktifan 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 merekam file rekaman aktivitas, hal ini dapat bermasalah. Oleh karena itu, kami memutuskan untuk tidak mengaktifkan caching hierarki aksesibilitas secara default. Pengguna dapat mengaktifkan penyimpanan cache sendiri dengan mengaktifkan Domain aksesibilitas CDP.

Tolok ukur rangkaian pengujian DevTools

Tolok ukur sebelumnya menunjukkan bahwa penerapan mekanisme kueri pada lapisan CDP akan memberikan peningkatan performa dalam skenario pengujian unit klinis.

Untuk melihat apakah perbedaannya cukup jelas untuk membuatnya terlihat dalam skenario yang lebih realistis saat menjalankan rangkaian pengujian lengkap, kami mem-patch rangkaian pengujian end-to-end DevTools untuk memanfaatkan prototipe berbasis JavaScript dan CDP serta membandingkan runtime-nya. Dalam benchmark ini, kami mengubah total 43 pemilih dari [aria-label=…] menjadi pengendali kueri kustom aria/…, yang kemudian kami terapkan menggunakan masing-masing 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, jadi hanya sebagian kecil dari pilihan kueri yang terjadi melalui prototipe.

Benchmark: e2e test suite

Seperti yang terlihat pada gambar di atas, ada perbedaan jelas dalam total runtime. Data terlalu berisik untuk menyimpulkan sesuatu yang spesifik, tetapi jelas bahwa kesenjangan performa antara kedua prototipe juga ditunjukkan dalam skenario ini.

Endpoint CDP baru

Sehubungan dengan tolok ukur di atas, dan karena pendekatan berbasis flag peluncuran tidak diinginkan secara umum, kami memutuskan untuk melanjutkan penerapan perintah CDP baru untuk mengkueri hierarki aksesibilitas. Sekarang, kami harus mencari tahu antarmuka dari {i>endpoint<i} baru ini.

Untuk kasus penggunaan di Puppeteer, endpoint akan menampilkan apa yang disebut RemoteObjectIds sebagai argumen dan, agar dapat menemukan elemen DOM yang sesuai setelahnya, endpoint akan 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 sini, kita menemukan bahwa ukuran objek yang ditampilkan, yaitu apakah kita menampilkan node aksesibilitas penuh atau hanya backendNodeIds yang tidak menunjukkan 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.

Tolok ukur: Perbandingan prototipe traversal AXTree berbasis CDP

Menyelesaikan semuanya

Sekarang, setelah endpoint CDP diterapkan, kita mengimplementasikan pengendali kueri di sisi Puppeteer. Tugas utama di sini adalah merestrukturisasi kode penanganan kueri guna memungkinkan kueri diselesaikan secara 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 tidak sabar untuk melihat bagaimana pengguna menerapkannya ke dalam skrip pengujian mereka, dan kami tidak sabar untuk mendengar ide Anda tentang bagaimana kami dapat membuatnya lebih berguna lagi.

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, menguji API platform web mutakhir, dan menemukan masalah di situs Anda sebelum pengguna melakukannya.

Menghubungi tim Chrome DevTools

Gunakan opsi berikut untuk membahas fitur dan perubahan baru dalam postingan, atau hal lain yang terkait dengan DevTools.

  • Kirim saran atau masukan kepada kami melalui crbug.com.
  • Laporkan masalah DevTools menggunakan Opsi lainnya   Lainnya > Bantuan > Laporkan masalah DevTools di DevTools.
  • Tweet di @ChromeDevTools.
  • Beri komentar di Video YouTube yang baru di DevTools atau Tips DevTools Video YouTube.