TSM - Teste cu Selenium în contextul Continuous Delivery

Robert Lantos - Senior QA Engineer @Betfair

Ca parte a unui nou proiect web, una dintre țintele echipei de QA a fost să proiecteze și să ruleze o suită de regression rapidă și fiabilă ca parte a procesului de continous delivery. Menirea acestuia a fost ridicarea nivelului de încredere pentru fiecare build rulând un set întreg de teste în locul unui set specific de teste de tip sanity. Totuși, pentru ca acesta să reprezinte o soluție viabilă, trebuia ca testele să se execute rapid, în maxim 10 minute.

Reducerea timpului de execuție când suita conține un număr mare de teste, este mai dificilă decât ar crede mulți. Încă de la început am identificat trei mari provocări care trebuiau luate în considerare.

Execuția suitelor de teste ar trebui să fie rapidă

Un deployment tipic constă în execuția testelor pe cel puțin 2 noduri și un VIP. Fiecare dintre aceste rulări înseamnă execuția a 450 de teste font end si a 70 de backend. Aceasta înseamnă că e nevoie de cel puțin 3 rulări a setului de regression, iar ca să menținem timpii de execuție sub 30 de minute, rularea unui set nu trebuie să depășească 10 minute.

Nivelul de fiabilitate a testelor trebuie să fie mare

Pentru ca rularea testelor de regression să fie viabilă, trebuie să ne asigurăm că testele sunt fiabile și că nu există alerte false precum timpii de răspuns mari pe serverele interne folosite.

Pachetele de teste trebuie să fie scalabile

A trebuit să luăm în calcul și scalabilitatea pachetelor de teste în cazul în care noi teste vor fi adăugate, în felul acesta reușind să menținem timpii mici de execuție.

Folosind următoarele idei, am reușit să contruim o suită de teste care nu doar că rulează rapid dar este si suficient de fiabilă pentru un build de Continous Delivery. Am reușit să executăm în mod constant suite de teste de regression în aproximativ 7 minute fără a avea erori.

Următoarele idei reprezintă modul în care am reușit să finalizam cu succes acest proiect.

1. Tool-uri și medii de teste folosite

Testele se bazează pe RemoteWebDriver (grid) al Selenium și folosesc un environment de tip remote cu multiple noduri pentru o mai bună scalabilitate precum si un hub local cu un singur nod, pentru execuția testelor locale.

2. Paralelizarea testelor într-o măsură cât mai mare

Execuția rapidă a sute de teste nu este posibilă fără o oarecare paralelizare. Cu cât testele sunt mai paralelizabile cu atât execuția setului de regression poate să fie mai rapidă. Însă crearea unui pachet de teste ușor de paralelizat nu este simplă, pentru că fiecare metodă de teste trebuie proiectat cu multi-threading. Aceasta înseamnă că fiecare test trebuie creat ca un modul individual separat de celelalte.

Ca să putem urma strategia menționată anterior, folosim următoarea meodă pentru crearea unei instanțe de RemoteWebDriver:

MyDriver, în cazul nostru este o clasă personalizată care extinde clasa RemoteWebDriver, noi o folosim pentru a suprascrie unele funcții de bază al Seleniumului precum metoda getScreenshotAs.

Testele modulare oferă o scalabilitate ridicată. Astfel capabilitațile de paralelizare ale testNG-ului împreună cu un selenium grid cu noduri numeroase permit rularea unui număr mare de teste în același timp, reducând drastic durata de execuție a unei suite de tip regression.

În continuare, este prezentat un exemplu de clasă de teste care folosește metoda prezentată un pic mai sus:

Notă:

Probabil că ați observat că nu folosim @BeforeMethod pentru pregătirea metodelor de teste și nici @AfterMethod pentru curățarea instanțelor de WebDriver de după ele. Acest lucru se datorează unor probleme de thread safety pe timpul execuției unui număr mare de teste în paralel si mai ales când se folosește un DataProvider. Am ales în schimb să folosim listener-i custom-i pentru a suprascrie metodele de onTestFailure și onTestSuccess pentru a face teardown după fiecare test.

3. Folosirea PageObject-elor

În UI-ul aplicației web testat, sunt elemente cu care metodele de teste interacționează. Un PageObject pur si simplu modelează aceste elemente ca obiecte în cod. Acest lucru reduce numărul de cod duplicat, ceea ce înseamnă că, în cazul unei schimbari de UI, o singură modificare va ajunge pentru a repara testele.

4. Listeneri customizați și importanța sesiunilor bine administrate

Un listener customizat extinde TestListenerAdapter din testNG pentru a se folosi de obiectul ITestResult. Acești listener-i oferă o modalitate flexibilă de administrare a tuturor acțiunilor post test, și sunt folosite pentru curățarea instanțelor de WebDriver rămase active.

Următoarele fragmente de cod arată modul în care tratăm oprirea instanțelor de driver.

Custom onTestFailure

Custom onTestSuccess

Notă: AbstractTest din exemplul de mai sus reprezintă o clasă de tip setup extins de fiecare din testele noastre, Acesta tratează încărcarea contextelor de spring si inițializarea listener-ilor.

5. Stabilizarea testelor prin wait-uri dinamice.

Una dintre cele mai importante nevoi pentru ca pasul de regression să poată fi rulat în procesul de Continuous Delivery este stabilitatea testelor. Acestea trebuie să ruleze în mod constant fără rezultate fals positive și cu o rată de succes a testelor de 100%. Acest lucru însă este foarte greu de implementat, mai ales în cazul testeler de tip frontend. Sunt destul de multe probleme care pot să apară, precum: probleme cu dependințe, încărcarea lentă a unor pagini, defecte ș.a.. Pentru o mai bună stabilitate a testelor de regression am apelat la folosirea wait-urilor dinamice și a mecanismului de retry condițional.

Selenium încorporează un mecanism foarte util de așteptare, implicit wait face ca WebDriver să parseze DOM-ul pentru un anumit timp prestabilit atâta timp cât elementul căutat nu este disponibil. Odată ce un implicit wait este setat, acesta rămâne active pe toată durata de viață al instanței de WebDriver. Din păcate această metoda nu functionează în toate cazurile, mai ales când vine vorba de pagini cu mult java script. Pentru a compensa aceste lipsuri noi, am apelat la wait-uri condiționale.

Această metodă va astepta activarea Jquery-ului și pentru ca document.readyState să fie "complete":

Exemplul următor va aștepta până când se poate face click pe element:

Notă: Ambele exemple folosesc metoda wait.until(), condițiile pot să fie personalizate dar există și o serie de opțiuni predefinite ( precum elementToBeClickable)

6. Stabilizarea testelor prin retry-uri condiționale.

Așa cum am menționat și mai devreme un alt instrument de stabilizare a testelor este mecanismul de retry condițional. Prin simpla rerulare a testelor eșuate putem crește stabilitatea și rata de succes a testelor de regression. Totuși, această metoda are o vulnerabilite majoră, ea permite propagarea unor posibile defecte în cazul în care acestea nu apar în mod constant. Soluția noastră pentru această problemă a fost folosirea mecanismului de retry doar in unele condiții prestabilite. Acest lucru se face prin verificarea excepției generate de un test eșuat. Astfel doar testele eșuate cu o excepție așteptată vor fi rerulate.

7.  Soluții alternative

Una din cele mai frustrante probleme care pot să apară la testarea unor aplicații web în chrome este cea cu "Element is not clickable at point". Metoda click din Selenium încearcă să apese un element chiar în punctul ei central, însă din când în când, deși pagina s-a încărcat cu succes, elementul țintit mai trece printr-o fază de randare a poziției în pagină cauzând ratarea operației de click. Ca soluții alternative, noi am apelat la folosirea javascript-ului sau al obiectului Actions pentru a face click pe element.

jsClickElement

Actions

În cazul în care veți rula teste pe Internet Explorer, chiar dacă ultimele versiuni sunt comparabile cu browserele mai populare, mai sunt unele mici probleme. De exemplu, capabilitatea ACCEPT_SSL_CERTS nu funcționează cu versiunile recente de IE. O soluție alternativă a fost să folosim metoda următoare pentru a accepta certificatul.

Prin acest proiect am învățat mult despre capabilitățile Seleniumului, dovedindu-se că acesta poate fi un tool extrem de util. Pentru noi, această utilitate ne-a ajutat să integrăm cu succes pachetele de teste de regression în Continuous Delivery, oferind un mod rapid și fiabil de testare a produsului nostru.