În ultimul articol am vorbit despre câteva idei și modele, cum ar fi modelul Object Page, care ajută la scrierea testelor UI sustenabile. În acest articol vom discuta câteva subiecte avansate care vă vor ajuta să scrieți mai multe teste robuste și să le depanați când nu reușesc:
Voi folosi Selenium pentru subiectele de automatizare a browserului discutate în acest articol.
La fel ca articolul precedent, conceptele și soluțiile discutate în acest articol sunt aplicabile indiferent de limbajul și interfața de utilizare pe care o utilizați. Înainte de a merge mai departe vă rugăm să citiți articolul precedent așa cum am de gând să se refere la ea și codul de probă de câteva ori. Nu-ți face griji; o sa astept aici.
adăugare Thread.Sleep
(sau întârzieri, în general) se simte ca un hack inevitabil când vine vorba de testarea UI. Aveți un test care nu reușește intermitent și după o anchetă puteți urmări întârzierile ocazionale ale răspunsului; De exemplu, navigați la o pagină și căutați sau afirmați ceva înainte ca pagina să fie complet încărcată, iar cadrul dvs. de automatizare a browserului aruncă o excepție indicând faptul că elementul nu există. O mulțime de lucruri ar putea contribui la această întârziere. De exemplu:
Sau o combinație între aceste și alte probleme.
Să presupunem că aveți o pagină care în mod normal durează mai puțin de o secundă pentru a încărca, dar testele care o lovesc nu reușesc din când în când din cauza întârzierii ocazionale a răspunsului. Aveți câteva opțiuni:
Vedeți, nu există câștiguri cu întârzieri arbitrare: fie primiți o suită de testare lentă, fie una fragilă. Aici vă voi arăta cum să evitați introducerea întârzierilor fixe în testele dvs. Vom discuta două tipuri de întârzieri care ar trebui să acopere aproape toate cazurile cu care trebuie să vă ocupați: adăugând o întârziere globală și așteptând ceva să se întâmple.
Dacă toate paginile dvs. se încarcă în același timp pentru încărcare, ceea ce este mai mult decât se aștepta, majoritatea testelor vor eșua din cauza răspunsului prealabil. În astfel de cazuri, puteți utiliza așteptarea implicită:
O așteptare implicită este să-i spuneți WebDriver-ului să sondă DOM pentru o anumită perioadă de timp atunci când încearcă să găsească un element sau elemente dacă acestea nu sunt disponibile imediat. Setarea implicită este 0. Odată setată, așteptarea implicită este setată pentru durata de viață a instanței obiectului WebDriver.
Acesta este modul în care setați o așteptare implicită:
Driverul WebDriver = noul FirefoxDriver (); .. Driver.Manage () intreruperilor () ImplicitlyWait (TimeSpan.FromSeconds (5));
În acest fel îi spuneți Selenium să aștepte până la 5 secunde atunci când încearcă să găsească un element sau să interacționeze cu pagina. Acum puteți scrie:
driver.Url = "http: // somedomain / url_that_delays_loading"; IWebElement myDynamicElement = driver.FindElement (By.Id ("someDynamicElement"));
in loc de:
driver.Url = "http: // somedomain / url_that_delays_loading"; Thread.Sleep (5000); IWebElement myDynamicElement = driver.FindElement (By.Id ("someDynamicElement"));
Beneficiul acestei abordări este acela FindElement
va reveni de îndată ce va găsi elementul și nu va aștepta întreaga 5 secunde atunci când elementul este disponibil mai devreme.
După așteptarea implicită este setată pe dvs. WebDriver
instanța se aplică tuturor acțiunilor asupra conducătorului auto; astfel încât să puteți scăpa de mulți Thread.Sleep
s în codul dvs..
5 secunde este o așteptare am făcut pentru acest articol - ar trebui să găsească așteptarea optimă implicit pentru cererea dumneavoastră și ar trebui să vă așteptați cât mai scurt posibil. Din documentațiile API:
Creșterea timpului de așteptare implicit ar trebui să fie utilizată în mod judicios, deoarece va avea un efect advers asupra timpului de testare, în special atunci când este utilizat cu strategii de locație mai mici, cum ar fi XPath.
Chiar dacă nu utilizați XPath, folosirea așteptărilor implicite lungi încetinește testele dvs., mai ales când unele teste sunt într-adevăr eșuate, deoarece driverul web va aștepta mult timp înainte de expirarea timpului și aruncă o excepție.
Utilizarea așteptării implicite este o modalitate excelentă de a scăpa de multe întârzieri în codul dvs.; dar totuși vă veți afla într-o situație în care trebuie să adăugați anumite întârzieri fixe în codul dvs., deoarece așteptați să se întâmple ceva: o pagină este mai lentă decât toate celelalte pagini și trebuie să așteptați mai mult, sunteți de așteptare pentru un apel AJAX pentru a termina sau pentru ca un element să apară sau să dispară de pe pagină etc. Aici aveți nevoie de așteptare explicită.
Așa că ați setat așteptarea implicită la 5 secunde și funcționează pentru multe dintre testele dvs.; dar există încă câteva pagini care uneori necesită mai mult de 5 secunde pentru a încărca și a duce la testele care nu au reușit.
Ca o notă laterală, ar trebui să investigați de ce o pagină durează mai întâi, înainte de a încerca să remediați testul rupt, făcându-l să aștepte mai mult. S-ar putea să apară o problemă de performanță pe pagina care duce la testul roșu, caz în care trebuie să remediați pagina, nu testul.
În cazul unei pagini lente puteți înlocui întârzierile fixe cu așteptarea explicită:
O așteptare explicită este codul pe care îl definiți să așteptați să apară o anumită condiție înainte de a continua în continuare în cod.
Puteți aplica așteptări explicite utilizând WebDriverWait
clasă. WebDriverWait
traieste in WebDriver.Support
asamblare și pot fi instalate folosind Selenium.Support nuget:
////// Oferă capacitatea de a aștepta o condiție arbitrară în timpul executării testului. /// clasa publică WebDriverWait: DefaultWait/// /// Inițializează o nouă instanță a /// Exemplu WebDriver folosit pentru a aștepta.Valoarea timpului de expirare indică durata de așteptare a condiției. public WebDriverWait (driverul IWebDriver, timeout Timeout); ///clasă. /// /// Inițializează o nouă instanță a /// Obiect de implementare aclasă. /// interfață utilizată pentru a determina când a trecut timpul.Exemplu WebDriver folosit pentru a aștepta.Valoarea timpului de expirare indică durata de așteptare a condiției.A valoare indicând cât de des pentru a verifica dacă condiția este adevărată. public WebDriverWait (ceas IClock, șofer IWebDriver, timeout Timeout, SleepInterval);
Iată un exemplu despre modul în care puteți utiliza WebDriverWait
in testele tale:
driver.Url = "http: // somedomain / url_that_takes_a_long_time_to_load"; WebDriverWait wait = nou WebDriverWait (driver, TimeSpan.FromSeconds (10)); var myDynamicElement = wait.Until (d => d.FindElement (By.Id ("someElement"));
Spunem Selenium că vrem să aștepte această pagină / element special timp de până la 10 secunde.
Este posibil să aveți câteva pagini care durează mai mult decât implicit așteptați implicit și nu este o practică bună de codificare pentru a continua să repetați acest cod peste tot. Dupa toate acestea Codul de test este codul. În schimb, puteți extrage acest lucru într-o metodă și utilizați-l din testele dvs.:
public IWebElement FindElementWithWait (după, int secundeToWait = 10) var wait = nou WebDriverWait (WebDriver, TimeSpan.FromSeconds (secondsToWait)); retur așteptați.În timp (d => d.FindElement (by));
Apoi puteți utiliza această metodă ca:
var slowPage = nou SlowPage ("http: // somedomain / url_that_takes_a_long_time_to_load"); elementul var = slowPage.FindElementWithWait (By.Id ("someElement"));
Acesta este un exemplu conturat pentru a arăta cum ar putea arăta metoda și cum ar putea fi folosită. În mod ideal, veți muta toate interacțiunile de pagină către obiectele paginii.
Să vedem un alt exemplu de așteptare explicită. Uneori pagina este încărcată complet, dar elementul nu este încă acolo, deoarece este încărcat mai târziu ca rezultat al unei solicitări AJAX. Poate că nu este un element pe care îl așteptați, ci doar doriți să așteptați ca o interacțiune AJAX să se termine înainte de a putea face o afirmație, să zicem în baza de date. Din nou, aceasta este locul în care majoritatea dezvoltatorilor folosesc Thread.Sleep
pentru a vă asigura că, de exemplu, apelul AJAX se face și înregistrarea este acum în baza de date înainte de a trece la linia următoare a testului. Acest lucru poate fi rectificat cu ușurință prin executarea JavaScript!
Majoritatea cadrelor de automatizare a browserului vă permit să executați JavaScript pe sesiunea activă, iar Selenium nu face excepție. În Selenium există o interfață numită IJavaScriptExecutor
cu două metode:
////// definește interfața prin care utilizatorul poate executa JavaScript. /// interfață publică IJavaScriptExecutor ////// Execută JavaScript în contextul ferestrei sau ferestrei selectate. /// /// Codul JavaScript care trebuie executat. ////// Valoarea returnată de script. /// obiect ExecuteScript (script de șir, params object [] args); ////// Execută JavaScript în mod asincron în contextul ferestrei sau ferestrei selectate. /// /// Codul JavaScript care trebuie executat. ////// Valoarea returnată de script. /// obiect ExecuteAsyncScript (script de șir, params object [] args);
Această interfață este implementată de către RemoteWebDriver
care este clasa de bază pentru toate implementările de driver web. Deci, pe instanța driverului dvs. web, puteți apela ExecuteScript
pentru a rula un script JavaScript. Iată o metodă pe care o puteți utiliza pentru a aștepta terminarea tuturor apelurilor AJAX (presupunând că utilizați jQuery):
// Aceasta se presupune că trăiește într-o clasă care are acces la instanța activă "WebDriver" prin câmpul / proprietatea "WebDriver". void public WaitForAjax (int secundeToWait = 10) var wait = nou WebDriverWait (WebDriver, TimeSpan.FromSeconds (secondsToWait)); așteptați.În caz contrar (d => (bool) ((IJavaScriptExecutor) d) .ExecuteScript ("return jQuery.active == 0"));
Combinați ExecuteScript
cu WebDriverWait
și puteți scăpa de ea Thread.Sleep
adăugat pentru apelurile AJAX.
jQuery.active
returnează numărul de apeluri active AJAX inițiat de jQuery; astfel încât atunci când este zero, nu există apeluri AJAX în curs. Această metodă, evident, funcționează numai dacă toate solicitările AJAX sunt inițiate de jQuery. Dacă utilizați alte biblioteci JavaScript pentru comunicații AJAX, ar trebui să consultați documentațiile API pentru o metodă echivalentă sau să urmăriți singur apelurile AJAX.
Dacă așteptați explicit, puteți seta o condiție și așteptați până când aceasta este îndeplinită sau pentru expirarea expirării. Am văzut cum am putea verifica dacă apelurile AJAX vor termina - un alt exemplu este verificarea vizibilității unui element. La fel ca verificarea AJAX, puteți scrie o condiție care verifică vizibilitatea unui element; dar există o soluție mai ușoară pentru cea numită ExpectedCondition
.
Din documentația Selenium:
Există câteva condiții frecvente întâlnite frecvent când se automatizează browserele web.
Dacă utilizați Java aveți noroc deoarece ExpectedCondition
clasa în Java este destul de extinsă și are o mulțime de metode de conveniență. Puteți găsi documentația aici.
.Dezvoltatorii de pe net nu sunt la fel de norocoși. Mai există încă o ExpectedConditions
clasă în WebDriver.Support
montaj (documentat aici), dar este foarte minim:
clauză publică sigilată Condiții așteptate ////// O așteptare pentru verificarea titlului unei pagini. /// /// Titlul așteptat, care trebuie să fie o potrivire exactă. ////// Func static publiccând se potrivește titlul; in caz contrar, . /// TitluI (titlul șirului); /// /// O așteptare pentru verificarea faptului că titlul unei pagini conține o substring sensibil la minuscule. /// /// Se așteaptă fragmentul de titlu. ////// Func static publiccând se potrivește titlul; in caz contrar, . /// TitleContains (titlul șirului); /// /// O așteptare pentru a verifica dacă un element este prezent în DOM-ul unei pagini ///. Aceasta nu înseamnă neapărat că elementul este vizibil. /// /// Locatorul a folosit pentru a găsi elementul. ////// The Func static publicodată ce se află. /// ElementExiste (prin locator); /// /// O așteptare pentru a verifica dacă un element este prezent în DOM-ul unei pagini /// și este vizibil. Vizibilitatea înseamnă că elementul nu este doar afișat, dar /// are de asemenea o înălțime și o lățime mai mare decât 0. /// /// Locatorul a folosit pentru a găsi elementul. ////// The Func static publicodată ce este localizat și vizibil. /// ElementIsVisibil (prin localizator);
Puteți folosi această clasă în combinație cu WebDriverWait
:
var wait = nou WebDriverWait (șofer, TimeSpan.FromSeconds (3)) var element = wait.Until (ExpectedConditions.ElementExists (By.Id ("foo")));
După cum puteți vedea din semnătura clasei de mai sus, puteți verifica titlul sau părți ale acestuia și existența și vizibilitatea elementelor folosind ExpectedCondition
. Suportul din cutie din .Net ar putea fi foarte minim; dar această clasă nu este altceva decât o împachetare în jurul unor condiții simple. Puteți implementa la fel de ușor alte condiții comune într-o clasă și le puteți folosi WebDriverWait
din scripturile de testare.
O altă bijuterie numai pentru dezvoltatorii Java este FluentWait
. Din pagina de documentare, FluentWait
este
O implementare a interfeței de așteptare care poate avea timpul de expirare și intervalul de interogare configurate în zbor. Fiecare instanță FluentWait definește perioada maximă de așteptare pentru o condiție, precum și frecvența cu care se verifică starea. În plus, utilizatorul poate configura așteptarea pentru a ignora anumite tipuri de excepții în timp ce așteaptă, cum ar fi NoSuchElementExceptions când căutați un element pe pagină.
În următorul exemplu, încercăm să găsim un element cu id foo
pe pagina de sondare la fiecare 5 secunde timp de până la 30 de secunde:
// Așteptați 30 de secunde pentru ca un element să fie prezent pe pagină, verificând // pentru prezența sa o dată la fiecare 5 secunde. Așteptaașteptați = FluentWait nou (driver) .withTimeout (30, SECONDS) .pollingEvery (5, SECONDS) .igning (NoSuchElementException.class); WebElement foo = wait.until (noua funcție () public WebElement se aplică (driverul WebDriver) return driver.findElement (By.id ("foo")); );
Există două lucruri deosebite FluentWait
: în primul rând, vă permite să specificați intervalul de interogare care ar putea îmbunătăți performanța dvs. de testare și, în al doilea rând, vă permite să ignorați excepțiile care nu vă interesează.
FluentWait
este destul de minunat și ar fi grozav dacă un echivalent a existat și în .Net. Asta a spus că nu este atât de greu să o implementezi WebDriverWait
.
Aveți obiectele dvs. de pagină în loc, aveți un cod de testare usor întreținut și evitați și întârzierile fixe ale testelor; dar testele tale încă nu reușesc!
Interfața utilizator este, de obicei, cea mai frecvent schimbată parte a unei aplicații tipice: uneori mutați elemente în jurul unei pagini pentru a schimba designul paginii și, uneori, modificările structurii paginii pe baza cerințelor. Aceste modificări privind aspectul și designul paginii ar putea conduce la o mulțime de teste rupte, dacă nu alegeți cu ușurință selectorii.
Nu utilizați selectori fuzzy și nu vă bazați pe structura paginii dvs..
De multe ori am fost întrebat dacă este bine să adăugați un ID la elementele de pe pagină doar pentru testare, iar răspunsul este un răspuns răsunător. Pentru a face testabilitatea unității noastre de cod, facem o mulțime de modificări, cum ar fi adăugarea de interfețe și utilizarea dependenței de injecție. Codul de test este codul. Faceți ce este necesar pentru a vă susține testele.
Să presupunem că avem o pagină cu următoarea listă:
Într-unul din testele mele vreau să fac clic pe albumul "Fii acolo". Aș fi cerut probleme dacă am folosit următorul selector:
By.XPath ( "// ul [@ id = 'album-list'] / li [3] / a")
Când este posibil, trebuie să adăugați ID-ul elementelor și să le direcționați direct și fără a se baza pe elementele din jur. Așa că o să fac o mică schimbare în listă:
Am adăugat id
atribuie ancorelor bazate pe ID-ul unic al unui album astfel încât să putem viza direct un link fără a trebui să trecem prin ul
și Li
elemente. Deci, acum pot înlocui selectorul fragil cu By.Id ( "album-35")
care este garantat să funcționeze atâta timp cât albumul se află pe pagină, ceea ce este, de asemenea, o afirmație bună. Pentru a crea acel selector, evident că trebuie să am acces la id-ul albumului din codul de testare.
Nu este întotdeauna posibil să adăugați ID-uri unice elementelor, deși, ca rânduri într-o rețea sau elemente dintr-o listă. În astfel de cazuri, puteți folosi clasele CSS și atributele de date HTML pentru a atașa proprietățile trasabile elementelor dvs. pentru a le selecta mai ușor. De exemplu, dacă ați avut două liste de albume în pagina dvs., unul ca rezultat al căutării de utilizatori și altul pentru albume sugerate pe baza achizițiilor anterioare ale utilizatorului, le puteți diferenția utilizând o clasă CSS pe ul
element, chiar dacă acea clasă nu este utilizată pentru modelarea listei:
Dacă preferați să nu aveți clase CSS neutilizate, puteți utiliza în schimb atributele de date HTML și modificați listele la:
și:
Unul dintre principalele motive pentru care testele UI eșuează este că un element sau un text nu este găsit pe pagină. Uneori, acest lucru se întâmplă pentru că aterizați pe o pagină greșită din cauza erorilor de navigare sau a modificărilor la navigarea paginilor de pe site-ul dvs. sau a erorilor de validare. Alteori ar putea fi din cauza unei pagini lipsă sau a unei erori de server.
Indiferent de ce cauzează eroarea și dacă aveți acest lucru pe jurnalul serverului dvs. CI sau în consola de testare desktop, a NoSuchElementException
(sau ceva asemănător) nu este destul de util pentru a afla ce sa întâmplat, nu-i așa? Deci, atunci când testul nu reușește, singura modalitate de depanare a erorii este să o executați din nou și să o urmăriți deoarece nu reușește. Există câteva trucuri care ar putea să vă salveze să vă reluați testele UI lent pentru depanare. O soluție la acest lucru este să capturați o captură de ecran ori de câte ori un test eșuează, astfel încât să ne putem referi mai târziu la acesta.
Există o interfață numită Selenium ITakesScreenshot
:
////// Definește interfața utilizată pentru a realiza imagini cu ecranul ecranului. /// interfață publică ITakesScreenshot ////// Obține a /// ///obiect care reprezintă imaginea paginii de pe ecran. /// /// A Screenshot GetScreenshot ();obiect care conține imaginea. ///
Această interfață este implementată de clase de driver web și poate fi utilizată astfel:
var ecranul = driver.GetScreenshot (); screenshot.SaveAsFile (“", ImageFormat.Png);
În acest fel, atunci când un test eșuează pentru că vă aflați pe o pagină greșită, puteți să-l găsiți rapid verificând ecranul capturat.
Capturarea capturilor de ecran nu este întotdeauna suficientă. De exemplu, s-ar putea să vedeți elementul pe care îl așteptați pe pagină, dar testul încă nu reușește să spună că nu îl găsește, probabil datorită unui selector greșit care duce la o căutare nereușită a elementelor. Deci, în loc de (sau să completeze) captura de ecran, puteți captura, de asemenea, sursa de pagină ca html. Este un Sursa paginii
proprietate pe IWebDriver
interfață (care este implementată de toate driverele web):
////// Obține sursa paginii încărcată ultima dată de browser. /// ////// Dacă pagina a fost modificată după încărcare (de exemplu, prin JavaScript) /// nu există nici o garanție că textul returnat este acela al paginii modificate. /// Vă rugăm să consultați documentația driverului special utilizat pentru a /// determina dacă textul returnat reflectă starea curentă a paginii /// sau textul ultimul trimis de serverul web. Sursa de pagină returnată este o reprezentare /// a DOM-ului subiacent: nu te aștepta ca acesta să fie formatat /// sau să scape în același mod ca răspunsul trimis de serverul web. /// string PageSource a lua;
La fel ca și noi ITakesScreenshot
ați putea implementa o metodă care captează sursa paginii și o persistă într-un fișier pentru o inspecție ulterioară:
File.WriteAllText (“", driver.PageSource);
Nu doriți să capturați capturi de ecran și surse de pagini ale tuturor paginilor pe care le vizitați și pentru testele de deplasare; în caz contrar va trebui să treci prin mii dintre ei când ceva nu merge bine. În schimb, trebuie să le capturați numai atunci când un test nu reușește sau altfel când aveți nevoie de mai multe informații pentru depanare. Pentru a evita poluarea codului cu prea multe blocuri try-catch și pentru a evita dublarea codurilor, trebuie să puneți toate căutările elementelor și afirmațiile într-o singură clasă și să le înfășurați cu try-catch și apoi să capturați captura de ecran și / sau sursa paginii din blocul de captură . Iată un pic de cod pe care l-ați putea folosi pentru executarea acțiunilor împotriva unui element:
public void Executa (Prin, de, Acțiuneacțiune) try var element = WebDriver.FindElement (by); acțiune (elementul); captura var capturer = nou Capturer (WebDriver); capturer.CaptureScreenshot (); capturer.CapturePageSource (); arunca;
Capturer
clasa poate fi implementată ca:
public class Capturer public static string OutputFolder = Path.Combine (AppDomain.CurrentDomain.BaseDirectory, "FailedTests"); private readonly RemoteWebDriver _webDriver; Capturer public (RemoteWebDriver webDriver) _webDriver = webDriver; void publice CaptureScreenshot (șir fileName = null) var camera = (ITakesScreenshot) _webDriver; var screenshot = camera.GetScreenshot (); var screenShotPath = GetOutputFilePath (NumeFile, "png"); screenshot.SaveAsFile (screenShotPath, ImageFormat.Png); void publice CapturePageSource (șir fileName = null) var filePath = GetOutputFilePath (fileName, "html"); File.WriteAllText (filePath, _webDriver.PageSource); șirul privat GetOutputFilePath (string fileName, fișier stringExtension) if (! Directory.Exists (OutputFolder)) Directory.CreateDirectory (OutputFolder); var windowTitle = _webDriver.Title; fileName = nume_fișier? string.Format ("0 1. 2", windowTitle, DateTime.Now.ToFileTime (), fișierExtension) .Replace (':', '.'); var outputPath = Path.Combine (OutputFolder, NumeFile); var caleChars = Path.GetInvalidPathChars (); var stringBuilder = noul StringBuilder (outputPath); foreach (element var în pathChars) stringBuilder.Replace (element, '.'); var screenShotPath = șirBuilder.ToString (); retur screenShotPath;
Această implementare persistă screenshot-ul și sursa HTML într-un folder numit FailedTests lângă teste, dar îl puteți modifica dacă doriți un comportament diferit.
Deși am arătat doar metode specifice seleniului, API-uri similare există în toate cadrele de automatizare pe care le cunosc și pot fi utilizate cu ușurință.
În acest articol am vorbit despre câteva sfaturi și trucuri de teste UI. Am discutat despre modul în care puteți evita o suită de testare UI fragilă și lentă, evitând întârzierile fixe în testele dvs. Am discutat despre cum să evităm selectorii și testele fragile, alegând cu ușurință selectorii și, de asemenea, cum să depanem testele dvs. UI când acestea nu reușesc.
Majoritatea codului prezentat în acest articol pot fi găsite în repozitoriul de mostre MvcMusicStore pe care l-am văzut în ultimul articol. Este, de asemenea, de remarcat faptul că o mulțime de cod în MvcMusicStore a fost împrumutat de la codul de bază Seleno, așa că, dacă doriți să vedeți o mulțime de trucuri se răcească s-ar putea să doriți să verificați Seleno afară. Disclaimer: Sunt co-fondator al organizației TestStack și contribuitor la Seleno.
Sper că ceea ce am discutat în acest articol vă ajută în eforturile de testare a UI.