de Todd Chaffee

Cum să scrieți teste de browser fiabile folosind Selenium și Node.js

Cum sa scrieti teste de browser fiabile folosind Selenium si

Există multe articole bune despre cum să începeți cu testarea automată a browserului folosind versiunea NodeJS de Selenium.

Unii încheie testele în Mocha sau Jasmine, iar alții automatizează totul cu npm sau Grunt sau Gulp. Toate acestea descriu cum să instalați ceea ce aveți nevoie, împreună cu un exemplu de cod de lucru de bază. Acest lucru este foarte util, deoarece lucrul pentru toate piesele pentru prima dată poate fi o provocare.

Dar nu reușesc să se implice în detaliile multora dintre ele și cele mai bune practici de automatizare a testării browserului dvs. atunci când utilizați Selenium.

Acest articol continuă acolo unde aceste alte articole nu mai sunt disponibile și vă va ajuta să scrieți teste automate ale browserului, care sunt mult mai fiabile și mai ușor de întreținut cu API-ul NodeJS Selenium.

Evitați să dormiți

Seleniul driver.sleep metoda este cel mai rău dușman al tău. Și toată lumea îl folosește. Acest lucru se poate datora faptului că documentația pentru versiunea Node.js a Seleniu este concis și acoperă doar sintaxa API. Îi lipsesc exemple din viața reală.

Sau se poate datora faptului că o mulțime de exemple de cod din articolele de pe blog și de pe site-urile de întrebări și răspunsuri precum StackOverflow îl folosesc.

Să presupunem că un panou animă de la o dimensiune zero, la dimensiunea completă. Hai sa ne uitam.

Cum sa scrieti teste de browser fiabile folosind Selenium si

Se întâmplă atât de repede încât nu puteți observa că butoanele și comenzile din interiorul panoului schimbă constant dimensiunea și poziția.

Iată o versiune încetinită. Acordați atenție butonului verde de închidere și puteți vedea dimensiunea și poziția schimbătoare a panoului.

1611922393 215 Cum sa scrieti teste de browser fiabile folosind Selenium si

Aceasta nu este niciodată o problemă pentru utilizatorii reali, deoarece animația se întâmplă atât de repede. Dacă ar fi fost suficient de lent, ca în cel de-al doilea videoclip, și ați încercat să faceți clic manual pe butonul de închidere în timp ce se întâmpla acest lucru, este posibil să faceți clic pe butonul greșit sau să pierdeți complet butonul.

Dar aceste animații se întâmplă de obicei atât de repede încât nu ai niciodată șansa să faci asta. Oamenii așteaptă doar finalizarea animației. Nu este adevărat în cazul seleniului. Este atât de rapid încât poate încerca să facă clic pe elemente care sunt încă animate și s-ar putea să primiți un mesaj de eroare precum:

System.InvalidOperationException : Element is not clickable at point (326, 792.5)

Atunci mulți programatori vor spune „Aha! Trebuie să aștept finalizarea animației, așa că o voi folosi driver.sleep(1000) să aștept ca panoul să fie utilizabil ”.

Deci care este problema?

driver.sleep(1000) declarația face ceea ce arată. Oprește executarea programului dvs. timp de 1000 de milisecunde și permite browserului să continue să funcționeze. Realizarea aspectului, decolorarea sau animarea elementelor, încărcarea paginii sau orice altceva.

Folosind exemplul de mai sus, dacă panoul s-a estompat într-o perioadă de 800 de milisecunde driver.sleep(1000)ar obișnuit realizează ceea ce vrei. Deci, de ce să nu o folosești?

Cel mai important motiv este că nu este determinat. Adică va funcționa doar o parte din timp. Deoarece funcționează doar o parte din timp, ajungem la teste fragile care se rup în anumite condiții. Acest lucru conferă testării automate a browserului un nume rău.

De ce funcționează doar o parte din timp? Cu alte cuvinte, de ce nu este determinist?

Ceea ce observați cu ochii nu este adesea singurul lucru care se întâmplă pe un site web. Un element de fade-in sau animație este un exemplu perfect. Nu ar trebui să observăm aceste lucruri dacă sunt făcute bine.

Dacă îi spuneți Selenium să găsească mai întâi un element și apoi faceți clic pe el, este posibil să existe doar câteva milisecunde între aceste două operații. Seleniul poate fi mult mai rapid decât un om.

Când un om folosește site-ul web, așteptăm ca elementul să dispară înainte de a face clic pe el. Și când acel fade-in durează mai puțin de o secundă, probabil că nici măcar nu observăm că facem „așteptarea” respectivă. Seleniul nu este doar mai rapid și mai puțin iertător, testele dvs. automate trebuie să se ocupe de tot felul de alți factori imprevizibili:

  1. Proiectantul paginii dvs. web poate schimba timpul de animație de la 800 milisecunde la 1200 milisecunde. Testul tău tocmai s-a rupt.
  2. Browserele nu fac întotdeauna exact ceea ce solicitați. Datorită încărcării sistemului, animația ar putea să se blocheze și să dureze mai mult de 800 de milisecunde, poate chiar mai mult decât somnul dvs. de 1000 de milisecunde. Testul tău tocmai s-a rupt.
  3. Browsere diferite au motoare de aspect diferite și prioritizează operațiunile de aspect în mod diferit. Adăugați un browser nou în suita de testare și testele tale tocmai s-au rupt.
  4. Browserele și JavaScript-ul care controlează o pagină sunt asincrone prin natura lor. Dacă animația din exemplul nostru schimbă funcționalitatea care necesită informații din back-end, programatorul poate adăuga un apel AJAX și poate aștepta rezultatul înainte de a declanșa animația.
    Acum avem de-a face cu latența rețelei și cu garanția zero a cât timp va dura panoul afișării. Testul tău tocmai s-a rupt.
  5. Există cu siguranță alte motive pe care nu le cunosc.
    Chiar unu browserul singur este o fiară complexă și toate au erori. Deci, vorbim despre încercarea de a face același lucru să funcționeze pe mai multe browsere diferite, mai multe versiuni diferite de browser, mai multe sisteme de operare diferite și mai multe versiuni diferite de sisteme de operare.
    La un moment dat testele tale se întrerup doar dacă nu sunt deterministe. Nu este de mirare că programatorii renunță la testarea automată a browserului și se plâng de cât de fragile sunt testele.

Ce fac de obicei programatorii pentru a remedia lucrurile atunci când se întâmplă oricare dintre cele de mai sus? Acestea urmăresc lucrurile înapoi la probleme de sincronizare, astfel încât răspunsul evident este să măriți timpul în declarația driver.sleep. Apoi încrucișați-le degetele pentru a acoperi toate scenariile viitoare posibile de încărcare a sistemului, diferențele de aspect ale motorului și așa mai departe. este nu determinist și se rupe, deci nu face asta!

Dacă încă nu sunteți convins, iată încă un motiv: testele dvs. vor rula mult mai repede. Sperăm că animația din exemplul nostru durează doar 800 de milisecunde. Pentru a face față „sperăm” și a face testele să funcționeze în toate condițiile, probabil că veți vedea ceva de genul driver.sleep(2000) în lumea reală.

Asta înseamnă mai mult de o secundă întreagă pierdută doar pentru un singur pas a testelor dvs. automate. În mai mulți pași, se adaugă rapid. Un test refactorizat recent pentru una dintre paginile noastre web, care a durat câteva minute din cauza utilizării excesive a driverului.sleep durează acum mai puțin de cincisprezece secunde.

Majoritatea restului acestui articol oferă exemple specifice cu privire la modul în care puteți face testele pe deplin deterministe și să evitați utilizarea driver.sleep.

O notă despre promisiuni

API-ul JavaScript pentru Selenium folosește intens promisiunile și, de asemenea, face o treabă bună de a ascunde acest lucru folosind un manager de promisiuni încorporat. Acest lucru se schimbă și va fi depreciat.

În viitor, va trebui fie să învățați cum să utilizați promisiunea înlănțuindu-vă, fie să folosiți noile funcții asincronizate JavaScript await.

În acest articol, exemplele folosesc încă tradiționalul manager de promisiuni Selenium încorporat și profită de înlănțuirea promisiunilor. Exemplele de cod de aici vor avea mai mult sens dacă înțelegeți cum funcționează promisiunile. Dar puteți totuși obține multe din acest articol dacă doriți să ignorați promisiunile de învățare pentru moment.

Să începem

Continuând cu exemplul nostru de buton pe care vrem să-l facem clic pe un panou care animă, să ne uităm la câteva gotcha-uri specifice care ar putea rupe testele noastre.

Ce zici de un element care este adăugat în mod dinamic la pagină și nici măcar nu există încă după ce pagina este terminată de încărcare?

Se așteaptă ca un element să fie prezent în DOM

Următorul cod nu ar funcționa dacă un element cu un ID CSS „butonul meu” ar fi adăugat la DOM după încărcarea paginii:

// Selenium initialization code left out for clarity
// Load the page.driver.get('https:/foobar.baz');
// Find the element.const button = driver.findElement(By.id('my-button'));
button.click();

driver.findElement metoda se așteaptă ca elementul să fie deja prezent în DOM. Se va erora dacă elementul nu poate fi găsit imediat. În acest caz, înseamnă imediat „după ce încărcarea paginii este completă” din cauza versiunii anterioare driver.get statement.

Amintiți-vă că versiunea actuală a JavaScript Selenium gestionează promisiunile pentru dvs. Deci, fiecare declarație se va completa complet înainte de a trece la următoarea declarație.

Notă: Comportamentul de mai sus nu este întotdeauna nedorit. driver.findElement de la sine ar putea fi de fapt util dacă sunteți sigur că elementul ar trebui să fie deja acolo.

Mai întâi să ne uităm la modul greșit de a remedia acest lucru. După ce i s-a spus că ar putea dura câteva secunde până când elementul va fi adăugat la DOM:

driver.get('https:/foobar.baz');
// Page has been loaded, now go to sleep for a few seconds.driver.sleep(3000);
// Pray that three seconds is enough and find the element.const button = driver.findElement(By.id('my-button'));
button.click();

Din toate motivele menționate mai devreme, acest lucru se poate rupe și probabil se va întâmpla. Trebuie să învățăm cum să așteptăm localizarea unui element. Acest lucru este destul de ușor și veți vedea acest lucru adesea în exemple de pe web. În exemplul de mai jos, folosim bine documentat driver.wait metoda de a aștepta până la douăzeci de secunde pentru ca elementul să fie găsit în DOM:

const button = driver.wait(  until.elementLocated(By.id('my-button')),   20000);
button.click();

Există avantaje imediate în acest sens. De exemplu, dacă elementul este adăugat la DOM într-o secundă, metoda driver.wait se va finaliza într-o secundă. Nu va aștepta cele douăzeci de secunde specificate.

Datorită acestui comportament, putem pune o mulțime de căptușeală în timpul nostru de expirare, fără a ne face griji că timpul de expirare încetinește testele noastre. spre deosebire de driver.sleep care va aștepta întotdeauna tot timpul specificat.

Acest lucru funcționează în multe cazuri. Dar un caz în care nu funcționează este încercarea de a face clic pe un element care este prezent în DOM, dar care nu este încă vizibil.

Seleniul este suficient de inteligent pentru a nu face clic pe un element care nu este vizibil. Acest lucru este bun, deoarece utilizatorii nu pot face clic pe elemente invizibile, dar ne face să lucrăm mai mult la crearea unor teste automate de încredere.

Se așteaptă până când un element este vizibil

Ne vom baza pe exemplul de mai sus, deoarece are sens să așteptăm ca un element să fie localizat înainte de a deveni vizibil.

De asemenea, veți găsi mai jos prima noastră utilizare a promisiunii:

const button = driver.wait(  until.elementLocated(By.id('my-button')),   20000).then(element => {   return driver.wait(     until.elementIsVisible(element),     20000   );});
button.click();

Aproape că ne-am putea opri aici și deja ai fi mult mai bine. Cu codul de mai sus, veți elimina o mulțime de cazuri de testare care altfel s-ar rupe deoarece un element nu este prezent imediat în DOM. Sau pentru că nu este vizibil imediat din cauza unor lucruri precum animația. Sau chiar din ambele motive.

Acum că înțelegeți tehnica, nu ar trebui să existe niciodată un motiv pentru a scrie codul de seleniu care nu este determinist. Asta nu înseamnă că acest lucru este întotdeauna ușor.

Când lucrurile devin mai dificile, dezvoltatorii renunță adesea și recurg la driver.sleep. Sper că, oferind și mai multe exemple, vă pot încuraja să vă faceți testele deterministe.

Scrierea propriilor condiții

Mulțumită until metodă, API-ul JavaScript Selenium are deja un o mână de metode de comoditate puteți folosi cu driver.wait. De asemenea, puteți aștepta până când un element nu mai există, pentru un element care conține un anumit text, pentru a fi prezentă o alertă sau pentru multe alte condiții.

Dacă nu găsiți ceea ce aveți nevoie în metodele de comoditate furnizate, va trebui să scrieți propriile condiții. Acest lucru este de fapt destul de ușor, dar este greu să găsești exemple. Și există un gotcha – la care vom ajunge.

Conform documentației, puteți furniza driver.wait cu o funcție care revine true sau false.

Să presupunem că am vrut să așteptăm ca un element să fie opacitatea deplină:

// Get the element.const element = driver.wait(  until.elementLocated(By.id('some-id')),  20000);
// driver.wait just needs a function that returns true of false.driver.wait(() => {   return element.getCssValue('opacity')          .then(opacity => opacity === '1');});

Acest lucru pare util și reutilizabil, așa că hai să-l punem într-o funcție:

const waitForOpacity = function(element) {  return driver.wait(element => element.getCssValue('opacity')          .then(opacity => opacity === '1');  );};

Și atunci putem folosi funcția noastră:

driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity);

Aici vine gotcha. Ce se întâmplă dacă vrem să facem clic pe element după ce acesta atinge opacitatea completă? Dacă încercăm să atribuim valoarea returnată de cele de mai sus, nu am obține ceea ce dorim:

const element = driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity);
// Oops, element is true or false, not an element.element.click();

Nu putem folosi nici înlănțuirea promisiunilor, din același motiv.

const element = driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity).then(element => {  // Nope, element is a boolean here too.  element.click();}); 

Acest lucru este ușor de remediat. Iată metoda noastră îmbunătățită:

const waitForOpacity = function(element) {  return driver.wait(element => element.getCssValue('opacity')          .then(opacity => {      if (opacity === '1') {        return element;      } else {        return false;    });  );};

Modelul de mai sus, care returnează elementul atunci când condiția este adevărată, și returnează fals în caz contrar, este un model reutilizabil pe care îl puteți utiliza atunci când scrieți propriile condiții.

Iată cum îl putem folosi cu înlănțuirea promisiunilor:

driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity).then(element => element.click());

Sau chiar:

const element = driver.wait(  until.elementLocated(By.id('some-id')),  20000).then(waitForOpacity);
element.click();

Scriind propriile condiții simple, vă puteți extinde opțiunile pentru a vă face testele deterministe. Dar asta nu este întotdeauna suficient.

Du-te negativ

Așa este, uneori trebuie să fii negativ în loc de pozitiv. Ceea ce vreau să spun prin aceasta este să testez dacă ceva nu mai există sau ca ceva să nu mai fie vizibil.

Să presupunem că un element există deja în DOM, dar nu ar trebui să interacționați cu acesta până când unele date nu sunt încărcate prin AJAX. Elementul ar putea fi acoperit cu un panou „încărcare…”.

Dacă ați acordat o atenție deosebită condițiilor oferite de until poate fi observat metode precum elementIsNotVisible sau elementIsDisabled sau nu atât de evident stalenessOfElement.

Puteți testa dacă un panou „se încarcă…” pentru a nu mai fi vizibil:

// Already added to the DOM, so this will return immediately.const desiredElement = driver.wait(  until.elementLocated(By.id('some-id')),  20000);
// But the element isn't really ready until the loading panel// is gone.driver.wait(  until.elementIsNotVisible(By.id('loading-panel')),  20000);
// Loading panel is no longer visible, safe to interact now.desiredElement.click();

Găsesc stalenessOfElement să fie deosebit de util. Se așteaptă până când un element a fost eliminat din DOM, ceea ce s-ar putea întâmpla și din reîmprospătarea paginii.

Iată un exemplu de așteptare a conținutului unui iframe pentru a reîmprospăta înainte de a continua:

let iframeElem = driver.wait(  until.elementLocated(By.className('result-iframe')),  20000  );
// Now we do something that causes the iframe to refresh.someElement.click();
// Wait for the previous iframe to no longer exist:driver.wait(  until.stalenessOf(iframeElem),  20000);
// Switch to the new iframe. driver.wait(  until.ableToSwitchToFrame(By.className('result-iframe')),  20000);
// Any following code will be relative to the new iframe.

Fii întotdeauna determinist și nu dormi

Sper că aceste exemple te-au ajutat să înțelegi mai bine cum să faci testele de seleniu deterministe. Nu vă bazați pe driver.sleep cu o presupunere arbitrară.

Dacă aveți întrebări sau tehnici proprii pentru a face determinist testarea seleniului, vă rugăm să lăsați un comentariu.