Idei pentru configurarea, organizarea și consolidarea testelor dvs. Cypress #frontend@twiliosendgrid

Publicat: 2020-12-15

Am scris multe teste Cypress în diferite aplicații web și echipe frontend aici, la Twilio SendGrid. Pe măsură ce testele noastre s-au extins la multe caracteristici și pagini, am dat peste câteva opțiuni de configurare utile și am dezvoltat modalități de a ne menține mai bine numărul tot mai mare de fișiere, selectoare pentru elementele paginii noastre și valorile de timeout pe parcurs.

Ne propunem să vă arătăm aceste sfaturi și idei pentru configurarea, organizarea și consolidarea propriilor lucruri legate de Cypress, așa că nu ezitați să faceți cu ele ceea ce funcționează cel mai bine pentru dvs. și echipa dvs.

Această postare de blog presupune că aveți unele cunoștințe practice despre testele Cypress și că sunteți în căutarea unor idei pentru o mai bună întreținere și îmbunătățire a testelor Cypress. Cu toate acestea, dacă sunteți curios să aflați mai multe despre funcțiile, afirmațiile și modelele obișnuite pe care le puteți găsi utile pentru a scrie teste Cypress în medii separate, puteți consulta în schimb această postare de blog de o mie de picioare.

În caz contrar, să continuăm și să vă ghidăm mai întâi prin câteva sfaturi de configurare pentru Cypress.

Configurarea cypress.json

Fișierul cypress.json este locul în care puteți seta toată configurația pentru testele Cypress, cum ar fi timeout-urile de bază pentru comenzile Cypress, variabilele de mediu și alte proprietăți.

Iată câteva sfaturi pe care să le încercați în configurația dvs.:

  • Reglați-vă intervalele de timp pentru comenzile de bază, astfel încât să nu fie necesar să adăugați întotdeauna { timeout: timeoutInMs } în comenzile Cypress. Lucrați cu numerele până când găsiți echilibrul potrivit pentru setări precum „defaultCommandTimeout”, „requestTimeout” și „responseTimeout”.
  • Dacă testul dvs. implică cadre iframe, cel mai probabil va trebui să setați „chromeWebSecurity” la false, astfel încât să puteți accesa cadre iframe cu origini încrucișate în aplicația dvs.
  • Încercați să blocați scripturile de marketing, analiză și înregistrare de la terțe părți atunci când executați testele Cypress pentru a crește viteza și a nu declanșa evenimente nedorite. Puteți configura cu ușurință o listă de refuz cu proprietatea lor „blacklistHosts” (sau proprietatea „blockHosts” în Cypress 5.0.0), luând o serie de globuri de șir care se potrivesc cu căile gazdei terță parte.
  • Ajustați dimensiunile implicite ale ferestrei de vizualizare cu „viewportWidth” și „viewportHeight” pentru când deschideți interfața Cypress pentru a face lucrurile mai ușor de văzut.
  • Dacă există probleme legate de memorie în Docker pentru teste grele sau dacă doriți să ajutați la eficientizarea lucrurilor, puteți încerca să modificați „numTestsKeptInMemory” la un număr mai mic decât valoarea implicită și să setați „videoUploadOnPasses” la false pentru a vă concentra pe încărcarea videoclipurilor pentru testul eșuat. aleargă numai.

În altă ordine de idei, după ce ți-ai ajustat configurația Cypress, poți adăuga și TypeScript și să tastați mai bine testele Cypress așa cum am făcut în această postare de blog. Acest lucru este benefic în special pentru completarea automată, avertismentele de tip sau erorile la apelarea și înlănțuirea funcțiilor (cum ar fi cy.task('someTaskPluginFunction) , Cypress.env('someEnvVariable') sau cy.customCommand() ).

De asemenea, poate doriți să experimentați configurarea unui fișier cypress.json de bază și încărcarea unui fișier de configurare Cypress separat pentru fiecare mediu de testare, cum ar fi staging.json , atunci când rulați diferite scripturi Cypress în package.json . Documentația Cypress face o treabă grozavă de a vă ghida prin această abordare.

Organizarea folderului Cypress

Cypress configurează deja un folder de cypress de nivel superior cu o structură ghidată atunci când porniți Cypress pentru prima dată în baza de cod. Aceasta include și alte foldere, cum ar fi integration fișierelor cu specificații, fix-uri pentru fișierele de date JSON, plugins -uri pentru funcțiile fixtures cy.task(...) și alte configurații și un folder de support pentru comenzile și tipurile dvs. personalizate.

O regulă bună de urmat atunci când vă organizați în folderele Cypress, componentele React sau orice cod în general este să colocați împreună lucruri care sunt susceptibile de a se schimba împreună. În acest caz, deoarece avem de-a face cu aplicații web în browser, o abordare care se scalează bine este gruparea lucrurilor într-un folder după pagină sau caracteristică generală.

Am creat un folder separat de pages împărțit după numele caracteristicii, cum ar fi „SenderAuthentication” pentru a păstra toate obiectele paginii de sub ruta /settings/sender_auth , cum ar fi „DomainAuthentication” (/settings/sender_auth/domains/**/*) și „LinkBranding” (/settings/sender_auth/links/**/*). În folderul nostru cu plugins , am făcut același lucru cu organizarea tuturor fișierelor plugin cy.task(...) pentru o anumită funcție sau pagină în același folder pentru Autentificarea expeditorului și am urmat aceeași abordare pentru folderul nostru de integration pentru fișierele cu specificații. Ne-am scalat testele la sute de fișiere cu specificații, obiecte de pagină, pluginuri și alte fișiere într-una dintre bazele noastre de coduri – și este ușor și convenabil de navigat.

Iată o schiță a modului în care am organizat folderul de cypress .

Un alt lucru de luat în considerare atunci când vă organizați folderul de integration (unde se află toate specificațiile) este potențialul împărțirea fișierelor cu specificații în funcție de prioritatea testului. De exemplu, este posibil să aveți toate testele cu cea mai mare prioritate și valoare într-un folder „P1” și testele cu prioritate a doua într-un folder „P2”, astfel încât să puteți rula cu ușurință toate testele „P1” setând opțiunea --spec ca --spec 'cypress/integration/P1/**/*' .

Creați o ierarhie de foldere cu specificații care să funcționeze pentru dvs., astfel încât să puteți grupa cu ușurință specificațiile împreună nu numai după pagină sau caracteristică, cum ar fi --spec 'cypress/integration/SomePage/**/*' , ci și după alte criterii precum prioritate, produs , sau mediu.

Consolidarea selectoarelor de elemente

Când dezvoltăm componentele React ale paginii noastre, adăugăm de obicei un anumit nivel de teste unitare și de integrare cu Jest și Enzyme. Spre sfârșitul dezvoltării caracteristicilor, adăugăm un alt strat de teste Cypress E2E pentru a ne asigura că totul funcționează cu backend-ul. Atât testele unitare pentru componentele noastre React, cât și obiectele de pagină pentru testele noastre Cypress E2E necesită selectoare pentru componentele/elementele DOM de pe pagina cu care dorim să interacționăm și să ne afirmăm.

Când actualizăm acele pagini și componente, există șansa să apară deviere și erori de a sincroniza mai multe locuri de la selectoarele de test unitar la obiectul paginii Cypress la codul componentului propriu-zis. Dacă ne-am baza doar pe nume de clasă legate de stiluri, ar fi greu să ne amintim să actualizăm toate locurile care se pot rupe. În schimb, adăugăm „data-hook”, „data-testid” sau orice alt atribut denumit în mod consecvent „data-*” la anumite componente și elemente de pe pagina pe care dorim să le amintim și scriem un selector pentru acesta în straturile noastre de testare.

Putem atașa atribute „data-hook” la multe dintre elementele noastre, dar mai aveam nevoie de o modalitate de a le grupa într-un singur loc pentru a le actualiza și reutiliza în alte fișiere. Am descoperit o modalitate tipizată de a gestiona toți acești selectori „cârlig de date” care să fie răspândiți pe componentele noastre React și să fie utilizați în testele noastre unitare și selectoare de obiecte de pagină pentru mai multă reutilizare și întreținere mai ușoară în obiectele exportate.

Pentru folderul de nivel superior al fiecărei pagini, vom crea un fișier hooks.ts care gestionează și exportă un obiect cu un nume de cheie care poate fi citit pentru element și selectorul CSS de șir real pentru element ca valoare. Le vom numi „Selectori de citire”, deoarece trebuie să citim și să folosim formularul de selecție CSS pentru un element în apeluri precum wrapper.find(“[data-hook='selector']”) în testele noastre unitare sau Cypress. cy.get(“[data-hook='selector']”) în obiectele paginii noastre. Aceste apeluri ar arăta mai clar ca wrapper.find(Selectors.someElement) sau cy.get(Selectors.someElement) .

Următorul fragment de cod acoperă mai multe despre motivul și cum folosim acești selectori de citire în practică.

În mod similar, exportăm și obiecte cu un nume de cheie care poate fi citit pentru element și cu un obiect precum { “data-hook”: “selector” } ca valoare. Le vom numi „Selectori de scriere”, deoarece trebuie să scriem sau să răspândim aceste obiecte pe o componentă React ca elemente de recuzită pentru a adăuga cu succes atributul „data-hook” la elementele de bază. Scopul ar fi să facem așa ceva iar elementul DOM real de dedesubt - presupunând că elementele de recuzită sunt transmise corect elementelor JSX - va avea, de asemenea, setat atributul „data-hook=”.

Următorul fragment de cod acoperă mai multe despre de ce și cum folosim acești selectori de scriere în practică.

Putem crea obiecte pentru a ne consolida selectoarele de citire și selectoarele de scriere pentru a actualiza mai puține locuri, dar dacă ar trebui să scriem mulți selectori pentru unele dintre paginile noastre mai complexe? Acest lucru poate fi mai predispus la erori pentru a construi singur, așa că haideți să creăm funcții pentru a genera cu ușurință acești selectori de citire și selectoare de scriere pentru a exporta eventual pentru o anumită pagină.

Pentru funcția generator de selector de citire, vom parcurge proprietățile unui obiect de intrare și vom forma șirul selector CSS [data-hook=”selector”] pentru fiecare nume de element. Dacă valoarea corespunzătoare a unei chei este null , vom presupune că numele elementului din obiectul de intrare va fi același cu valoarea „data-hook”, cum ar fi { someElement: null } => { someElement: '[data-hook=”someElement”] } . În caz contrar, dacă valoarea corespunzătoare a unei chei nu este nulă, putem alege să înlocuim acea valoare „data-hook” precum { someElement: “newSelector” } => { someElement: '[data-hook=”newSelector”]' } .

Pentru funcția generator de selector de scriere, vom parcurge proprietățile unui obiect de intrare și vom forma obiectele { “data-hook”: “selector” } pentru fiecare nume de element. Dacă valoarea corespunzătoare a unei chei este null , vom presupune că numele elementului din obiectul de intrare va fi același cu valoarea „data-hook” precum { someElement: null } => { someElement: { “data-hook”: “someElement” } } . În caz contrar, dacă valoarea corespunzătoare a unei chei nu este null , putem alege să înlocuim acea valoare „data-hook” precum { someElement: “newSelector” } => { someElement: { “data-hook”: “newSelector” }' } .

Folosind aceste două funcții generatoare, construim selectorul nostru de citire și obiectele selector de scriere pentru o pagină și le exportăm pentru a fi reutilizate în testele noastre unitare și în obiectele paginii Cypress. Un alt bonus este că aceste obiecte sunt tastate în așa fel încât să nu putem face accidental Selectors.unknownElement sau WriteSelectors.unknownElement și în fișierele noastre TypeScript. Înainte de a exporta selectoarele noastre de citire, permitem, de asemenea, adăugarea de elemente suplimentare și mapări ale selectoarelor CSS pentru componente terțe asupra cărora nu avem control. În unele cazuri, nu putem adăuga un atribut „data-hook” la anumite elemente, așa că mai trebuie să selectăm după alte atribute, ID-uri și clase și să adăugăm mai multe proprietăți obiectului selector de citire, așa cum se arată mai jos.

Acest model ne ajută să rămânem organizați cu toți selectorii noștri pentru o pagină și atunci când trebuie să actualizăm lucrurile. Vă recomandăm să investigați modalități prin care puteți gestiona toți acești selectori într-un fel de obiect sau prin orice alte mijloace pentru a minimiza numărul de fișiere de care aveți nevoie pentru a atinge modificările viitoare.

Consolidarea timeout-urilor

Un lucru pe care l-am observat după ce am scris multe teste Cypress a fost că selectorii noștri pentru anumite elemente vor expira și vor dura mai mult decât valorile implicite de timeout stabilite în cypress.json , cum ar fi „defaultCommandTimeout” și „responseTimeout”. Ne-am întoarce înapoi și ajustam anumite pagini care aveau nevoie de timeout-uri mai lungi, dar apoi, în timp, numărul de valori arbitrare timeout a crescut și menținerea acestuia a devenit mai dificilă pentru modificări la scară largă.

Drept urmare, ne-am consolidat timeout-urile într-un obiect care începe deasupra „defaultCommandTimeout”, care se află undeva în intervalul de cinci până la zece secunde pentru a acoperi majoritatea timeout-urilor generale pentru selectorii noștri, cum ar fi cy.get(...) sau cy.contains(...) . Dincolo de acest timeout implicit, am scala la „scurt”, „mediu”, „lung”, „xlong” și „xxlong” într-un obiect timeout pe care îl putem importa oriunde în fișierele noastre pentru a le folosi în comenzile Cypress, cum ar fi cy.get(“someElement”, { timeout: timeouts.short }) sau cy.task('pluginName', {}, { timeout: timeouts.xlong }) . După ce ne-am înlocuit timeout-urile cu aceste valori din obiectul nostru importat, avem un loc pentru a actualiza pentru a crește sau reduce timpul necesar pentru anumite timeout-uri.

Un exemplu despre cum puteți începe cu ușurință cu aceasta este prezentat mai jos.

Încheierea

Pe măsură ce suitele dvs. de testare Cypress cresc, este posibil să întâmpinați unele dintre aceleași probleme pe care le-am întâlnit pe care le-am întâlnit atunci când ați aflat cum să scalați și să vă întrețineți cel mai bine testele Cypress. Puteți alege să vă organizați fișierele în funcție de pagină, caracteristică sau altă convenție de grupare, astfel încât să știți întotdeauna unde să căutați fișierele de testare existente și unde să adăugați altele noi, pe măsură ce mai mulți dezvoltatori contribuie la baza de cod.

Pe măsură ce interfața de utilizare se schimbă, puteți folosi un fel de obiect tipat (cum ar fi selectoarele de citire și scriere) pentru a vă menține selectoarele de atribute „date” la elementele cheie ale fiecărei pagini pe care doriți să le afirmați sau să interacționați în cadrul testelor unitare și Cypress. teste. Dacă începeți să aplicați prea multe valori arbitrare pentru lucruri precum valorile timeout pentru comenzile dvs. Cypress, poate fi timpul să configurați un obiect plin cu valori scalate, astfel încât să puteți actualiza acele valori într-un singur loc.

Pe măsură ce lucrurile se schimbă în interfața de utilizare frontală, API-ul backend și testele Cypress, ar trebui să vă gândiți întotdeauna la modalități de a menține mai ușor aceste teste. Mai puține locuri pentru a actualiza și a face greșeli și mai puțină ambiguitate cu privire la locul în care să puneți lucrurile fac o diferență enormă, deoarece mulți dezvoltatori adaugă noi pagini, funcții și (inevitabil) teste Cypress pe drum.

Vă interesează mai multe postări despre Cypress? Aruncă o privire la următoarele articole:

  • Ce să țineți cont atunci când scrieți teste E2E
  • Prezentare generală de 1.000 de picioare despre scrierea testelor de chiparoși
  • TypeScript Toate lucrurile din testele tale Cypress
  • Confruntarea cu fluxurile de e-mail în testele Cypress
  • Integrarea testelor Cypress cu Docker, Buildkite și CICD