Kami menulis tes integrasi untuk rilis frontend dan mempercepat

Halo! Nama saya Vova, saya seorang frontend di Tinkoff. Tim kami bertanggung jawab atas dua produk untuk badan hukum. Saya dapat mengatakan dalam gambar tentang ukuran produk: regresi lengkap dari setiap produk oleh dua penguji berlangsung dari tiga hari (tanpa pengaruh faktor eksternal).



Kerangka waktu yang signifikan dan memohon untuk ditangani. Ada beberapa cara untuk bertarung, yang utama:



  • Memotong aplikasi menjadi produk yang lebih kecil dengan siklus rilis mereka sendiri.

  • Melapisi produk dengan tes sesuai dengan uji piramida.



Paragraf terakhir adalah topik artikel saya.



gambar



Menguji Piramida



Seperti yang kita ketahui, ada tiga level dalam piramida pengujian: tes unit, tes integrasi, dan tes e2e. Saya pikir banyak yang akrab dengan unit, serta dengan e2e, jadi saya akan membahas tes integrasi lebih detail.



Sebagai bagian dari pengujian integrasi, kami memeriksa operasi seluruh aplikasi melalui interaksi dengan UI, namun, perbedaan utama dari tes e2e adalah bahwa kami tidak membuat permintaan nyata untuk mendukung. Hal ini dilakukan untuk memeriksa interaksi semua sistem di depan, untuk mengurangi jumlah tes e2e di masa depan.



Kami menggunakan Cypress untuk menulis tes integrasi . Dalam artikel ini saya tidak akan membandingkannya dengan kerangka kerja lain, saya hanya akan mengatakan mengapa hal itu terjadi pada kita:



  1. Dokumentasi yang sangat rinci.

  2. Mudah debugging tes (Cypress memiliki GUI khusus untuk ini dengan langkah-langkah perjalanan waktu dalam tes).



Poin-poin ini penting bagi tim kami, karena kami tidak memiliki pengalaman dalam menulis tes integrasi dan awal yang sangat sederhana diperlukan. Dalam artikel ini, saya ingin memberi tahu Anda tentang jalur yang telah kami lalui, tentang gundukan yang telah diisi, dan berbagi resep untuk implementasi.



Awal jalan



Pada awalnya, Angular Workspace dengan satu aplikasi digunakan untuk mengatur kode. Setelah menginstal paket Cypress, folder cypress dengan konfigurasi dan tes muncul di root aplikasi, kami berhenti di opsi ini. Saat mencoba menyiapkan skrip di package.json yang diperlukan untuk menjalankan aplikasi dan menjalankan tes di atasnya, kami mengalami masalah berikut:



  1. Index.html berisi beberapa skrip yang tidak diperlukan dalam tes integrasi.

  2. Untuk menjalankan tes integrasi, perlu memastikan bahwa server dengan aplikasi sedang berjalan.



Masalah dengan index.html diselesaikan melalui konfigurasi build yang terpisah - sebut saja sypress - di mana kami menentukan index.html khusus. Bagaimana cara mengimplementasikannya? Kami menemukan konfigurasi aplikasi Anda di angular.json, buka bagian build, tambahkan konfigurasi terpisah untuk Cypress di sana dan jangan lupa untuk menentukan konfigurasi ini untuk mode-melayani.



Contoh konfigurasi untuk build:



"build": {
 ...
 "configurations": {
   β€¦ //  
   "cypress": {
     "aot": true,
     "index": "projects/main-app-integrations/src/fixtures/index.html",
     "fileReplacements": [
       {
         "replace": "projects/main-app/src/environments/environment.ts",
         "with": "projects/main-app/src/environments/environment.prod.ts"
       }
     ]
   }
 }
}


Melayani integrasi:



"serve": {
 ...
 "configurations": {
   β€¦ //  
   "cypress": {
     "browserTarget": "main-app:build:cypress"
   }
 }
}


Dari main: untuk konfigurasi cypress, kami menentukan rakitan aot dan mengganti file dengan lingkungan - ini diperlukan untuk membuat rakitan seperti produk selama pengujian.



Jadi, kami menemukan index.html, masih untuk meningkatkan aplikasi, menunggu build untuk menyelesaikan dan menjalankan tes di atasnya. Untuk melakukan ini, gunakan perpustakaan start-server-and-test dan tulis skrip berdasarkan itu:



 "main-app:cy:run": "cypress run",
 "main-app:cy:open": "cypress open",
 "main-app:integrations": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:run",
 "main-app:integrations:open": "start-server-and-test main-app:serve:cypress http://localhost:8808/app/user/ main-app:cy:open"


Seperti yang Anda lihat, ada dua jenis skrip: buka dan jalankan. Mode terbuka membuka GUI Cypress itu sendiri, di mana Anda dapat beralih di antara tes dan menggunakan perjalanan waktu. Mode jalankan hanyalah uji coba dan hasil akhir dari menjalankan itu, bagus untuk berjalan di CI.



Berdasarkan hasil pekerjaan yang dilakukan, kami bisa mendapatkan kerangka kerja awal untuk menulis tes pertama dan menjalankannya dalam CI.



Monorepositori



Pendekatan yang dijelaskan memiliki masalah yang sangat nyata: jika ada dua atau lebih aplikasi dalam repositori, maka pendekatan satu folder tidak layak. Dan begitulah yang terjadi dengan kami. Tapi itu terjadi dengan cara yang agak menarik. Pada saat diperkenalkannya Cypress, kami pindah ke NX, dan pria tampan ini di luar kotak memungkinkan untuk bekerja dengan Cypress. Apa prinsip kerja di dalamnya:



  1. Anda memiliki aplikasi seperti aplikasi-utama, di sebelahnya aplikasi aplikasi-e2e utama dibuat.

  2. Ubah nama main-app-e2e menjadi integrasi aplikasi utama - Anda luar biasa.



Sekarang Anda dapat menjalankan tes integrasi dengan satu perintah - ng e2e main-app-integrations. NX akan secara otomatis memunculkan aplikasi utama, menunggu jawaban, dan menjalankan tes.



Sayangnya, mereka yang saat ini menggunakan Angular Workspace tetap ada di sela-sela, tapi tidak apa-apa, saya punya resep untuk Anda juga. Kami akan menggunakan struktur file seperti pada NX:



  1. Buat folder integrasi aplikasi utama di sebelah aplikasi Anda.

  2. Kami membuat folder src di dalamnya dan memasukkan isi folder cypress ke dalamnya.

  3. Jangan lupa untuk mentransfer cypress.json (awalnya akan muncul di root) ke folder utama-aplikasi-integrasi.

  4. Edit cypress.json, menentukan jalur ke folder baru dengan tes, plugins dan perintah tambahan (parameter integrasiFolder, pluginsFile, dan supportFile).

  5. Cypress dapat bekerja dengan tes di folder apa pun, parameter

    proyek digunakan untuk menentukan folder , jadi kami mengubah perintah dari cypress run / open ke cypress run / open -–project ./projects/main-app-integrations/src .



Solusi untuk Angular Workspace paling mirip dengan solusi untuk NX, kecuali bahwa kami membuat folder dengan tangan kami dan itu bukan salah satu proyek di repositori mono Anda. Atau, Anda dapat langsung menggunakan pembangun NX untuk Cypress ( contoh repositori NX dengan Cypress, Anda dapat melihat penggunaan akhir pembangun nx-cypress di sana - fokus pada angular.json dan proyek

cart-e2e dan produk-e2e).



Kemunduran visual



Setelah lima tes pertama, kami berpikir tentang pengujian tangkapan layar, karena, pada kenyataannya, ada semua kemungkinan untuk ini. Saya akan mengatakan sebelumnya bahwa kata "pengujian tangkapan layar" menyebabkan banyak kesusahan dalam tim, karena jalan untuk mendapatkan tes yang stabil bukanlah yang termudah. Selanjutnya, saya akan menjelaskan masalah utama yang kami temui dan solusinya.



Pustaka cypress-image-snapshot diambil sebagai solusi . Implementasinya tidak memakan banyak waktu, dan setelah 20 menit kami menerima tangkapan layar pertama dari aplikasi kami dengan ukuran 1000 Γ— 600 px. Ada banyak kegembiraan karena integrasi dan penggunaannya terlalu mudah, dan manfaatnya bisa sangat besar.



Setelah menghasilkan lima tangkapan layar referensi, kami meluncurkan tes di CI, sebagai hasilnya, bangunan berantakan. Ternyata screenshot yang diambil dengan perintah buka dan jalankan berbeda. Solusinya cukup sederhana: untuk mengambil tangkapan layar hanya dalam mode CI, untuk ini kami menghapus pengambilan tangkapan layar dalam mode lokal, misalnya:



Cypress.Commands.overwrite(
   'matchImageSnapshot',
   (originalFn, subject, fileName, options) => {
       if (Cypress.env('ci')) {
           return originalFn(subject, fileName, options);
       }

       return subject;
   },
);


Dalam solusi ini kita melihat parameter env di Cypress, dapat diatur dengan berbagai cara.



Font



Secara lokal, tes mulai lulus restart, kami mencoba menjalankannya lagi di CI. Hasilnya dapat dilihat di bawah ini:







Sangat mudah untuk melihat perbedaan font di screenshot yang berbeda. Tangkapan layar referensi dibuat di macOS, dan di CI, agen menginstal Linux.



Keputusan yang salah



Kami mengambil salah satu font standar (seperti itu adalah Font Ubuntu), yang memberikan perbedaan per-pixel minimal, dan menerapkan font ini untuk blok teks (dibuat dalam

index.html, yang dimaksudkan hanya untuk tes cypress). Kemudian meningkat perbedaan total menjadi 0,05% dan piksel dengan perbedaan menjadi 20%. Kami hidup dengan parameter seperti itu selama seminggu - sampai kasus pertama ketika perlu mengubah teks dalam komponen. Akibatnya, build tetap berwarna hijau, meskipun kami tidak memperbarui tangkapan layar. Solusi saat ini terbukti tidak berguna.



Solusi yang benar



Masalah aslinya adalah di lingkungan yang berbeda, solusinya, pada prinsipnya, menunjukkan dirinya sendiri - Docker. Sudah ada gambar buruh pelabuhan yang siap pakai untuk Cypress . Ada berbagai variasi gambar, kami tertarik untuk disertakan, karena Cypress sudah termasuk dalam gambar dan biner Cypress tidak akan diunduh dan dibongkar lagi setiap kali (GUI Cypress diluncurkan melalui file biner , dan pengunduhan serta pembongkaran membutuhkan waktu lebih lama daripada pengunduhan gambar buruh pelabuhan).

Berdasarkan gambar buruh pelabuhan yang disertakan, kami membuat wadah buruh pelabuhan kami, untuk ini kami membuat tes integrasi. File docker file dengan konten serupa:



FROM cypress:included:4.3.0
COPY package.json /app/
COPY package-lock.json app/
WORKDIR /app
RUN npm ci
COPY / /app/
ENTRYPOINT []


Saya ingin mencatat pembersihan ENTRYPOINT, ini disebabkan oleh fakta bahwa ia diset secara default pada cypress / image yang disertakan dan menunjuk ke perintah run cypress, yang mencegah kita menggunakan perintah lain. Kami juga membagi dockerfile kami menjadi beberapa lapisan sehingga setiap kali kami me-restart tes, kami tidak mengeksekusi ulang npm ci.



Kami menambahkan file .dockerignore (jika tidak) ke akar repositori dan kami harus menentukan node-modules / dan * / node-modules / di dalamnya.



Untuk menjalankan pengujian kami di Docker, mari kita menulis integrasi bash script-tests.sh dengan konten berikut:



docker build -t integrations -f integration-tests.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations


Deskripsi singkat: kami membuat uji integrasi docker-container kami. Fileocker dan arahkan volume ke folder dengan pengujian sehingga dimungkinkan untuk mendapatkan tangkapan layar yang dibuat dari Docker.



Font lagi



Setelah menyelesaikan masalah yang dijelaskan pada bab sebelumnya, ada ketenangan di build, tetapi sekitar satu hari kemudian kami mengalami masalah berikut (di kiri dan kanan - tangkapan layar dari komponen yang sama diambil pada waktu yang berbeda):







Saya pikir yang paling penuh perhatian memperhatikan bahwa tidak ada cukup judul dalam popup . Alasannya sangat sederhana - font tidak punya waktu untuk memuat, karena tidak terhubung melalui aset, tetapi ada di CDN.



Keputusan yang salah



Kami mengunduh font-font dari CDN, mengunggahnya ke aset untuk konfigurasi cypress dan

memasukkannya ke indeks kustom kami.html untuk pengujian integrasi. Dengan keputusan ini, kami menjalani waktu yang layak sampai kami mengubah font perusahaan. Tidak ada keinginan untuk memainkan cerita yang sama untuk kedua kalinya.



Solusi yang benar



Diputuskan untuk mulai preloading semua font yang diperlukan untuk pengujian di

index.html untuk konfigurasi cypress, tampilannya seperti ini:



<link	
      rel="preload"
      href="...."	
      as="font"	
      type="font/woff2"	
      crossorigin="anonymous"
/>


Jumlah tes macet karena font yang tidak punya waktu untuk memuat menurun ke minimum, tetapi tidak ke nol: masih, kadang-kadang font tidak berhasil memuat. Solusi dari KitchenSink of Cypress sendiri datang untuk menyelamatkan - waitForResource.

Dalam kasus kami, karena preloading font sudah terhubung, kami cukup mendefinisikan ulang perintah kunjungan di Cypress, sebagai hasilnya, itu tidak hanya menavigasi ke halaman, tetapi juga menunggu font yang ditentukan untuk dimuat. Saya juga ingin menambahkan bahwa waitForResource menyelesaikan masalah tidak hanya font, tetapi juga statika yang dimuat, seperti gambar (karena itu, tangkapan layar juga rusak, dan waitForResource banyak membantu). Setelah menerapkan solusi ini, tidak ada masalah dengan font dan statika pemuatan apa pun.



Animasi



Sakit kepala kami terhubung dengan animasi, yang masih ada hingga hari ini. Pada titik tertentu, tangkapan layar akan mulai muncul di mana elemen dianimasikan, atau tangkapan layar diambil sebelum animasi dimulai. Tangkapan layar semacam itu tidak stabil, dan dengan setiap perbandingan berikutnya dengan standar akan ada perbedaan. Jadi jalur apa yang kami ambil saat menyelesaikan masalah animasi?



Solusi pertama



Hal paling sederhana yang muncul di pikiran kita pada tahap awal: sebelum membuat tangkapan layar, hentikan browser untuk waktu tertentu sehingga animasi punya waktu untuk menyelesaikannya. Kami berjalan di sepanjang rantai 100ms, 200ms, 500ms dan akhirnya 1000ms. Menengok ke belakang, saya mengerti bahwa keputusan ini awalnya mengerikan, tetapi saya hanya ingin memperingatkan Anda terhadap keputusan yang sama. Mengapa mengerikan? Waktu animasi berbeda, agen di CI kadang-kadang juga bisa membosankan, itulah sebabnya setiap waktu menunggu stabilisasi halaman dari waktu ke waktu berbeda.



Solusi kedua



Bahkan dengan menunggu 1 detik, halaman tidak selalu berhasil menjadi stabil. Setelah sedikit riset, kami menemukan alat dari Angular - Testability. Prinsip ini didasarkan pada pemantauan stabilitas ZoneJS:



Cypress.Commands.add('waitStableState', () => {
   return cy.window().then(window => {
       const [testability]: [Testability] = window.getAllAngularTestabilities();

       return new Cypress.Promise(resolve => {
           testability.whenStable(() => {
               resolve();
           }, 3000);
       });
   });
});


Jadi, saat membuat tangkapan layar, kami memanggil dua perintah: cy.wait (1000) dan cy.waitStableState ().



Sejak itu, tidak ada tangkapan layar yang dijatuhkan secara acak, tetapi mari kita hitung bersama-sama berapa banyak waktu yang dihabiskan untuk browser yang tidak digunakan. Misalkan Anda memiliki 5 tangkapan layar dalam pengujian Anda, untuk masing-masing ada waktu tunggu stabil 1 detik dan beberapa waktu acak, katakanlah rata-rata 1,5 detik (saya tidak mengukur nilai rata-rata dalam kenyataan, jadi saya mengambilnya dari kepala saya sesuai dengan perasaan saya sendiri) . Akibatnya, kami menghabiskan 12,5 detik tambahan untuk membuat tangkapan layar dalam pengujian. Bayangkan Anda telah menulis 20 skrip uji, di mana setiap tes memiliki setidaknya 5 tangkapan layar. Kami mendapatkan bahwa kelebihan pembayaran untuk stabilitas adalah ~ 4 menit dengan 20 tes yang tersedia. 



Tetapi bahkan ini bukan masalah terbesar. Seperti dibahas di atas, ketika menjalankan tes secara lokal, tangkapan layar tidak dikejar, tetapi dalam CI mereka dikejar, dan karena harapan, panggilan balik dalam kode, misalnya, pada Waktu debounce, dipicu untuk setiap tangkapan layar, yang sudah membuat pengacakan dalam tes, karena di CI dan secara lokal mereka berlalu dengan cara yang berbeda.



Solusi saat ini



Mari kita mulai dengan animasi Sudut. Kerangka favorit kami menggantung kelas ng-animating pada elemen DOM selama animasi. Ini adalah kunci keputusan kami, karena sekarang kami harus memastikan bahwa elemen tersebut tidak memiliki kelas animasi sekarang. Akibatnya, itu berubah menjadi fungsi ini:



export function waitAnimation(element: Chainable<JQuery>): Chainable<JQuery> {
   return element.should('be.visible').should('not.have.class', 'ng-animating');
}


Sepertinya tidak ada yang rumit, tetapi inilah yang membentuk dasar dari keputusan kami. Apa yang ingin saya perhatikan dalam pendekatan ini: saat membuat tangkapan layar, Anda harus memahami animasi elemen mana yang dapat membuat tangkapan layar Anda tidak stabil, dan sebelum membuat tangkapan layar, tambahkan pernyataan yang akan memeriksa bahwa elemen tersebut tidak dianimasikan. Tetapi animasi juga bisa dalam CSS. Seperti yang dikatakan Cypress sendiri, setiap pernyataan tentang suatu elemen sedang menunggu animasi untuk menyelesaikannya - info lebih lanjut di sini dan di sini . Artinya, esensi dari pendekatan ini adalah sebagai berikut: kami memiliki elemen animasi, tambahkan pernyataan untuk itu - harus ('be.visible') / should ('not.be.visible')- dan Cypress sendiri akan menunggu animasi berakhir pada elemen (mungkin, omong-omong, solusi dengan ng-animating tidak diperlukan dan hanya pemeriksaan Cypress yang cukup, tetapi untuk saat ini kami menggunakan utilitas waitAnimation).



Seperti yang dinyatakan dalam dokumentasi itu sendiri, Cypress memeriksa perubahan posisi elemen pada halaman, tetapi tidak semua animasi tentang mengubah posisi, ada juga animasi fadeIn / fadeOut. Dalam kasus ini, prinsip solusinya sama: kami memverifikasi bahwa elemen tersebut terlihat / tidak terlihat pada halaman. 



Ketika bergerak dari solusi cy.wait (1000) + cy.waitStableState () untuk menungguAnimation dan Cypress Assertion, butuh ~ 2 jam untuk menstabilkan tangkapan layar lama, tetapi sebagai hasilnya kami mendapat + 20-30 detik alih-alih +4 menit untuk waktu eksekusi pengujian ... Saat ini, kami dengan hati-hati mendekati peninjauan tangkapan layar: kami memverifikasi bahwa itu tidak dilakukan selama animasi elemen DOM dan menambahkan pemeriksaan dalam tes untuk menunggu animasi. Sebagai contoh, kita sering menambahkan tampilan "kerangka" pada halaman sampai data dimuat. Oleh karena itu, ulasan segera menerima persyaratan bahwa saat membuat tangkapan layar, kerangka tidak boleh ada di DOM, karena ada animasi pudar di atasnya. 



Masalah dengan pendekatan ini adalah satu: tidak selalu mungkin untuk meramalkan semuanya saat membuat tangkapan layar dan itu masih bisa jatuh ke CI. Hanya ada satu cara untuk mengatasinya - pergi dan segera edit pembuatan tangkapan layar seperti itu, Anda tidak dapat menunda, jika tidak akan menumpuk seperti bola salju dan pada akhirnya Anda hanya akan mematikan tes integrasi.



Ukuran Screenshot



Anda mungkin telah memperhatikan fitur yang menarik: resolusi default tangkapan layar adalah 1000 Γ— 600 px. Sayangnya, ada masalah dengan ukuran jendela browser saat memulai di Docker: bahkan jika Anda mengubah ukuran viewport melalui Cypress, ini tidak akan membantu. Kami menemukan solusi untuk browser Chrome (untuk Electron, kami tidak dapat dengan cepat menemukan solusi yang berfungsi, dan kami tidak mendapatkan yang diusulkan dalam masalah ini ). Pertama, Anda perlu mengubah browser untuk menjalankan tes di Chrome:



  1. Bukan untuk NX kita melakukannya dengan menggunakan argumen chrome --browser ketika memulai perintah open / run cypress dan untuk perintah run kita tentukan parameter --headless.

  2. Untuk NX, dalam konfigurasi proyek di angular.json dengan tes, kami menentukan browser: parameter chrome, dan untuk konfigurasi yang akan berjalan di CI, kami tentukan headless: true.



Sekarang kami melakukan pengeditan di plugin dan mendapatkan tangkapan layar dengan ukuran 1440 Γ— 900 px:



module.exports = (on, config) => {
   on('before:browser:launch', (browser, launchOptions) => {
       if (browser.name === 'chrome' && browser.isHeadless) {
           launchOptions.args.push('--disable-dev-shm-usage');
           launchOptions.args.push('--window-size=1440,1200');

           return launchOptions;
       }

       return launchOptions;
   });
};


tanggal



Semuanya sederhana di sini: jika tanggal yang terkait dengan yang sekarang ditampilkan di suatu tempat, tangkapan layar yang diambil hari ini akan jatuh besok. Fixim sederhana:



cy.clock(new Date(2025, 11, 22, 0).getTime(), ['Date']);


Sekarang timer. Kami tidak mengganggu dan menggunakan opsi pemadaman saat membuat tangkapan layar, misalnya:



cy.matchImageSnapshot('salary_signing-several-payments', {
   blackout: ['.timer'],
});


Tes terkelupas



Dengan menggunakan rekomendasi di atas, Anda dapat mencapai stabilitas maksimum tes, tetapi tidak 100%, karena tes tidak hanya dipengaruhi oleh kode Anda, tetapi juga oleh lingkungan di mana mereka menjalankannya.



Akibatnya, persentase tertentu dari tes kadang-kadang akan jatuh, misalnya, karena kinerja agen kendur di CI. Pertama-tama, kami menstabilkan tes dari pihak kami: kami menambahkan pernyataan yang diperlukan sebelum mengambil tangkapan layar, tetapi untuk periode memperbaiki tes tersebut, Anda dapat mencoba kembali tes gagal menggunakan cypress-plugin-retries.



Kami memompa CI



Dalam bab-bab sebelumnya, kami belajar cara menjalankan tes dengan satu perintah dan belajar tentang bekerja dengan pengujian tangkapan layar. Sekarang Anda dapat melihat ke arah optimasi CI. Dalam membangun kami pasti akan dieksekusi:



  1. Perintah npm ci.
  2. Meningkatkan aplikasi dalam mode aot.
  3. Jalankan tes integrasi.


Mari kita lihat paragraf pertama dan kedua dan pahami bahwa langkah-langkah serupa dilakukan di build Anda yang lain di CI - build dengan rakitan aplikasi.

Perbedaan utama adalah tidak melayani, tetapi membangun. Jadi, jika kita bisa mendapatkan aplikasi yang sudah dirakit dalam sebuah build dengan tes integrasi dan menaikkan server dengan itu, maka kita dapat mengurangi waktu eksekusi build dengan tes.



Mengapa kami membutuhkannya? Hanya saja aplikasi kita besar dan memuaskan

npm ci + npm mulai dalam mode aot pada agen di CI memakan waktu ~ 15 menit, yang pada prinsipnya membutuhkan banyak upaya dari agen, dan tes integrasi juga berjalan di atas itu. Misalkan Anda telah menulis 20+ tes dan pada tes ke 19 browser Anda crash, di mana tes dijalankan, karena beban yang berat pada agen. Seperti yang Anda pahami, memulai ulang pembangunan lagi menunggu pemasangan dependensi dan peluncuran aplikasi.



Selanjutnya saya hanya akan berbicara tentang skrip di sisi aplikasi. Anda perlu menyelesaikan sendiri masalah pemindahan artefak di antara tugas-tugas di CI, jadi kami akan ingat bahwa build baru dengan tes integrasi akan memiliki akses ke aplikasi yang dirakit dari tugas pada build aplikasi Anda.



Server Statis



Kami membutuhkan pengganti layanan untuk meningkatkan server dengan aplikasi kami. Ada banyak opsi, saya akan mulai dengan yang pertama - angular-http-server . Tidak ada yang rumit dalam konfigurasinya: kami menginstal dependensi, menunjukkan di folder mana statika kami berada, menunjukkan port mana yang akan menaikkan aplikasi, dan senang.



Solusi ini cukup bagi kami selama 20 menit, dan kemudian kami menyadari bahwa kami ingin mem-proxy beberapa permintaan ke rangkaian pengujian. Gagal menghubungkan proxy-server-http-server. Solusi terakhir adalah meningkatkan server ke Express . Untuk mengatasi masalah tersebut, kami menggunakan express dan express-http-proxy itu sendiri. Kami akan mendistribusikan statika kami menggunakan

express.static, akibatnya kami mendapatkan skrip yang mirip dengan ini:



const express = require('express');
const appStaticPathFolder = './dist';
const appBaseHref = './my/app';
const port = 4200;
const app = express();

app.use((req, res, next) => {
   const accept = req
       .accepts()
       .join()
       .replace('*/*', '');

   if (accept.includes('text/html')) {
       req.url = baseHref;
   }

   next();
});
app.use(appBaseHref, express.static(appStaticPathFolder));
app.listen(port);


Poin yang menarik di sini adalah bahwa sebelum mendengarkan rute baseHref dari aplikasi, kami juga memproses semua permintaan dan mencari permintaan untuk index.html. Ini dilakukan untuk kasus-kasus ketika tes masuk ke halaman aplikasi yang jalurnya berbeda dari baseHref. Jika Anda tidak melakukan trik ini, maka ketika Anda pergi ke halaman aplikasi Anda, kecuali halaman utama, Anda akan menerima kesalahan 404. Sekarang tambahkan sejumput proxy:



const proxy = require('express-http-proxy');

app.use(
   '/common',
   proxy('https://qa-stand.ru', {
       proxyReqPathResolver: req => '/common' + req.url,
   }),
);


Mari kita lihat lebih dekat apa yang terjadi. Ada konstanta:



  1. appStaticForlderPath adalah folder tempat statika aplikasi Anda berada. 
  2. appBaseHref - mungkin aplikasi Anda memiliki baseHref, jika tidak - Anda dapat menentukan '/'.


Kami proxy semua permintaan dimulai dengan / umum, dan ketika proksi kami menyimpan jalur yang sama dengan permintaan menggunakan pengaturan proxyReqPathResolver. Jika Anda tidak menggunakannya, maka semua permintaan hanya akan masuk ke https://qa-stand.ru.



Kustomisasi index.html



Kami harus menyelesaikan masalah dengan custom index.html yang kami gunakan saat ng melayani aplikasi dalam mode Cypress. Mari kita menulis skrip sederhana di node.js. Kami memiliki index.modern.html sebagai parameter awal, kami perlu mengubahnya menjadi index.html dan menghapus skrip yang tidak perlu dari sana:



const fs = require('fs');
const appStaticPathFolder = './dist';

fs.copyFileSync(appStaticPathFolder + '/index.modern.html', appStaticPathFolder + '/index.html');

fs.readFile(appStaticPathFolder + '/index.html', 'utf-8', (err, data) => {
   const newValue = data
       .replace(
           '<script type="text/javascript" src="/auth.js"></script>',
           '',
       )
       .replace(
           '<script type="text/javascript" src="/analytics.js"></script>',
           '',
       );

   fs.writeFileSync(appStaticPathFolder + '/index.html', newValue, 'utf-8');
});


Skrip



Saya benar-benar tidak ingin menjalankan npm ci dari semua dependensi lagi untuk menjalankan tes di CI (setelah semua, ini sudah dilakukan dalam tugas dengan membangun aplikasi), jadi ide muncul untuk membuat folder terpisah untuk semua skrip ini dengan package.json saya. Mari beri nama folder tersebut, misalnya, script tes integrasi dan letakkan tiga file di sana: server.js, create-index.js, package.json. Dua file pertama dijelaskan di atas, sekarang mari kita menganalisis konten package.json:



{
 "name": "cypress-tests",
 "version": "0.0.0",
 "private": true,
 "scripts": {
   "create-index": "node ./create-index.js",
   "main-app:serve": "node ./server.js",
   "main-app:cy:run": "cypress run --project ./projects/main-app-integrations ",
   "main-app:integrations": "npm run create-index && start-server-and-test main-app:serve http://localhost:4200/my/app/ main-app:cy:run"
 },
 "devDependencies": {
   "@cypress/webpack-preprocessor": "4.1.0",
   "@types/express": "4.17.2",
   "@types/mocha": "5.2.7",
   "@types/node": "8.9.5",
   "cypress": "4.1.0",
   "cypress-image-snapshot": "3.1.1",
   "express": "4.17.1",
   "express-http-proxy": "^1.6.0",
   "start-server-and-test": "1.10.8",
   "ts-loader": "6.2.1",
   "typescript": "3.8.3",
   "webpack": "4.41.6"
 }
}


Dalam package.json hanya ada dependensi yang diperlukan untuk menjalankan tes integrasi ( dengan dukungan skrip dan pengujian tangkapan layar) dan skrip untuk memulai server, membuat index.html dan yang terkenal dari bab tentang meluncurkan tes integrasi di start-server-and-test Angular Workspace .



Lari



Kami membungkus implementasi tes integrasi di Dockerfile baru - integrasi-tes-ci.Dockerfile :



FROM cypress/included:4.3.0
COPY integration-tests-scripts /app/
WORKDIR /app
RUN npm ci
COPY projects/main-app-integrations /app/projects/main-app-integrations
COPY dist /app/dist
COPY tsconfig.json /app/
ENTRYPOINT []


Intinya sederhana: salin dan perluas folder integrasi-tes-skrip ke root aplikasi dan salin semua yang diperlukan untuk menjalankan tes (set ini mungkin berbeda untuk Anda). Perbedaan utama dari file sebelumnya adalah bahwa kami tidak menyalin seluruh aplikasi di dalam wadah buruh pelabuhan, hanya optimasi minimum waktu eksekusi pengujian dalam CI.



Buat file integrasi-test-ci.sh dengan konten berikut:



docker build -t integrations -f integration-tests-ci.Dockerfile .
docker run --rm -v $PWD/projects/main-app-integrations/src:/app/projects/main-app-integrations/src integrations:latest npm run main-app:integrations


Ketika Anda menjalankan perintah dengan tes, package.json dari folder script-tes integrasi akan menjadi root dan aplikasi-utama: perintah integrasi akan berjalan di dalamnya. Oleh karena itu, karena folder ini akan diperluas ke root, maka jalur ke folder dengan statika aplikasi Anda harus ditentukan dengan pemikiran bahwa semuanya akan diluncurkan dari root, dan bukan dari folder script tes-integrasi.



Saya juga ingin membuat komentar kecil: skrip bash terakhir untuk menjalankan tes integrasi saat berevolusi, saya menyebutnya berbeda. Anda tidak perlu melakukan ini, itu dilakukan hanya untuk kenyamanan membaca artikel ini. Anda harus selalu memiliki satu file tersisa, misalnya integrasi-tests.sh, yang sudah Anda kembangkan. Jika Anda memiliki beberapa aplikasi di repositori dan metode persiapannya berbeda, Anda bisa menyelesaikannya dengan variabel di bash, atau file yang berbeda untuk setiap aplikasi - tergantung pada kebutuhan Anda.



Ringkasan



Ada banyak informasi - saya pikir sekarang ada baiknya menyimpulkan berdasarkan apa yang ditulis di atas.

Persiapan alat untuk menulis lokal dan menjalankan tes dengan sejumput pengujian tangkapan layar:



  1. Tambahkan ketergantungan untuk Cypress.
  2. Mempersiapkan folder dengan tes:

    1. Aplikasi Tunggal Sudut - tinggalkan semuanya di folder cemara.
    2. Angular Workspace - membuat integrasi aplikasi nama folder di sebelah aplikasi yang akan dilawan oleh tes, dan memindahkan semuanya dari folder cypress ke sana.
    3. NX - ganti nama proyek dari appname-e2e ke appname-integrations.


  3. cypress- β€” build- Cypress, aot, index.html, environment prod- serve- Cypress ( , - prod-, ).
  4. :

    1. Angular Single Application β€” serve- cypress- , start-server-and-test.
    2. Angular Workspace β€” Angular Single Application, cypress run/open.
    3. NX β€” ng e2e.


  5. -:

    1. cypress-image-snapshot.
    2. CI.
    3. Dalam pengujian, kami tidak mengambil tangkapan layar secara acak. Jika tangkapan layar didahului oleh animasi, pastikan untuk menunggu - misalnya, tambahkan Cypress Assertion ke elemen animasi.
    4. Tanggal dapat dibasahi hingga jam s / d jam atau kami menggunakan opsi pemadaman saat mengambil tangkapan layar.
    5. Kami mengharapkan statika yang dimuat dalam runtime melalui perintah cy.waitForResource khusus (gambar, font, dll.).


  6. Kami membungkus semuanya di Docker:

    1. Mempersiapkan Dockerfile.
    2. Kami membuat file bash.




 Kami menjalankan tes di atas aplikasi yang dirakit:



  1. Di CI, kita belajar melempar artefak dari aplikasi yang dirakit antar build (terserah kamu).
  2. Mempersiapkan folder script-tes integrasi:

    1. Sebuah skrip untuk meningkatkan server aplikasi Anda.
    2. Sebuah skrip untuk mengubah index.html Anda (jika Anda puas dengan index.html awal, Anda dapat mengabaikannya).
    3. Tambahkan ke folder package.json dengan skrip dan dependensi yang diperlukan.
    4. Mempersiapkan Dockerfile baru.
    5. Kami membuat file bash.


tautan yang bermanfaat



  1. Angular Workspace + Cypress + CI β€” Angular Workspace CI , ( typescript).

  2. Cypress β€” trade-offs.

  3. Start-server-and-test β€” , .

  4. Cypress-image-snapshot β€” -.

  5. Cypress recipes β€” Cypress, .

  6. Flaky Tests β€” , Google.

  7. Github Action β€” Cypress GitHub Action, README , β€” wait-on. docker.




All Articles