A Splash of EarlGrey – UI Testando o aplicativo Toptal Talent
Publicados: 2022-03-11Uma das coisas mais importantes que você pode fazer como testador para tornar seu trabalho mais eficiente e rápido é automatizar o aplicativo que está testando. Confiar apenas em testes manuais não é viável, pois você precisaria executar o conjunto completo de testes todos os dias, às vezes várias vezes ao dia, testando todas as alterações enviadas ao código do aplicativo.
Este artigo descreverá a jornada de nossa equipe para identificar o EarlGrey 1.0 do Google como a ferramenta que funcionou melhor para nós no contexto de automatizar o aplicativo iOS Toptal Talent. O fato de estarmos usando isso não significa que o EarlGrey seja a melhor ferramenta de teste para todos - é a que atendeu às nossas necessidades.
Por que fizemos a transição para EarlGrey
Ao longo dos anos, nossa equipe construiu diferentes aplicativos móveis para iOS e Android. No início, consideramos o uso de uma ferramenta de teste de interface do usuário multiplataforma que nos permitiria escrever um único conjunto de testes e executá-los em diferentes sistemas operacionais móveis. Primeiro, optamos pelo Appium, a opção de código aberto mais popular disponível.
Mas com o passar do tempo, as limitações do Appium tornaram-se cada vez mais óbvias. No nosso caso, as duas principais desvantagens do Appium foram:
- A estabilidade questionável da estrutura causou muitos testes de flocos.
- O processo de atualização comparativamente lento prejudicou nosso trabalho.
Para mitigar a primeira falha do Appium, escrevemos todos os tipos de ajustes de código e hacks para tornar os testes mais estáveis. No entanto, não havia nada que pudéssemos fazer para resolver o segundo. Toda vez que uma nova versão do iOS ou Android era lançada, o Appium demorava muito para se atualizar. E muitas vezes, por ter muitos bugs, a atualização inicial era inutilizável. Como resultado, muitas vezes fomos forçados a continuar executando nossos testes em uma versão mais antiga da plataforma ou desativá-los completamente até que uma atualização funcional do Appium fosse disponibilizada.
Essa abordagem estava longe de ser a ideal e, devido a esses problemas, juntamente com outros que não abordaremos em detalhes, decidimos procurar alternativas. Os principais critérios para uma nova ferramenta de teste foram maior estabilidade e atualizações mais rápidas . Após alguma investigação, decidimos usar ferramentas de teste nativas para cada plataforma.
Então, fizemos a transição para o Espresso para o projeto Android e para o EarlGrey 1.0 para o desenvolvimento iOS. Em retrospectiva, agora podemos dizer que esta foi uma boa decisão. O tempo “perdido” devido à necessidade de escrever e manter dois conjuntos diferentes de testes, um para cada plataforma, foi mais do que compensado por não precisar investigar tantos testes esquisitos e não ter tempo de inatividade nas atualizações de versão.
Estrutura do Projeto Local
Você precisará incluir a estrutura no mesmo projeto Xcode que o aplicativo que está desenvolvendo. Então criamos uma pasta no diretório raiz para hospedar os testes de UI. A criação do arquivo EarlGrey.swift
é obrigatória ao instalar a estrutura de teste e seu conteúdo é predefinido.

EarlGreyBase
é a classe pai para todas as classes de teste. Ele contém os métodos gerais setUp
e tearDown
, estendidos de XCTestCase
. Em setUp
, carregamos os stubs que geralmente serão usados pela maioria dos testes (mais sobre stubs posteriormente) e também definimos alguns sinalizadores de configuração que notamos aumentam a estabilidade dos testes:
// 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)
Usamos o padrão de design Page Object - cada tela do aplicativo possui uma classe correspondente onde são definidos todos os elementos da interface do usuário e suas possíveis interações. Essa classe é chamada de “página”. Os métodos de teste são agrupados por recursos que residem em arquivos e classes separados das páginas.
Para você ter uma ideia melhor de como tudo é exibido, é assim que as telas de Login e Esqueci a Senha se parecem em nosso aplicativo e como elas são representadas por objetos de página.

Mais adiante no artigo, apresentaremos o conteúdo do código do objeto Página de login.
Métodos de Utilidade Personalizados
A forma como EarlGrey sincroniza as ações de teste com o aplicativo nem sempre é perfeita. Por exemplo, ele pode tentar clicar em um botão que ainda não está carregado na hierarquia da interface do usuário, fazendo com que um teste falhe. Para evitar esse problema, criamos métodos personalizados para aguardar até que os elementos apareçam no estado desejado antes de interagirmos com eles.
Aqui estão alguns exemplos:
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 }
Uma outra coisa que EarlGrey não está fazendo por conta própria é rolar a tela até que o elemento desejado fique visível. Aqui está como podemos fazer isso:
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 } }
Outros métodos utilitários ausentes da API do EarlGrey que identificamos são a contagem de elementos e a leitura de valores de texto. O código para esses utilitários está disponível no GitHub: aqui e aqui.
Encaminhamento de chamadas de API
Para garantir que evitamos resultados de teste falsos causados por problemas de servidor de back-end, usamos a biblioteca OHHTTPStubs para simular chamadas de servidor. A documentação em sua página inicial é bastante direta, mas apresentaremos como stub respostas em nosso aplicativo, que usa a API 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 } }
O carregamento do stub é realizado chamando o método setupOHTTPStub
:
StubsHelper.setupOHTTPStub(for: .login)
Juntando tudo
Esta seção demonstrará como usamos todos os princípios descritos acima para escrever um teste de login de ponta a ponta 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" ) } }
Executando testes no CI
Usamos Jenkins como nosso sistema de integração contínua e executamos os testes de interface do usuário para cada commit em cada pull request.
Usamos fastlane scan
para executar os testes em CI e gerar relatórios. É útil ter capturas de tela anexadas a esses relatórios para testes com falha. Infelizmente, a scan
não fornece essa funcionalidade, então tivemos que personalizá-la.
Na função tearDown()
, detectamos se o teste falhou e salvamos uma captura de tela do simulador do iOS, caso tenha falhado.
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.") } }
As capturas de tela são salvas na pasta do Simulador e você precisará buscá-las de lá para anexá-las como artefatos de construção. Usamos o Rake
para gerenciar nossos scripts de CI. É assim que reunimos os artefatos de teste:
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
Principais conclusões
Se você está procurando uma maneira rápida e confiável de automatizar seus testes de iOS, não procure mais, EarlGrey. Ele é desenvolvido e mantido pelo Google (preciso dizer mais?), e em muitos aspectos, é superior a outras ferramentas disponíveis hoje.
Você precisará mexer um pouco com a estrutura para preparar métodos utilitários para promover a estabilidade do teste. Para fazer isso, você pode começar com nossos exemplos de métodos utilitários personalizados.
Recomendamos testar em dados de stub para garantir que seus testes não falhem porque o servidor de back-end não possui todos os dados de teste que você espera que ele tenha. Use OHHTTPStubs
ou um servidor web local semelhante para fazer o trabalho.
Ao executar seus testes no CI, certifique-se de fornecer capturas de tela para os casos com falha para facilitar a depuração.
Você pode estar se perguntando por que ainda não migramos para EarlGrey 2.0, e aqui está uma explicação rápida. A nova versão foi lançada no ano passado e promete algumas melhorias em relação à v1.0. Infelizmente, quando adotamos EarlGrey, a v2.0 não era particularmente estável. Portanto, ainda não fizemos a transição para a v2.0. No entanto, nossa equipe aguarda ansiosamente uma correção de bug para a nova versão para que possamos migrar nossa infraestrutura no futuro.
Recursos online
O guia de introdução do EarlGrey na página inicial do GitHub é o lugar de onde você deseja começar se estiver considerando a estrutura de teste para o seu projeto. Lá, você encontrará um guia de instalação fácil de usar, a documentação da API da ferramenta e uma folha de dicas útil listando todos os métodos do framework de uma maneira simples de usar ao escrever seus testes.
Para obter informações adicionais sobre como escrever testes automatizados para iOS, você também pode conferir uma de nossas postagens de blog anteriores.