A Splash of EarlGrey – UI Testing Toptal Talent App

Publicat: 2022-03-11

Unul dintre cele mai importante lucruri pe care le puteți face ca tester pentru a vă face munca mai eficientă și mai rapidă este să automatizați aplicația pe care o testați. Bazându-vă exclusiv pe teste manuale nu este fezabil, deoarece ar trebui să rulați setul complet de teste în fiecare zi, uneori de mai multe ori pe zi, testând fiecare modificare introdusă în codul aplicației.

Acest articol va descrie călătoria echipei noastre de a identifica EarlGrey 1.0 de la Google ca instrument care a funcționat cel mai bine pentru noi în contextul automatizării aplicației iOS Toptal Talent. Faptul că îl folosim nu înseamnă că EarlGrey este cel mai bun instrument de testare pentru toată lumea - se întâmplă să fie cel care se potrivește nevoilor noastre.

De ce am trecut la EarlGrey

De-a lungul anilor, echipa noastră a construit diferite aplicații mobile atât pe iOS, cât și pe Android. La început, ne-am gândit să folosim un instrument de testare UI multiplatformă care ne-ar permite să scriem un singur set de teste și să le executăm pe diferite sisteme de operare mobile. În primul rând, am optat pentru Appium, cea mai populară opțiune open-source disponibilă.

Dar, odată cu trecerea timpului, limitările Appium au devenit din ce în ce mai evidente. În cazul nostru, cele două dezavantaje principale ale lui Appium au fost:

  • Stabilitatea discutabilă a cadrului a cauzat multe fulgi de testare.
  • Procesul de actualizare relativ lent ne-a împiedicat munca.

Pentru a atenua primul deficiență Appium, am scris tot felul de modificări de cod și hack-uri pentru a face testele mai stabile. Cu toate acestea, nu am putut face nimic pentru a-l aborda pe cel de-al doilea. De fiecare dată când a fost lansată o nouă versiune de iOS sau Android, Appium a avut nevoie de mult timp pentru a ajunge din urmă. Și de foarte multe ori, din cauza numeroaselor erori, actualizarea inițială a fost inutilizabilă. Drept urmare, am fost adesea forțați să continuăm să executăm testele pe o versiune de platformă mai veche sau să le dezactivăm complet până când a fost disponibilă o actualizare Appium funcțională.

Această abordare a fost departe de a fi ideală și, din cauza acestor probleme, alături de altele pe care nu le vom trata în detaliu, am decis să căutăm alternative. Criteriile de top pentru un nou instrument de testare au fost stabilitatea sporită și actualizările mai rapide . După câteva investigații, am decis să folosim instrumente native de testare pentru fiecare platformă.

Deci, am trecut la Espresso pentru proiectul Android și la EarlGrey 1.0 pentru dezvoltarea iOS. În retrospectivă, putem spune acum că aceasta a fost o decizie bună. Timpul „pierdut” din cauza necesității de a scrie și de a menține două seturi diferite de teste, câte unul pentru fiecare platformă, a fost mai mult decât compensat prin faptul că nu a fost nevoie să investigheze atât de multe teste nerezolvate și să nu aibă timp de nefuncționare la actualizările versiunii.

Structura locală a proiectului

Va trebui să includeți cadrul în același proiect Xcode ca și aplicația pe care o dezvoltați. Așa că am creat un folder în directorul rădăcină pentru a găzdui testele UI. Crearea fișierului EarlGrey.swift este obligatorie la instalarea cadrului de testare, iar conținutul acestuia este predefinit.

Aplicația Toptal Talent: Structura locală a proiectului

EarlGreyBase este clasa părinte pentru toate clasele de testare. Conține metodele generale setUp și tearDown , extinse de la XCTestCase . În setUp , încărcăm stub-urile care vor fi utilizate în general de majoritatea testelor (mai multe despre stubbing mai târziu) și, de asemenea, setăm câteva steaguri de configurare despre care am observat că măresc stabilitatea testelor:

 // Turn off EarlGrey's network requests tracking since we don't use it and it can block tests execution GREYConfiguration.sharedInstance().setValue([".*"], forConfigKey: kGREYConfigKeyURLBlacklistRegex) GREYConfiguration.sharedInstance().setValue(false, forConfigKey: kGREYConfigKeyAnalyticsEnabled)

Folosim modelul de design Page Object - fiecare ecran din aplicație are o clasă corespunzătoare în care sunt definite toate elementele UI și posibilele interacțiuni ale acestora. Această clasă se numește „pagină”. Metodele de testare sunt grupate pe caracteristici care se află în fișiere și clase separate din pagini.

Pentru a vă oferi o idee mai bună despre cum este afișat totul, așa arată ecranele Login și Forgot Password în aplicația noastră și cum sunt reprezentate de obiectele paginii.

Acesta este aspectul ecranelor Login și Forgot Password din aplicația noastră.

Mai târziu, în articol, vom prezenta conținutul codului obiectului pagina Login.

Metode utilitare personalizate

Modul în care EarlGrey sincronizează acțiunile de testare cu aplicația nu este întotdeauna perfect. De exemplu, ar putea încerca să facă clic pe un buton care nu este încă încărcat în ierarhia interfeței de utilizare, provocând eșecul unui test. Pentru a evita această problemă, am creat metode personalizate pentru a aștepta până când elementele apar în starea dorită înainte de a interacționa cu ele.

Iată câteva exemple:

 static func asyncWaitForVisibility(on element: GREYInteraction) { // By default, EarlGrey blocks test execution while // the app is animating or doing anything in the background. //https://github.com/google/EarlGrey/blob/master/docs/api.md#synchronization GREYConfiguration.sharedInstance().setValue(false, forConfigKey: kGREYConfigKeySynchronizationEnabled) element.assert(grey_sufficientlyVisible()) GREYConfiguration.sharedInstance().setValue(true, forConfigKey: kGREYConfigKeySynchronizationEnabled) } static func waitElementVisibility(for element: GREYInteraction, timeout: Double = 15.0) -> Bool { GREYCondition(name: "Wait for element to appear", block: { var error: NSError? element.assert(grey_notNil(), error: &error) return error == nil }).wait(withTimeout: timeout, pollInterval: 0.5) if !elementVisible(element) { XCTFail("Element didn't appear") } return true }

Un alt lucru pe care EarlGrey nu îl face singur este derularea ecranului până când elementul dorit devine vizibil. Iată cum putem face asta:

 static func elementVisible(_ element: GREYInteraction) -> Bool { var error: NSError? element.assert(grey_notVisible(), error: &error) if error != nil { return true } else { return false } } static func scrollUntilElementVisible(_ scrollDirection: GREYDirection, _ speed: String, _ searchedElement: GREYInteraction, _ actionElement: GREYInteraction) -> Bool { var swipes = 0 while !elementVisible(searchedElement) && swipes < 10 { if speed == "slow" { actionElement.perform(grey_swipeSlowInDirection(scrollDirection)) } else { actionElement.perform(grey_swipeFastInDirection(scrollDirection)) } swipes += 1 } if swipes >= 10 { return false } else { return true } }

Alte metode utilitare care lipsesc din API-ul EarlGrey pe care le-am identificat sunt numărarea elementelor și citirea valorilor textului. Codul acestor utilitare este disponibil pe GitHub: aici și aici.

Stubbing apeluri API

Pentru a ne asigura că evităm rezultatele testelor false cauzate de problemele serverului de back-end, folosim biblioteca OHHTTPStubs pentru a bate joc de apelurile de pe server. Documentația de pe pagina lor de pornire este destul de simplă, dar vom prezenta modul în care stăm răspunsurile în aplicația noastră, care utilizează API-ul GraphQL.

 class StubsHelper { static let testURL = URL(string: "https://[our backend server]")! static func setupOHTTPStub(for request: StubbedRequest, delayed: Bool = false) { stub(condition: isHost(testURL.host!) && hasJsonBody(request.bodyDict())) { _ in let fix = appFixture(forRequest: request) if delayed { return fix.requestTime(0.1, responseTime: 7.0) } else { return fix } } } static let stubbedEmail = "[email protected]" static let stubbedPassword = "password" enum StubbedRequest { case login func bodyDict() -> [String: Any] { switch self { case .login: return EmailPasswordSignInMutation( email: stubbedTalentLogin, password: stubbedTalentPassword ).makeBodyIdentifier() } } func statusCode() -> Int32 { return 200 } func jsonFileName() -> String { let fileName: String switch self { case .login: fileName = "login" } return "\(fileName).json" } } private extension GraphQLOperation { func makeBodyIdentifier() -> [String: Any] { let body: GraphQLMap = [ "query": queryDocument, "variables": variables, "operationName": operationName ] // Normalize values like enums here, otherwise body comparison will fail guard let normalizedBody = body.jsonValue as? [String: Any] else { fatalError() } return normalizedBody } }

Încărcarea stub-ului se face apelând metoda setupOHTTPStub :

 StubsHelper.setupOHTTPStub(for: .login)

Punând totul laolaltă

Această secțiune va demonstra cum folosim toate principiile descrise mai sus pentru a scrie un test de autentificare end-to-end real.

 import EarlGrey final class LoginPage { func login() -> HomePage { fillLoginForm() loginButton().perform(grey_tap()) return HomePage() } func fillLoginForm() { ElementsHelper.waitElementVisibility(emailField()) emailField().perform(grey_replaceText(StubsHelper.stubbedTalentLogin)) passwordField().perform(grey_tap()) passwordField().perform(grey_replaceText(StubsHelper.stubbedTalentPassword)) } func clearAllInputs() { if ElementsHelper.elementVisible(passwordField()) { passwordField().perform(grey_tap()) passwordField().perform(grey_replaceText("")) } emailField().perform(grey_tap()) emailField().perform(grey_replaceText("")) } } private extension LoginPage { func emailField(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement(with: grey_accessibilityLabel("Email"), file: file, line: line) } func passwordField(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement( with: grey_allOf([ grey_accessibilityLabel("Password"), grey_sufficientlyVisible(), grey_userInteractionEnabled() ]), file: file, line: line ) } func loginButton(file: StaticString = #file, line: UInt = #line) -> GREYInteraction { return EarlGrey.selectElement(with: grey_accessibilityID("login_button"), file: file, line: line) } } class BBucketTests: EarlGreyBase { func testLogin() { StubsHelper.setupOHTTPStub(for: .login) LoginPage().clearAllInputs() let homePage = LoginPage().login() GREYAssertTrue( homePage.assertVisible(), reason: "Home screen not displayed after successful login" ) } }

Executarea testelor în CI

Folosim Jenkins ca sistem de integrare continuă și rulăm testele UI pentru fiecare comitere din fiecare cerere de extragere.

Folosim fastlane scan pentru a executa testele în CI și pentru a genera rapoarte. Este util să aveți capturi de ecran atașate la aceste rapoarte pentru testele eșuate. Din păcate, scan nu oferă această funcționalitate, așa că a trebuit să o personalizăm.

În funcția tearDown() , detectăm dacă testul a eșuat și salvăm o captură de ecran a simulatorului iOS dacă a reușit.

 import EarlGrey import XCTest import UIScreenCapture override func tearDown() { if testRun!.failureCount > 0 { // name is a property of the XCTest instance // https://developer.apple.com/documentation/xctest/xctest/1500990-name takeScreenshotAndSave(as: name) } super.tearDown() } func takeScreenshotAndSave(as testCaseName: String) { let imageData = UIScreenCapture.takeSnapshotGetJPEG() let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) let filePath = "\(paths[0])/\(testCaseName).jpg" do { try imageData?.write(to: URL.init(fileURLWithPath: filePath)) } catch { XCTFail("Screenshot not written.") } }

Capturile de ecran sunt salvate în folderul Simulator și va trebui să le preluați de acolo pentru a le atașa ca artefacte de construcție. Folosim Rake pentru a ne gestiona scripturile CI. Iată cum adunăm artefactele de testare:

 def gather_test_artifacts(booted_sim_id, destination_folder) app_container_on_sim = `xcrun simctl get_app_container #{booted_sim_id} [your bundle id] data`.strip FileUtils.cp_r "#{app_container_on_sim}/Documents", destination_folder end

Recomandări cheie

Dacă sunteți în căutarea unui mod rapid și fiabil de a vă automatiza testele iOS, nu căutați mai departe decât EarlGrey. Este dezvoltat și întreținut de Google (trebuie să spun mai multe?) și, în multe privințe, este superior altor instrumente disponibile în prezent.

Va trebui să modificați puțin cadrul pentru a pregăti metode utilitare pentru a promova stabilitatea testului. Pentru a face acest lucru, puteți începe cu exemplele noastre de metode utilitare personalizate.

Vă recomandăm să testați datele blocate pentru a vă asigura că testele dvs. nu vor eșua, deoarece serverul back-end nu are toate datele de testare pe care v-ați aștepta să le aibă. Utilizați OHHTTPStubs sau un server web local similar pentru a finaliza treaba.

Când rulați testele în CI, asigurați-vă că furnizați capturi de ecran pentru cazurile eșuate, pentru a ușura depanarea.

Poate vă întrebați de ce nu am migrat încă la EarlGrey 2.0 și iată o explicație rapidă. Noua versiune a fost lansată anul trecut și promite unele îmbunătățiri față de v1.0. Din păcate, când am adoptat EarlGrey, v2.0 nu era deosebit de stabilă. Prin urmare, nu am trecut încă la v2.0. Cu toate acestea, echipa noastră așteaptă cu nerăbdare o remediere a erorilor pentru noua versiune, astfel încât să ne putem migra infrastructura în viitor.

Resurse online

Ghidul de început al EarlGrey de pe pagina de pornire GitHub este locul de unde doriți să începeți dacă luați în considerare cadrul de testare pentru proiectul dvs. Acolo, veți găsi un ghid de instalare ușor de utilizat, documentația API a instrumentului și o foaie de cheat la îndemână care listează toate metodele cadrului într-o manieră care este ușor de utilizat în timpul scrierii testelor.

Pentru informații suplimentare despre scrierea testelor automate pentru iOS, puteți consulta și una dintre postările noastre anterioare de blog.