Por que os testes E2E são o "modo deus" para o desenvolvedor iOS?
\n\nImagine: você escreveu um código Swift perfeito, tudo compila, os testes unitários estão verdes. Você lança a versão — e uma hora depois surgem bugs: o botão "Comprar" não funciona no iPhone 14 Pro, e no iPad Mini a tela de login parece uma pintura abstrata. Familiar? É aqui que entram em cena os testes E2E Swift — a única maneira de ter certeza de que seu aplicativo funciona como um relógio nas mãos de um usuário real.
\n\nOs testes End-to-End (E2E) simulam o cenário completo do usuário: desde a inicialização do aplicativo até a conclusão da ação desejada (compra, registro, envio de formulário). Neste artigo, vamos detalhar como configurar o XCUITest Swift para que seus testes não falhem por motivos fúteis e o pipeline CI/CD não se torne um inferno.
\n\nO que você aprenderá:
\n- \n
- Como escrever testes de UI estáveis com XCUITest \n
- Page Object Model para Swift — exemplo real \n
- Tratamento de operações assíncronas e esperas \n
- Integração de testes E2E em CI/CD (GitHub Actions + Fastlane) \n
- Top 3 erros de iniciantes e como evitá-los \n
Ferramentas: o que vamos precisar
\n\nPara a automação de testes Swift, usamos três pilares:
\n- \n
- XCUITest — framework da Apple, integrado ao XCTest. Permite emular gestos e verificar estados de elementos de UI. \n
- Xcode Test Plan — para configurar esquemas de teste (diferentes idiomas, orientações, dados). \n
- Fastlane + GitHub Actions — para executar testes no CI. \n
A sintaxe básica do XCUITest é simples: você acessa o aplicativo via XCUIApplication(), localiza elementos com buttons["Login"] ou textFields["email"] e emula ações.
Escrevendo o primeiro teste E2E: cenário de login
\n\nSuponha que temos uma tela de login com campos de Email, Senha e um botão "Entrar". O objetivo: verificar que, ao inserir dados corretos, o usuário é direcionado para a tela principal.
\n\nimport XCTest\n\nclass LoginE2ETests: XCTestCase {\n \n let app = XCUIApplication()\n \n override func setUpWithError() throws {\n continueAfterFailure = false\n app.launch()\n }\n \n func testSuccessfulLogin() throws {\n // 1. Localizar elementos\n let emailField = app.textFields["emailTextField"]\n let passwordField = app.secureTextFields["passwordTextField"]\n let loginButton = app.buttons["signInButton"]\n \n // 2. Ações do usuário\n emailField.tap()\n emailField.typeText("user@example.com")\n \n passwordField.tap()\n passwordField.typeText("ValidPassword123")\n \n loginButton.tap()\n \n // 3. Verificar resultado\n let dashboard = app.staticTexts["dashboardTitle"]\n XCTAssertTrue(dashboard.waitForExistence(timeout: 5))\n }\n}\n\nPonto importante: use waitForExistence(timeout:) — sem isso, o teste pode falhar se a animação ou a requisição de rede atrasar. Isso é básico nos testes E2E Swift.
Por que esse código pode falhar?
\n- \n
- O campo de entrada pode não ser o primeiro responder — adicione
.tap()antes detypeText. \n - O botão pode estar coberto pelo teclado — use
app.keyboards.buttons["Return"].tap(). \n - Os rótulos de texto podem variar conforme a localização — sempre use o Accessibility Identifier. \n
Page Object Model (POM) para Swift: deixando os testes limpos
\n\nQuando o aplicativo cresce, os testes viram uma bagunça. A solução é o Page Object Model. Criamos classes separadas para cada tela, que encapsulam a lógica de localização de elementos e ações.
\n\nExemplo para a tela de login:
\n\n// Lo