Ein Spritzer EarlGrey – UI-Test der Toptal Talent App
Veröffentlicht: 2022-03-11Eines der wichtigsten Dinge, die Sie als Tester tun können, um Ihre Arbeit effizienter und schneller zu gestalten, ist die Automatisierung der App, die Sie testen. Es ist nicht möglich, sich ausschließlich auf manuelle Tests zu verlassen, da Sie jeden Tag, manchmal mehrmals am Tag, alle Tests ausführen müssten, um jede Änderung zu testen, die an den App-Code gesendet wird.
In diesem Artikel wird der Weg unseres Teams beschrieben, Googles EarlGrey 1.0 als das Tool zu identifizieren, das für uns im Zusammenhang mit der Automatisierung der iOS Toptal Talent-App am besten funktioniert hat. Die Tatsache, dass wir es verwenden, bedeutet nicht, dass EarlGrey das beste Testtool für alle ist – es ist einfach das, das unseren Anforderungen entspricht.
Warum wir zu EarlGrey gewechselt sind
Im Laufe der Jahre hat unser Team verschiedene mobile Apps für iOS und Android entwickelt. Am Anfang erwogen wir die Verwendung eines plattformübergreifenden UI-Testtools, das es uns ermöglichen würde, einen einzigen Satz von Tests zu schreiben und sie auf verschiedenen mobilen Betriebssystemen auszuführen. Zuerst haben wir uns für Appium entschieden, die beliebteste verfügbare Open-Source-Option.
Aber im Laufe der Zeit wurden die Einschränkungen von Appium immer deutlicher. In unserem Fall waren die beiden Hauptnachteile von Appium:
- Die fragwürdige Stabilität des Gerüsts sorgte für viele Testabplatzer.
- Der vergleichsweise langsame Update-Vorgang erschwerte unsere Arbeit.
Um den ersten Mangel von Appium zu beheben, haben wir alle möglichen Code-Anpassungen und Hacks geschrieben, um die Tests stabiler zu machen. Allerdings konnten wir nichts tun, um die zweite anzusprechen. Jedes Mal, wenn eine neue Version von iOS oder Android veröffentlicht wurde, brauchte Appium lange, um aufzuholen. Und sehr oft war das erste Update aufgrund vieler Fehler unbrauchbar. Infolgedessen waren wir oft gezwungen, unsere Tests auf einer älteren Plattformversion auszuführen oder sie vollständig abzuschalten, bis ein funktionierendes Appium-Update verfügbar war.
Dieser Ansatz war alles andere als ideal, und aufgrund dieser Probleme, zusammen mit weiteren, die wir nicht im Detail behandeln werden, haben wir uns entschieden, nach Alternativen zu suchen. Die obersten Kriterien für ein neues Testing-Tool waren erhöhte Stabilität und schnellere Updates . Nach einigen Untersuchungen haben wir uns entschieden, native Testtools für jede Plattform zu verwenden.
Also wechselten wir für das Android-Projekt zu Espresso und für die iOS-Entwicklung zu EarlGrey 1.0. Im Nachhinein können wir heute sagen, dass dies eine gute Entscheidung war. Die „verlorene“ Zeit aufgrund der Notwendigkeit, zwei verschiedene Testsätze zu schreiben und zu pflegen, einen für jede Plattform, wurde mehr als wettgemacht, da nicht so viele fehlerhafte Tests untersucht werden mussten und keine Ausfallzeiten bei Versionsaktualisierungen auftraten.
Lokale Projektstruktur
Sie müssen das Framework in dasselbe Xcode-Projekt einschließen wie die App, die Sie entwickeln. Also haben wir einen Ordner im Stammverzeichnis erstellt, um die UI-Tests zu hosten. Das Erstellen der Datei EarlGrey.swift
ist bei der Installation des Testframeworks obligatorisch und ihr Inhalt ist vordefiniert.

EarlGreyBase
ist die übergeordnete Klasse für alle Testklassen. Es enthält die allgemeinen setUp
und tearDown
Methoden, die von XCTestCase
erweitert wurden. In setUp
laden wir die Stubs hoch, die im Allgemeinen von den meisten Tests verwendet werden (mehr zum Stubbing später), und wir setzen auch einige Konfigurations-Flags, von denen wir festgestellt haben, dass sie die Stabilität der Tests erhöhen:
// 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)
Wir verwenden das Page Object Design Pattern – jeder Bildschirm in der App hat eine entsprechende Klasse, in der alle UI-Elemente und ihre möglichen Interaktionen definiert sind. Diese Klasse wird als „Seite“ bezeichnet. Die Testmethoden sind nach Merkmalen gruppiert, die sich in separaten Dateien und Klassen von den Seiten befinden.
Um Ihnen eine bessere Vorstellung davon zu geben, wie alles angezeigt wird, sehen die Bildschirme „Anmelden“ und „Passwort vergessen“ in unserer App so aus und wie sie durch Seitenobjekte dargestellt werden.

Später in diesem Artikel werden wir den Codeinhalt des Anmeldeseitenobjekts vorstellen.
Benutzerdefinierte Utility-Methoden
Die Art und Weise, wie EarlGrey die Testaktionen mit der App synchronisiert, ist nicht immer perfekt. Beispielsweise könnte es versuchen, auf eine Schaltfläche zu klicken, die noch nicht in der UI-Hierarchie geladen ist, wodurch ein Test fehlschlägt. Um dieses Problem zu vermeiden, haben wir benutzerdefinierte Methoden erstellt, um zu warten, bis Elemente im gewünschten Zustand angezeigt werden, bevor wir mit ihnen interagieren.
Hier sind ein paar Beispiele:
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 }
Eine andere Sache, die EarlGrey nicht alleine macht, ist das Scrollen des Bildschirms, bis das gewünschte Element sichtbar wird. So können wir das tun:
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 } }
Andere von uns identifizierte Hilfsmethoden, die in der API von EarlGrey fehlen, sind das Zählen von Elementen und das Lesen von Textwerten. Der Code für diese Dienstprogramme ist auf GitHub verfügbar: hier und hier.
Stubbing von API-Aufrufen
Um sicherzustellen, dass wir falsche Testergebnisse vermeiden, die durch Back-End-Serverprobleme verursacht werden, verwenden wir die OHHTTPStubs- Bibliothek, um Serveraufrufe zu simulieren. Die Dokumentation auf ihrer Homepage ist ziemlich einfach, aber wir werden zeigen, wie wir Antworten in unserer App, die die GraphQL-API verwendet, stumpfen.

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 } }
Das Laden des Stubs erfolgt durch Aufrufen der Methode setupOHTTPStub
:
StubsHelper.setupOHTTPStub(for: .login)
Alles zusammenfügen
In diesem Abschnitt wird gezeigt, wie wir alle oben beschriebenen Prinzipien verwenden, um einen tatsächlichen End-to-End-Anmeldetest zu schreiben.
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" ) } }
Laufende Tests in CI
Wir verwenden Jenkins als unser kontinuierliches Integrationssystem und führen die UI-Tests für jeden Commit in jeder Pull-Anfrage durch.
Wir verwenden fastlane scan
, um die Tests in CI auszuführen und Berichte zu erstellen. Es ist hilfreich, Screenshots für fehlgeschlagene Tests an diese Berichte anzuhängen. Leider bietet scan
diese Funktionalität nicht, also mussten wir sie individuell erstellen.
In der Funktion tearDown()
erkennen wir, ob der Test fehlgeschlagen ist, und speichern gegebenenfalls einen Screenshot des iOS-Simulators.
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.") } }
Die Screenshots werden im Simulator-Ordner gespeichert und Sie müssen sie von dort abrufen, um sie als Build-Artefakte anzuhängen. Wir verwenden Rake
, um unsere CI-Skripte zu verwalten. So sammeln wir die Testartefakte:
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
Die zentralen Thesen
Wenn Sie nach einer schnellen und zuverlässigen Möglichkeit suchen, Ihre iOS-Tests zu automatisieren, sind Sie bei EarlGrey genau richtig. Es wird von Google entwickelt und gewartet (muss ich noch mehr sagen?) und ist in vielerlei Hinsicht anderen heute verfügbaren Tools überlegen.
Sie müssen ein wenig am Framework herumbasteln, um Hilfsmethoden zur Förderung der Teststabilität vorzubereiten. Dazu können Sie mit unseren Beispielen für benutzerdefinierte Hilfsmethoden beginnen.
Wir empfehlen das Testen von Stub-Daten, um sicherzustellen, dass Ihre Tests nicht fehlschlagen, weil der Back-End-Server nicht über alle Testdaten verfügt, die Sie erwarten würden. Verwenden Sie OHHTTPStubs
oder einen ähnlichen lokalen Webserver, um die Arbeit zu erledigen.
Wenn Sie Ihre Tests in CI ausführen, stellen Sie sicher, dass Sie Screenshots für die fehlgeschlagenen Fälle bereitstellen, um das Debuggen zu vereinfachen.
Sie fragen sich vielleicht, warum wir noch nicht auf EarlGrey 2.0 migriert sind, und hier ist eine kurze Erklärung. Die neue Version wurde letztes Jahr veröffentlicht und verspricht einige Verbesserungen gegenüber v1.0. Unglücklicherweise war v2.0, als wir EarlGrey einführten, nicht besonders stabil. Daher haben wir noch nicht auf v2.0 umgestellt. Unser Team wartet jedoch gespannt auf eine Fehlerbehebung für die neue Version, damit wir unsere Infrastruktur in Zukunft migrieren können.
Internetquellen
Der Leitfaden Erste Schritte von EarlGrey auf der GitHub-Homepage ist der Ausgangspunkt, an dem Sie beginnen sollten, wenn Sie das Testframework für Ihr Projekt in Betracht ziehen. Dort finden Sie eine benutzerfreundliche Installationsanleitung, die API-Dokumentation des Tools und einen praktischen Spickzettel, der alle Methoden des Frameworks auf eine Weise auflistet, die beim Schreiben Ihrer Tests einfach zu verwenden ist.
Weitere Informationen zum Schreiben automatisierter Tests für iOS finden Sie auch in einem unserer früheren Blogbeiträge.