Pengujian Regresi Visual dengan Cypress: Pendekatan Pragmatis
Diterbitkan: 2022-03-11Setiap kali versi baru pustaka komponen kami, Picasso, dirilis, kami memperbarui semua aplikasi front-end kami untuk mendapatkan hasil maksimal dari fitur baru dan menyelaraskan desain kami di semua bagian situs kami.
Bulan lalu, kami meluncurkan pembaruan Picasso ke Toptal Talent Portal, platform yang digunakan talenta kami untuk mencari pekerjaan dan berinteraksi dengan klien. Mengetahui rilis akan datang dengan perubahan desain besar, dan dalam upaya untuk meminimalkan masalah tak terduga, masuk akal untuk menggunakan teknik pengujian regresi visual untuk membantu kami menemukan masalah sebelum rilis.
Pengujian regresi visual bukanlah konsep baru; banyak proyek lain di Toptal sudah menggunakannya, termasuk Picasso sendiri.
Alat seperti Percy, Happo, dan Chromatic dapat digunakan untuk membantu tim membangun jalur regresi visual yang sehat, dan kami mempertimbangkan untuk menambahkannya terlebih dahulu. Kami akhirnya memutuskan bahwa proses penyiapan akan terlalu memakan waktu dan dapat menggagalkan jadwal kami. Kami telah menetapkan tanggal untuk pembekuan kode untuk memulai migrasi, dan dengan hanya beberapa hari tersisa hingga tenggat waktu, kami tidak punya pilihan selain menjadi kreatif.
Pengujian Regresi Visual Melalui Pengujian UI
Meskipun kami tidak memiliki tes regresi visual dalam proyek, kami memiliki cakupan yang baik dari tes integrasi UI menggunakan Cypress. Meskipun bukan itu yang paling sering digunakan alat ini, Cypress memiliki satu halaman dalam dokumentasinya yang didedikasikan untuk pengujian visual dan halaman lain yang mencantumkan semua plug-in yang tersedia untuk membantu mengonfigurasi Cypress untuk pengujian visual.
Dari Cypress ke Tangkapan Layar
Setelah melalui dokumentasi yang tersedia, kami memutuskan untuk mencoba plugin cypress-snapshot-plugin. Hanya butuh beberapa menit untuk menyiapkan, dan begitu kami melakukannya, kami segera menyadari bahwa kami tidak mengejar output regresi visual tradisional.
Sebagian besar alat regresi visual membantu mengidentifikasi perubahan yang tidak diinginkan dengan membandingkan cuplikan dan mendeteksi perbedaan piksel antara garis dasar yang diketahui dan diterima dengan versi halaman atau komponen yang dimodifikasi. Jika perbedaan piksel lebih besar dari ambang batas toleransi yang ditetapkan, halaman atau komponen ditandai untuk diperiksa secara manual. Namun, dalam rilis ini, kami tahu bahwa kami akan memiliki beberapa perubahan kecil pada sebagian besar komponen UI kami, jadi menyetel ambang batas tidak berlaku. Bahkan jika komponen yang diberikan ternyata 100% berbeda, mungkin masih benar dalam konteks versi baru. Demikian pula, penyimpangan sekecil beberapa piksel dapat berarti komponen saat ini tidak cocok untuk produksi.
Pada saat itu, dua hal yang kontras menjadi jelas: mencatat perbedaan piksel tidak akan membantu mengidentifikasi masalah, dan membandingkan komponen secara berdampingan adalah hal yang kami butuhkan. Kami mengesampingkan plug-in snapshot dan mulai membuat koleksi gambar dengan komponen kami sebelum dan setelah pembaruan Picasso diterapkan. Dengan begitu, kami dapat dengan cepat memindai semua perubahan untuk menentukan apakah versi baru masih sesuai dengan kebutuhan situs dan standar perpustakaan.
Rencana barunya adalah mengambil tangkapan layar komponen, menyimpannya secara lokal, mengambil tangkapan layar baru dari komponen yang sama di cabang dengan versi Picasso yang diperbarui, lalu menggabungkannya menjadi satu gambar. Pada akhirnya, pendekatan baru ini tidak terlalu berbeda dari apa yang kami mulai, tetapi memberi kami lebih banyak fleksibilitas selama fase implementasi karena kami tidak lagi perlu mengimpor plug-in dan menggunakan perintah barunya.
Memanfaatkan API untuk Gambar Perbandingan
Dengan tujuan yang jelas, inilah saatnya untuk melihat bagaimana Cypress dapat membantu kami mendapatkan tangkapan layar yang kami butuhkan. Seperti yang telah disebutkan, kami memiliki sejumlah besar pengujian UI yang mencakup sebagian besar Talent Portal, jadi dalam upaya untuk mengumpulkan sebanyak mungkin komponen penting, kami memutuskan untuk mengambil tangkapan layar dari masing-masing elemen setelah setiap interaksi.
Pendekatan alternatif adalah mengambil tangkapan layar dari seluruh halaman pada saat-saat penting selama pengujian, tetapi kami memutuskan bahwa gambar-gambar itu akan terlalu sulit untuk dibandingkan. Juga, perbandingan seperti itu bisa lebih rentan terhadap kesalahan manusia, seperti kehilangan footer yang telah berubah.
Opsi ketiga adalah melalui setiap kasus uji untuk memutuskan apa yang akan diambil, tetapi itu akan memakan lebih banyak waktu, jadi tetap berpegang pada semua elemen yang digunakan pada halaman tampak seperti kompromi praktis.
Kami beralih ke API Cypress untuk menghasilkan gambar. Perintah cy.screenshot() dapat, secara langsung, membuat gambar individual dari komponen, dan After Screenshot API memungkinkan kita untuk mengganti nama file, mengubah direktori, dan membedakan proses regresi visual dari yang standar. Dengan menggabungkan keduanya, kami membuat proses yang tidak memengaruhi pengujian fungsional kami dan memungkinkan kami untuk menyimpan gambar di folder yang sesuai.
Pertama, kami memperluas file index.js di direktori plug-in kami untuk mendukung dua jenis proses baru (dasar dan perbandingan). Kemudian, kami mengatur jalur untuk gambar kami sesuai dengan jenis run:

// plugins/index.js const fs = require('fs') const path = require('path') module.exports = (on, config) => { // Adding these values to your config object allows you to access them in your tests. config.env.baseline = process.env.BASELINE || false config.env.comparison = process.env.COMPARISON || false on('after:screenshot', details => { // We only want to modify the behavior of baseline and comparison runs. if (config.env.baseline || config.env.comparison) { // We keep track of the file name and number to make sure they are saved in the proper order and in their relevant folders. // An alternative would have been to look up the folder for the latest image, but this was the simpler approach. let lastScreenshotFile = '' let lastScreenshotNumber = 0 // We append the proper suffix number to the image, create the folder, and move the file. const createDirAndRename = filePath => { if (lastScreenshotFile === filePath) { lastScreenshotNumber++ } else { lastScreenshotNumber = 0 } lastScreenshotFile = filePath const newPath = filePath.replace( '.png', ` #${lastScreenshotNumber}.png` ) return new Promise((resolve, reject) => { fs.mkdir(path.dirname(newPath), { recursive: true }, mkdirErr => { if (mkdirErr) { return reject(mkdirErr) } fs.rename(details.path, newPath, renameErr => { if (renameErr) { return reject(renameErr) } resolve({ path: newPath }) }) }) }) } const screenshotPath = `visualComparison/${config.env.baseline ? 'baseline' : 'comparison'}` return createDirAndRename(details.path .replace('cypress/integration', screenshotPath) .replace('All Specs', screenshotPath) ) } }) return config } Kemudian kita memanggil setiap proses dengan menambahkan variabel lingkungan yang sesuai ke panggilan Cypress di package.json proyek :
"scripts": { "cypress:baseline": "BASELINE=true yarn cypress:open", "cypress:comparison": "COMPARISON=true yarn cypress:open" }Setelah kami menjalankan perintah baru kami, kami dapat melihat bahwa semua tangkapan layar yang diambil selama menjalankan dipindahkan ke folder yang sesuai.
Selanjutnya, kami mencoba menimpa cy.get() , perintah utama Cypress untuk mengembalikan elemen DOM, dan mengambil tangkapan layar dari setiap elemen yang dipanggil bersama dengan implementasi defaultnya. Sayangnya, cy.get() adalah perintah yang rumit untuk diubah, karena memanggil perintah asli dalam definisinya sendiri mengarah ke infinite loop. Pendekatan yang disarankan untuk mengatasi batasan ini adalah membuat perintah khusus yang terpisah dan kemudian perintah baru itu mengambil tangkapan layar setelah menemukan elemen:
Cypress.Commands.add("getAndScreenshot", (selector, options) => { // Note: You might need to tweak the command when getting multiple elements. return cy.get(selector).screenshot() }); it("get overwrite", () => { cy.visit("https://example.cypress.io/commands/actions"); cy.getAndScreenshot(".action-email") }) Namun, panggilan kita untuk berinteraksi dengan elemen pada halaman sudah dibungkus dalam fungsi getElement() internal. Jadi yang harus kami lakukan adalah memastikan bahwa tangkapan layar diambil saat pembungkus dipanggil.
Hasil yang Diperoleh Melalui Pengujian Regresi Visual
Setelah kami memiliki tangkapan layar, satu-satunya yang tersisa untuk dilakukan adalah menggabungkannya. Untuk itu, kami membuat skrip node sederhana menggunakan Canvas. Pada akhirnya, skrip memungkinkan kami untuk menghasilkan 618 gambar perbandingan! Beberapa perbedaan mudah dikenali dengan membuka Talent Portal, tetapi beberapa masalah tidak begitu jelas.
Menambahkan Nilai ke Pengujian UI
Pertama-tama, tes regresi visual tambahan terbukti bermanfaat dan mengungkap beberapa masalah yang mungkin terlewatkan tanpanya. Meskipun kami mengharapkan perbedaan dalam komponen kami, mengetahui apa yang sebenarnya diubah membantu mempersempit kasus bermasalah. Jadi, jika proyek Anda memiliki antarmuka tetapi Anda belum melakukan pengujian ini, lakukanlah!
Pelajaran kedua di sini, dan mungkin yang lebih penting, adalah bahwa kita sekali lagi diingatkan bahwa sempurna adalah musuh kebaikan. Jika kami mengesampingkan kemungkinan menjalankan tes regresi visual untuk rilis ini karena tidak ada penyiapan sebelumnya, kami mungkin melewatkan beberapa bug selama migrasi. Sebaliknya, kami menyetujui rencana yang, meskipun tidak ideal, cepat dieksekusi, kami bekerja untuk itu, dan itu terbayar.
Untuk detail lebih lanjut tentang penerapan jalur regresi visual yang kuat dalam proyek Anda, silakan merujuk ke halaman pengujian visual Cypress, pilih alat yang paling sesuai dengan kebutuhan Anda, dan tonton video tutorial.
