O călătorie de testare E2E Partea 1: STUI la WebdriverIO
Publicat: 2019-11-21Notă: aceasta este o postare de la #frontend@twiliosendgrid. Pentru alte postări de inginerie, accesați lista blogului tehnic.
Pe măsură ce arhitectura de front-end a lui SendGrid a început să se maturizeze în aplicațiile noastre web, am dorit să adăugăm un alt nivel de testare în plus față de nivelul nostru obișnuit de testare a unității și a integrării. Am căutat să construim noi pagini și funcții cu acoperire de testare E2E (end-to-end) cu instrumente de automatizare a browserului.
Am dorit să automatizăm testarea din perspectiva unui client și să evităm testele de regresie manuale acolo unde este posibil pentru orice modificări majore care pot apărea în orice parte a stivei. Am avut și avem în continuare următorul obiectiv: să oferim o modalitate de a scrie teste de automatizare E2E consistente, depanabile, menținute și valoroase pentru aplicațiile noastre frontend și de a integra cu CICD (integrare continuă și implementare continuă).
Am experimentat cu mai multe abordări tehnice până când am finalizat soluția noastră ideală pentru testarea E2E. La un nivel înalt, aceasta rezumă călătoria noastră:
- Construirea propriei noastre soluții interne personalizate Ruby Selenium, SiteTestUI aka STUI
- Trecerea de la STUI la un WebdriverIO bazat pe nod
- Nefiind mulțumit de nicio configurare și, în sfârșit, migrând la Cypress
Această postare de blog este una dintre cele două părți care documentează și evidențiază experiențele noastre, lecțiile învățate și compromisurile cu fiecare dintre abordările utilizate de-a lungul drumului, pentru a vă ghida pe dumneavoastră și pe alți dezvoltatori în cum să conectați testele E2E cu modele utile și strategii de testare.
Prima parte cuprinde luptele noastre timpurii cu STUI, cum am migrat la WebdriverIO și, totuși, am experimentat o mulțime de căderi similare cu STUI. Vom analiza modul în care am scris teste cu WebdriverIO, am dockerizat testele pentru a rula într-un container și, în cele din urmă, am integrat testele cu Buildkite, furnizorul nostru CICD.
Dacă doriți să treceți până la punctul în care ne aflăm cu testarea E2E astăzi, vă rugăm să continuați cu partea a doua, în timp ce trece prin migrarea noastră finală de la STUI și WebdriverIO la Cypress și modul în care am configurat-o în diferite echipe.
TLDR: Am experimentat dureri și lupte similare cu ambele soluții de ambalare Selenium, STUI și WebdriverIO, pe care în cele din urmă am început să căutăm alternative în Cypress. Am învățat o grămadă de lecții perspicace pentru a aborda scrierea testelor E2E și integrarea cu Docker și Buildkite.
Cuprins:
Prima incursiune în testarea E2E: siteTESUI aka STUI
Trecerea de la STUI la WebdriverIO
Pasul 1: Decizia privind dependențele pentru WebdriverIO
Pasul 2: Configurații de mediu și scripturi
Pasul 3: Implementarea locală a testelor ENE
Pasul 4: Dockerizarea tuturor testelor
Pasul 5: Integrarea cu CICD
Compensații cu WebdriverIo
Trec pe Cypress
Prima incursiune în testarea E2E: SiteTestUI aka STUI
Când au căutat inițial un instrument de automatizare a browserului, SDET-ii noștri (ingineri de dezvoltare software aflati în testare) au făcut propria noastră soluție internă personalizată, construită cu Ruby și Selenium, în special Rspec și un cadru personalizat Selenium numit Gridium. Am apreciat suportul pe mai multe browsere, capacitatea de a configura propriile noastre integrări personalizate cu TestRail pentru cazurile noastre de testare a inginerilor QA (Asigurarea calității) și gândul de a construi repo ideal pentru toate echipele de front-end pentru a scrie teste E2E într-o singură locație și pentru a fi rulați conform unui program.
În calitate de dezvoltator de front-end dornic să scrie câteva teste E2E pentru prima dată cu instrumentele construite de SDET-urile pentru noi, am început să implementăm teste pentru paginile pe care le-am lansat deja și ne-am gândit cum să configuram corect utilizatorii și datele inițiale pentru a ne concentra pe anumite părți ale caracteristica pe care am vrut să o testăm. Am învățat câteva lucruri grozave pe parcurs, cum ar fi formarea obiectelor de pagină pentru organizarea funcționalității de ajutor și selectoare de elemente cu care dorim să interacționăm pe pagină și am început să formăm specificații care au urmat această structură:
Am construit treptat suite de testare substanțiale pentru diferite echipe în același repo, urmând modele similare, dar în curând am experimentat multe frustrări care ne-ar încetini enorm progresul pentru noii dezvoltatori și contribuitorii consecvenți la STUI, cum ar fi:
- Pornirea și funcționarea a necesitat timp și efort considerabil pentru a instala toate driverele de browser, dependențele Ruby Gem și versiunile corecte înainte chiar de a rula suitele de testare. Uneori a trebuit să ne dăm seama de ce testele ar rula pe mașina cuiva față de mașina altei persoane și cum diferă setările lor.
- Suitele de testare au proliferat și au funcționat ore întregi până la finalizare. Deoarece toate echipele au contribuit la același repo, rularea în serie a tuturor testelor însemna să aștepte câteva ore pentru a rula suita generală de teste, iar mai multe echipe care împingeau coduri noi au condus potențial la un alt test întrerupt în altă parte.
- Am devenit frustrați de selectoarele CSS slăbite și selectoarele XPath complicate . Această imagine de mai jos explică suficient modul în care utilizarea XPath poate complica lucrurile și acestea au fost unele dintre cele mai simple.
- Testele de depanare au fost dureroase . Am avut probleme la depanarea erorilor vagi și, de obicei, nu aveam idee unde și cum au eșuat lucrurile. Nu am putut decât să rulăm testele în mod repetat și să observăm browserul pentru a deduce unde a eșuat și ce cod a fost responsabil pentru aceasta. Când un test a eșuat într-un mediu Docker în CICD fără prea multe de văzut în afară de ieșirile consolei, ne-am străduit să reproducem local și să rezolvăm problema.
- Am întâlnit bug-uri cu seleniu și încetineală . Testele s-au desfășurat lent din cauza tuturor solicitărilor trimise de la server la browser și uneori testele noastre se blocau complet atunci când încercam să selectăm multe elemente de pe pagină sau din motive necunoscute în timpul testelor.
- S-a petrecut mai mult timp reparând și săriind teste, iar testele de construcție programate întrerupte au început să fie ignorate. Testele nu au oferit valoare în semnificarea efectivă a erorilor reale din sistem.
- Echipele noastre de front-end s-au simțit deconectate de la testele E2E, deoarece exista într-un depozit separat de aplicațiile web respective. Adesea, aveam nevoie să avem ambele depozite deschise în același timp și să continuăm să privim înainte și înapoi între bazele de cod, în plus față de filele browserului, atunci când au rulat testele.
- Echipelor de front-end nu le-a plăcut trecerea contextului de la scrierea codului în JavaScript sau TypeScript zilnic la Ruby și nevoia să reînvețe cum să scrie teste ori de câte ori contribuie la STUI.
- Deoarece a fost prima luare pentru mulți dintre noi atunci când am contribuit la test, am căzut în o mulțime de antimodeluri , cum ar fi crearea stării prin interfața de utilizare pentru autentificare, nu facem suficient demontare sau configurare prin API și nu avem suficientă documentație de urmat pentru ceea ce face un test grozav.
În ciuda progresului nostru în ceea ce privește scrierea unui număr considerabil de teste E2E pentru mai multe echipe diferite într-un singur depozit și a învățat câteva modele utile pe care să le luăm cu noi, am avut dureri de cap cu experiența generală a dezvoltatorului, mai multe puncte de eșec și lipsa testelor valoroase și stabile. pentru a verifica întregul nostru stack.
Am apreciat o modalitate de a împuternici alți dezvoltatori de front-end și QA-uri să-și construiască propriile suite de testare E2E stabile cu JavaScript care rezidă cu propriul cod de aplicație pentru a promova reutilizarea, proximitatea și proprietatea asupra testelor. Acest lucru ne-a determinat să cercetăm WebdriverIO, un cadru Selenium bazat pe JavaScript pentru testele de automatizare a browserului, ca înlocuitor inițial pentru STUI, soluția internă Ruby Selenium personalizată.
Mai târziu ne-am confruntă cu căderile sale și, în cele din urmă, vom trece la Cypress (înainte rapid la Partea 2 aici, dacă lucrurile WebdriverIO nu vă atrag), dar am câștigat o experiență neprețuită în stabilirea infrastructurii standardizate în depozitul fiecărei echipe, integrând teste E2E în CICD pentru frontend-ul nostru. echipe și adoptând modele tehnice despre care merită documentate în călătoria noastră și pentru ca alții să învețe despre cine ar putea fi pe cale să intre în WebdriverIO sau în orice altă soluție de testare E2E.
Trecerea de la STUI la WebdriverIO
Când ne-am angajat pe WebdriverIO pentru a atenua, sperăm, frustrările pe care le-am experimentat, am experimentat ca fiecare echipă de front-end să-și convertească testele de automatizare existente scrise cu abordarea Ruby Selenium în teste WebdriverIO în JavaScript sau TypeScript și să comparăm stabilitatea, viteza, experiența dezvoltatorului și întreținerea generală a testele.
Pentru a realiza configurația noastră ideală de a avea teste E2E care se află în depozitele de aplicații ale echipelor frontend și care rulează atât în CICD, cât și în conductele programate, am recapitulat următorii pași care se aplică în general oricărei echipe care dorește să integreze un cadru de testare E2E cu obiective similare. :
- Instalarea și alegerea dependențelor pentru a se conecta cu cadrul de testare
- Stabilirea configurațiilor de mediu și a comenzilor de script
- Implementarea testelor E2E care trec local în diferite medii
- Dockerizarea testelor
- Integrarea testelor Dockerized cu furnizorul CICD
Pasul 1: Decizia privind dependențele pentru WebdriverIO
WebdriverIO oferă dezvoltatorilor flexibilitatea de a alege dintre multe cadre, reporteri și servicii pentru a începe testul. Acest lucru a necesitat o mulțime de reparații și cercetări pentru ca echipele să se stabilească pe anumite biblioteci pentru a începe.
Deoarece WebdriverIO nu este prescriptiv cu privire la ce să folosească, a deschis ușa echipelor de front-end pentru a avea biblioteci și configurații diferite, deși testele generale de bază ar fi consistente în utilizarea API-ului WebdriverIO.
Am ales să lăsăm fiecare dintre echipele de front-end să se personalizeze în funcție de preferințele lor și, de obicei, am ajuns să folosim Mocha ca cadru de testare, Mochawesome ca reporter, serviciul Selenium Standalone și suport Typescript. Am ales Mocha și Mochawesome datorită familiarității echipelor noastre și experienței anterioare cu Mocha, dar alte echipe au decis să folosească și alte alternative.
Pasul 2: Configurații de mediu și scripturi
După ce ne-am decis asupra infrastructurii WebdriverIO, aveam nevoie de o modalitate prin care testele noastre WebdriverIO să ruleze cu setări diferite pentru fiecare mediu. Iată o listă care ilustrează majoritatea cazurilor de utilizare a modului în care am dorit să executăm aceste teste și de ce am dorit să le susținem:
- Împotriva unui server de dezvoltare Webpack care rulează pe localhost (adică http://localhost:8000) și acel server de dezvoltare ar fi îndreptat către un anumit mediu API, cum ar fi testarea sau staging (adică https://testing.api.com sau https:// staging.api.com).
De ce? Uneori trebuie să facem modificări aplicației noastre web locale, cum ar fi adăugarea de selectoare mai specifice pentru testele noastre, pentru a interacționa cu elementele într-un mod mai robust sau eram în curs de dezvoltare a unei noi funcții și trebuia să ajustem și să validăm testele de automatizare existente. ar trece la nivel local împotriva noilor noastre modificări de cod. Ori de câte ori codul aplicației s-a schimbat și nu am ajuns încă la mediul implementat, am folosit această comandă pentru a ne rula testele pe aplicația noastră web locală. - Împotriva unei aplicații implementate pentru un anumit mediu (adică https://testing.app.com sau https://staging.app.com), cum ar fi testarea sau staging
De ce? Alteori, codul aplicației nu se schimbă, dar este posibil să fie nevoiți să ne modificăm codul de testare pentru a remedia unele deficiențe sau ne simțim suficient de încrezători pentru a adăuga sau șterge teste cu totul fără a face nicio modificare de front-end. Am folosit această comandă în mod intens pentru a actualiza sau a depana testele la nivel local împotriva aplicației implementate pentru a simula mai îndeaproape modul în care testele noastre rulează în conductele CICD. - Rularea într-un container Docker pe o aplicație implementată pentru un anumit mediu, cum ar fi testarea sau pregătirea
De ce? Acest lucru este destinat conductelor CICD, astfel încât să putem declanșa testele E2E care să fie rulate într-un container Docker, de exemplu, împotriva aplicației implementate de staging și să ne asigurăm că acestea trec înainte de implementarea codului în producție sau în rulările de testare programate într-o conductă dedicată. Când am configurat inițial aceste comenzi, am efectuat multe încercări și erori pentru a porni containerele Docker cu diferite valori ale variabilelor de mediu și a testat pentru a vedea testele corecte executate cu succes înainte de a le conecta cu furnizorul nostru CICD, Buildkite.
Pentru a realiza acest lucru, am configurat un fișier de configurare de bază general cu proprietăți partajate și multe fișiere specifice mediului, astfel încât fiecare fișier de configurare a mediului să fuzioneze cu fișierul de bază și să suprascrie sau să adauge proprietăți așa cum se cere pentru a rula. Am fi putut avea un fișier pentru fiecare mediu fără a fi nevoie de un fișier de bază, dar asta ar duce la o mulțime de duplicare în setările comune. Am ales să folosim o bibliotecă precum deepmerge
pentru a o gestiona pentru noi, dar este important de reținut că îmbinarea nu este întotdeauna perfectă cu obiecte sau matrice imbricate. Verificați întotdeauna configurațiile rezultate, deoarece pot duce la un comportament nedefinit atunci când există proprietăți duplicat care nu s-au îmbinat corect.
Am format un fișier de configurare de bază comun , wdio.conf.js
, astfel:
Pentru a se potrivi cu primul nostru caz major de utilizare de a rula teste E2E pe un server de dezvoltare webpack local care a indicat un API de mediu, am generat fișierul de configurare localhost, wdio.localhost.conf.js
, după cum urmează:
Observați că am îmbinat fișierul de bază și am adăugat proprietățile specifice localhost fișierului pentru a-l face mai compact și mai ușor de întreținut. De asemenea, folosim serviciul Selenium Standalone pentru a porni diferite tipuri de browsere, numite capabilități.
Pentru cel de-al doilea caz de utilizare al rulării testelor E2E pe o aplicație web implementată, am configurat fișierele de configurare a aplicației de testare și punere în pas, `wdio.testing.conf.js` și wdio.staging.conf.js
, similar cu acesta:
Aici am adăugat câteva variabile de mediu suplimentare la fișierele de configurare, cum ar fi acreditările de conectare pentru utilizatorii dedicați în staging și am actualizat `baseUrl` pentru a indica adresa URL a aplicației de staging implementată.
Pentru al treilea caz de utilizare al rulării testelor E2E într-un container Docker pe o aplicație web implementată în domeniul furnizorului nostru CICD, am configurat fișierele de configurare CICD , wdio.cicd.testing.conf.js
și wdio.cicd.staging.conf.js
, așa:
Observați cum nu mai folosim serviciul Selenium Standalone, deoarece mai târziu vom instala Selenium Chrome, Selenium Hub și codul aplicației în servicii separate într-un fișier Docker Compose. Această configurație prezintă, de asemenea, aceleași variabile de mediu ca și configurația de staging, cum ar fi acreditările de conectare și „baseUrl”, deoarece ne așteptăm să rulăm testele pe aplicația de staging implementată, iar singura diferență este că aceste teste sunt destinate să fie executate într-un container Docker. .
Cu aceste fișiere de configurare a mediului stabilite, am subliniat comenzile de script package.json
care ar servi drept bază pentru testarea noastră. Pentru acest exemplu, am prefixat comenzile cu „uitest” pentru a indica testele UI cu WebdriverIO și pentru că am încheiat și fișierele de testare cu *.uitest.js
. Iată câteva exemple de comenzi pentru mediul de pregătire:
Pasul 3: Implementarea locală a testelor E2E
Cu toate comenzile de testare la îndemână, am analizat testele în depozitul nostru STUI pentru a le converti în teste WebdriverIO. Ne-am concentrat pe testele de pagini de dimensiuni mici și medii și am început să aplicăm modelul de obiecte de pagină pentru a încapsula toată interfața de utilizare pentru fiecare pagină într-o manieră organizată.
Am putea avea fișiere structurate cu o grămadă de funcții auxiliare sau literale obiect sau orice altă strategie, dar cheia a fost să avem o modalitate consecventă de a livra rapid teste care pot fi întreținute și de a rămâne cu ea. Dacă fluxul UI sau elementele DOM s-au modificat pentru o anumită pagină, nu trebuia decât să refactorăm obiectul paginii asociat cu acesta și, eventual, codul de testare pentru a trece din nou testele.
Am implementat modelul de obiecte de pagină având un obiect de pagină de bază cu funcționalitate partajată de la care s-ar extinde toate celelalte obiecte de pagină. Am avut funcții precum deschidere pentru a oferi un API consecvent pentru toate obiectele paginii noastre pentru a „deschide” sau a vizita adresa URL a unei pagini în browser. Semăna cu ceva de genul:
Implementarea obiectelor specifice paginii a urmat același model de extindere de la clasa de bază de Page
și adăugarea selectoarelor la anumite elemente cu care doream să interacționăm sau asupra cărora dorim să ne afirmăm și funcții de ajutor pentru a efectua acțiuni pe pagină.
Observați cum am folosit clasa de bază deschisă prin super.open(...)
cu ruta specifică a paginii, astfel încât să putem vizita pagina cu acest apel, SomePage.open()
. De asemenea, am exportat clasa deja inițializată, astfel încât să putem face referire la elemente precum SomePage.submitButton
sau SomePage.tableRows
și să interacționăm cu sau să afirmăm asupra acestor elemente cu comenzile WebdriverIO. Dacă obiectul pagină a fost menit să fie partajat și inițializat cu proprietățile proprii ale membrilor într-un constructor, am putea, de asemenea, să exportăm clasa direct și să instanțiem obiectul pagină în fișierele de testare cu new SomePage(...constructorArgs)
.
După ce am așezat obiectele paginii cu selectoare și unele funcționalități de ajutor, am scris apoi testele E2E și am modelat în mod obișnuit această formulă de testare:
- Configurați sau demontați prin API ceea ce este necesar pentru a reseta condițiile de testare la punctul de pornire așteptat înainte de a rula testele reale.
- Conectați-vă la un utilizator dedicat pentru test, astfel încât ori de câte ori am vizitat paginile direct, să rămânem conectați și să nu trebuia să trecem prin interfața de utilizare. Am creat o funcție simplă de ajutor de
login
care preia un nume de utilizator și o parolă care efectuează același apel API pe care îl folosim pentru pagina noastră de autentificare și care în cele din urmă returnează jetonul nostru de autentificare necesar pentru a rămâne conectat și pentru a transmite anteturile solicitărilor API protejate. Alte companii pot avea și mai multe puncte finale sau instrumente interne personalizate pentru a crea rapid utilizatori noi cu date și configurații de bază, dar noi, din păcate, nu am avut unul suficient de dezvoltat. Am proceda în mod demodat și am crea utilizatori de testare dedicați în mediile noastre cu diferite configurații prin interfața de utilizare și, adesea, am întrerupt testele pentru pagini cu utilizatori diferiți pentru a evita ciocnirea resurselor și a rămâne izolați atunci când testele rulau în paralel. Trebuia să ne asigurăm că utilizatorii de testare dedicați nu erau atinși de alții, altfel testele s-ar rupe atunci când cineva, fără să știe, schimba cu unul dintre ei. - Automatizați pașii ca și cum un utilizator final ar interacționa cu caracteristica/pagina. În mod obișnuit, am vizita pagina care conține fluxul nostru de caracteristici și am începe să urmăm pașii de nivel înalt pe care i-ar face utilizatorul final, cum ar fi completarea intrărilor, clic pe butoane, așteptarea apariției modalelor sau bannerelor și observarea tabelelor pentru ieșirile modificate ca un rezultat al acțiunii. Folosind obiectele și selectoarele noastre la îndemână, am implementat rapid fiecare pas și, pe măsură ce verificăm pe parcurs, vom afirma despre ceea ce utilizatorul ar trebui sau nu ar trebui să vadă pe pagină în timpul fluxului de caracteristici pentru a fi siguri că lucrurile se comportă conform așteptărilor. înainte și după fiecare pas. Am fost, de asemenea, deliberați să alegem teste de drum fericit de mare valoare și, uneori, stări de eroare comune ușor de reprodus, amânând restul testării de nivel inferior la testele unitare și de integrare.
Iată un exemplu aproximativ al aspectului general al testelor noastre E2E (această strategie aplicată și altor cadre de testare pe care le-am încercat):
Pe o notă secundară, am optat să nu acoperim toate sfaturile și problemele pentru cele mai bune practici WebdriverIO și E2E din această serie de postări de blog, dar vom vorbi despre aceste subiecte într-o postare viitoare de blog, așa că rămâneți pe fază!
Pasul 4: Dockerizarea tuturor testelor
Când executăm fiecare pas de conductă Buildkite pe o nouă mașină AWS în cloud, nu am putea numi pur și simplu „npm run uitests:staging” deoarece acele mașini nu au Node, browsere, codul aplicației noastre sau orice alte dependențe pentru a rula efectiv testele. .
Pentru a rezolva acest lucru, am grupat toate dependențele precum Node, Selenium, Chrome și codul aplicației într-un container Docker pentru ca testele WebdriverIO să ruleze cu succes. Am profitat de Docker și Docker Compose pentru a asambla toate serviciile necesare pentru a începe și a funcționa, care s-au tradus în Dockerfiles
și docker-compose.yml
și multe experimente cu întocmirea locală a containerelor Docker pentru ca lucrurile să funcționeze.
Pentru a oferi mai mult context, nu eram experți în Docker, așa că a fost nevoie de un timp considerabil pentru a înțelege cum să punem lucrurile cap la cap. Există mai multe moduri de a Dockerize testele WebdriverIO și ne-a fost dificil să orchestram multe servicii diferite împreună și să parcurgem diferite imagini Docker, versiuni Compose și tutoriale până când lucrurile au funcționat.
Vom demonstra în mare parte fișiere concrete care se potrivesc cu una dintre configurațiile echipelor noastre și sperăm că acest lucru oferă informații pentru dvs. sau pentru oricine abordează problema generală a testelor bazate pe Dockerizing Selenium.
La nivel înalt, testele noastre au cerut următoarele:
- Selenium pentru a executa comenzi împotriva și a comunica cu un browser. Am folosit Selenium Hub pentru a porni mai multe instanțe după bunul plac și am descărcat imaginea, „selenium/hub”, pentru serviciul
selenium-hub
în fișierul docker-compose. - Un browser pe care să rulați. Am adus în discuție instanțe Selenium Chrome și am instalat imaginea, „selenium/node-chrome-debug”, pentru serviciul
selenium-chrome
îndocker-compose.yml file
. - Cod de aplicație pentru a rula fișierele noastre de testare cu orice alte module Node instalate. Am creat un nou
Dockerfile
pentru a oferi un mediu cu Node pentru a instala pachete npm și a rula scripturipackage.json
, a copia codul de testare și a atribui un serviciu dedicat rulării fișierelor de testare numiteuitests
în fișieruldocker-compose.yml
.
Pentru a aduce un serviciu cu toate aplicațiile și codul nostru de testare necesar pentru a rula testele WebdriverIO, am creat un Dockerfile
numit Dockerfile.uitests
și am instalat toate node_modules
și am copiat codul în directorul de lucru al imaginii într-un mediu Node. Acesta ar fi folosit de serviciul nostru uitests
Docker Compose și am realizat configurarea Dockerfile
în următorul mod:
Pentru a aduce împreună Selenium Hub, browserul Chrome și codul de testare al aplicației pentru a rula testele WebdriverIO, am subliniat serviciile selenium-hub
, selenium-chrom
e și uitest
s în fișierul docker-compose.uitests.yml
:
Am conectat Selenium Hub și imaginile Chrome prin variabile de mediu, depends_on
și expunând porturile la servicii. Imaginea codului aplicației noastre de testare va fi în cele din urmă împinsă și extrasă dintr-un registru Docker privat pe care îl gestionăm.
Am construi imaginea Docker pentru codul de testare în timpul CICD cu anumite variabile de mediu, cum ar fi VERSION
și PIPELINE_SUFFIX
, pentru a face referire la imagini printr-o etichetă și un nume mai specific. Apoi vom porni serviciile Selenium și vom executa comenzi prin serviciul uitests
pentru a executa testele WebdriverIO.
Pe măsură ce ne-am construit fișierele Docker Compose, am folosit comenzile utile precum docker-compose up
și docker-compose down
cu Mac Docker instalat pe mașinile noastre pentru a testa local imaginile noastre aveau configurațiile adecvate și funcționau fără probleme înainte de integrarea cu Buildkite. Am documentat toate comenzile necesare pentru a construi imaginile etichetate, a le împinge în registru, a le trage în jos și a rula testele în funcție de valorile variabilelor de mediu.
Pasul 5: Integrarea cu CICD
După ce am stabilit comenzi Docker funcționale și testele noastre au rulat cu succes într-un container Docker în diferite medii, am început să ne integrăm cu Buildkite, furnizorul nostru CICD.
Buildkite a oferit modalități de a executa pași într-un fișier .yml
pe mașinile noastre AWS cu scripturi Bash și variabile de mediu setate fie prin cod, fie prin interfața de utilizare a setărilor Buildkite pentru conducta depozitului nostru.
Buildkite ne-a permis, de asemenea, să declanșăm această conductă de testare din conducta noastră principală de implementare cu variabile de mediu exportate și am reutiliza acești pași de testare pentru alte conducte de testare izolate care ar rula într-un program pentru monitorizarea și examinarea de către QA-urile noastre.
La un nivel înalt, conductele noastre de testare Buildkite pentru WebdriverIO și mai târziu Cypress au împărtășit următorii pași similari:
- Configurați imaginile Docker . Construiți, etichetați și împingeți imaginile Docker necesare pentru teste până în registru, astfel încât să le putem trage în jos într-un pas ulterior.
- Rulați testele pe baza configurațiilor variabilelor de mediu . Trageți în jos imaginile Docker etichetate pentru versiunea specifică și executați comenzile adecvate într-un mediu implementat pentru a rula suitele de testare selectate din variabilele de mediu setate.
Iată un exemplu apropiat de fișier pipeline.uitests.yml
care demonstrează configurarea imaginilor Docker în pasul „Build UITests Docker Image” și rularea testelor în pasul „Run Webdriver tests against Chrome”:
Un lucru de observat este primul pas, „Build UITests Docker Image” și modul în care configurează imaginile Docker pentru test. A folosit comanda de compilare Docker Compose pentru a build
serviciul uitests
cu tot codul de testare al aplicației și l-a etichetat cu latest
și variabilă de mediu ${VERSION}
, astfel încât în viitor să putem derula aceeași imagine cu eticheta adecvată pentru această versiune. Etapa.
Fiecare pas se poate executa pe o altă mașină din cloudul AWS undeva, astfel încât etichetele identifică în mod unic imaginea pentru rularea Buildkite specifică. După ce am etichetat imaginea, am ridicat cea mai recentă imagine și versiunea etichetată în registrul nostru privat Docker pentru a fi reutilizată.
În pasul „Rulează testele Webdriver pe Chrome”, tragem în jos imaginea pe care am construit-o, am etichetat și am împins în primul pas și pornim Selenium Hub, Chrome și serviciile de teste. Pe baza variabilelor de mediu, cum ar fi $UITESTENV
și $UITESTSUITE
, vom alege și alege tipul de comandă care să ruleze ca npm run uitest:
și suitele de testare care să ruleze pentru această versiune Buildkite specifică, cum ar fi --suite $UITESTSUITE
.
Aceste variabile de mediu ar fi setate prin setările conductei Buildkite sau ar fi declanșate dinamic dintr-un script Bash care ar analiza un câmp de selectare Buildkite pentru a determina ce suite de testare să ruleze și în ce mediu.
Iată un exemplu de teste WebdriverIO declanșate într-o conductă de teste dedicată, care a reutilizat și același fișier pipeline.uitests.yml
, dar cu variabile de mediu setate unde a fost declanșată conducta. Această versiune a eșuat și a avut capturi de ecran de eroare pe care să le aruncăm în fila Artifacts
și la ieșirea consolei în fila Logs
. Amintiți-vă de artifact_paths
din pipeline.uitests.yml
(https://gist.github.com/alfredlucero/71032a82f3a72cb2128361c08edbcff2#file-pipeline-uitests-yml-L38), setările de capturi de ecran pentru mocha.wejcon` ` (https://gist.github.com/alfredlucero/4ee280be0e0674048974520b79dc993a#file-wdio-conf-js-L39), și montarea volumelor în serviciul `uitests` în `docker-compose.y.uitest` (https://gist.github.com/alfredlucero/d2df4533a4a49d5b2f2c4a0eb5590ff8#file-docker-compose-yml-L32)?
Am reușit să conectăm capturile de ecran pentru a fi accesibile prin interfața de utilizare Buildkite pentru ca noi să le descărcam direct și să le vedem imediat pentru a ajuta la testele de depanare, așa cum se arată mai jos.
Un alt exemplu de teste WebdriverIO care rulează într-o conductă separată pe o programare pentru o anumită pagină, folosind fișierul pipeline.uitests.yml
, cu excepția variabilelor de mediu deja configurate în setările conductei Buildkite, este afișat dedesubt.
Este important de reținut că fiecare furnizor CICD are diferite funcționalități și modalități de a integra pași într-un fel de proces de implementare atunci când îmbină codul nou, indiferent dacă este prin fișiere .yml
cu sintaxă specifică, setări GUI, scripturi Bash sau orice alte mijloace.
Când am trecut de la Jenkins la Buildkite, am îmbunătățit drastic capacitatea echipelor de a-și defini propriile conducte în bazele de cod respective, paralelizând pașii pe mașinile de scalare la cerere și utilizând comenzi mai ușor de citit.
Indiferent de furnizorul CICD pe care îl puteți utiliza, strategiile de integrare a testelor vor fi similare în configurarea imaginilor Docker și rularea testelor pe baza variabilelor de mediu pentru portabilitate și flexibilitate.
Compensații cu WebdriverIO
După ce am convertit un număr considerabil de teste personalizate ale soluției Ruby Selenium în teste WebdriverIO și ne-am integrat cu Docker și Buildkite, ne-am îmbunătățit în unele domenii, dar am simțit totuși lupte similare cu vechiul sistem care ne-au condus în cele din urmă la următoarea și ultima noastră oprire cu Cypress pentru soluția noastră de testare E2E.
Iată o listă a unora dintre profesioniștii pe care i-am găsit din experiențele noastre cu WebdriverIO în comparație cu soluția personalizată Ruby Selenium:
- Testele au fost scrise doar în JavaScript sau TypeScript, mai degrabă decât în Ruby . Acest lucru a însemnat mai puțină schimbare a contextului între limbi și mai puțin timp petrecut reînvățând Ruby de fiecare dată când am scris teste E2E.
- Am colocat testele cu codul aplicației, mai degrabă decât departe într-un depozit partajat Ruby. Nu ne mai simțeam dependenți de eșecul testelor altor echipe și ne-am asumat mai multă proprietate directă asupra testelor E2E pentru funcțiile noastre din depozitele noastre.
- Am apreciat opțiunea de testare între browsere . Cu WebdriverIO am putut face teste pentru diferite capacități sau browsere, cum ar fi Chrome, Firefox și IE, deși ne-am concentrat în principal pe rularea testelor pe Chrome, deoarece peste 80% dintre utilizatorii noștri ne-au vizitat aplicația prin Chrome.
- Am avut posibilitatea de a ne integra cu servicii terțe . Documentația WebdriverIO a explicat cum să se integreze cu servicii terțe, cum ar fi BrowserStack și SauceLabs, pentru a ajuta la acoperirea aplicației noastre pe toate dispozitivele și browserele.
- Am avut flexibilitatea de a ne alege propriul tester, reporter și servicii . WebdriverIO nu a fost prescriptiv cu ce să folosească, așa că fiecare echipă și-a luat libertatea de a decide dacă să folosească sau nu lucruri precum Mocha și Chai sau Jest și alte servicii. Acest lucru ar putea fi interpretat și ca un dezamăgire, deoarece echipele au început să se îndepărteze de configurația celeilalte și a necesitat o perioadă considerabilă de timp pentru a experimenta fiecare dintre opțiunile pe care să le alegem.
- API-ul WebdriverIO, CLI și documentația au fost suficient de utile pentru a scrie teste și a se integra cu Docker și CIC D. Am putea avea multe fișiere de configurare diferite, să grupăm specificații, să executăm teste prin linia de comandă și să scriem teste urmând modelul obiectului paginii. Cu toate acestea, documentația ar putea fi mai clară și a trebuit să cercetăm o mulțime de erori ciudate. Cu toate acestea, am reușit să ne transformăm testele din soluția Ruby Selenium.
Am făcut progrese în multe domenii care ne lipseau în soluția anterioară Ruby Selenium, dar am întâlnit o mulțime de avarii care ne-au împiedicat să mergem all-in cu WebdriverIO, cum ar fi următoarele:
- Deoarece WebdriverIO era încă bazat pe seleniu , am experimentat o mulțime de timeout-uri, blocări și erori ciudate, amintindu-ne de flashback-uri negative cu vechea noastră soluție Ruby Selenium. Uneori, testele noastre se blocau complet atunci când selectăm multe elemente de pe pagină și testele rulau mai lent decât ne-am dori. A trebuit să găsim soluții pentru o mulțime de probleme Github sau să evităm anumite metodologii atunci când scriem teste.
- Experiența generală a dezvoltatorului a fost suboptimă . Documentația a oferit o privire de ansamblu la nivel înalt asupra comenzilor, dar nu suficiente exemple pentru a explica toate modalitățile de utilizare. Am evitat să scriem teste E2E cu Ruby și, în cele din urmă, am ajuns să scriem teste în JavaScript sau TypeScript, dar API-ul WebdriverIO a fost puțin confuz. Câteva exemple comune au fost utilizarea
$
vs.$$
pentru elemente singular vs plural,$('...').waitForVisible(9000, true)
pentru așteptarea ca un element să nu fie vizibil și alte comenzi neintuitive. Ne-am confruntat cu o mulțime de selectoare defectuoase și a trebuit în mod explicit$(...).waitForVisible()
pentru tot. - Testele de depanare au fost extrem de dureroase și plictisitoare pentru dezvoltatori și QA. Whenever tests failed, we only had screenshots, which would often be blank or not capturing the right moment for us to deduce what went wrong, and vague console error messages that did not point us in the right direction of how to solve the problem and even where the issue occurred. We often had to re-run the tests many times and stare closely at the Chrome browser running the tests to hopefully put things together as to where in the code our tests failed. We used things like
browser.debug()
but it often did not work or did not provide enough information. We gradually gathered a bunch of console error messages and mapped them to possible solutions over time but it took lots of pain and headache to get there. - WebdriverIO tests were tough to set up with Docker . We struggled with trying to incorporate it into Docker as there were many tutorials and ways to do things in articles online, but it was hard to figure out a way that worked in general. Hooking up 2 to 3 services together with all these configurations led to long trial and error experiments and the documentation did not guide us enough in how to do that.
- Choosing the test runner, reporter, assertions, and services demanded lots of research time upfront . Since WebdriverIO was flexible enough to allow other options, many teams had to spend plenty of time to even have a solid WebdriverIO infrastructure after experimenting with a lot of different choices and each team can have a completely different setup that doesn't transfer over well for shared knowledge and reuse.
To summarize our WebdriverIO and STUI comparison, we analyzed the overall developer experience (related to tools, writing tests, debugging, API, documentation, etc.), test run times, test passing rates, and maintenance as displayed in this table:
Moving On to Cypress
At the end of the day, our WebdriverIO tests were still flaky and tough to maintain. More time was still spent debugging tests in dealing with weird Selenium issues, vague console errors, and somewhat useful screenshots than actually reaping the benefits of seeing tests fail for when the backend or frontend encountered issues.
We appreciated cross-browser testing and implementing tests in JavaScript, but if our tests could not pass consistently without much headache for even Chrome, then it became no longer worth it and we would then simply have a STUI 2.0.
With WebdriverIO we still strayed from the crucial aspect of providing a way to write consistent, debuggable, maintainable, and valuable E2E automation tests for our frontend applications in our original goal. Overall, we learned a lot about integrating with Buildkite and Docker, using page objects, and outlining tests in a structured way that will transfer over to our final solution with Cypress.
If we felt it was necessary to run our tests in multiple browsers and against various third-party services, we could always circle back to having some tests written with WebdriverIO, or if we needed something fully custom, we would revisit the STUI solution.
Ultimately, neither solution met our main goal for E2E tests, so follow us on our journey in how we migrated from STUI and WebdriverIO to Cypress in part 2 of the blog post series.