TL;DR

  • Appium automatiza apps iOS y Android con protocolo WebDriver — un framework, ambas plataformas
  • Setup: servidor Appium, SDKs de plataforma (Android Studio/Xcode), biblioteca cliente
  • Encuentra elementos por accessibility id, xpath o locators específicos de plataforma
  • Soporta gestos (swipe, scroll, tap), dispositivos reales y emuladores/simuladores
  • Integra con CI/CD vía Appium en Docker o servicios cloud (BrowserStack, Sauce Labs)

Ideal para: Equipos QA testeando apps móviles cross-platform Omite si: Testeas solo Android (usa Espresso) o solo iOS (usa XCUITest) Tiempo de lectura: 20 minutos

Tu app funciona en iOS. Se cae en Android. O viceversa. Testing manual en ambas plataformas duplica el esfuerzo. La paridad de features se vuelve una pesadilla.

Appium resuelve el testing móvil cross-platform. Escribe un test, corre en iOS y Android. Mismo lenguaje, mismo framework, mismo pipeline CI.

Este tutorial cubre Appium desde setup hasta integración CI/CD — todo lo que necesitas para automatizar apps móviles.

¿Qué es Appium?

Appium es un framework open-source para automatización móvil. Automatiza apps nativas, híbridas y web móviles en iOS y Android usando el protocolo WebDriver.

Cómo funciona Appium:

  1. Tu código de test envía comandos al servidor Appium
  2. Appium traduce comandos a acciones específicas de plataforma
  3. UIAutomator2 (Android) o XCUITest (iOS) ejecuta las acciones
  4. Los resultados regresan por la misma cadena

Por qué Appium:

  • Cross-platform — mismos tests para iOS y Android
  • Agnóstico de lenguaje — Java, Python, JavaScript, Ruby, C#
  • Sin modificación de app — testea builds de producción reales
  • Estándar WebDriver — API familiar para usuarios de Selenium
  • Open-source — gratis, comunidad activa, actualizaciones regulares

Configuración del Entorno

Prerrequisitos

Para Android:

  • Java JDK 11+
  • Android Studio con SDK
  • Variable de entorno ANDROID_HOME
  • Depuración USB habilitada en dispositivo

Para iOS (solo macOS):

  • Xcode con Command Line Tools
  • Simulador iOS o dispositivo real
  • Cuenta Apple Developer (para dispositivos reales)

Instalando Appium

# Instalar Appium 2.x globalmente
npm install -g appium

# Verificar instalación
appium --version

# Instalar drivers de plataforma
appium driver install uiautomator2  # Android
appium driver install xcuitest      # iOS

# Listar drivers instalados
appium driver list --installed

Appium Inspector

Instala Appium Inspector para inspección de elementos:

Desired Capabilities

Capabilities le dicen a Appium qué dispositivo y app usar.

Android Capabilities

from appium import webdriver
from appium.options.android import UiAutomator2Options

options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "Pixel 6"
options.app = "/path/to/app.apk"
options.automation_name = "UiAutomator2"

# Opcional pero recomendado
options.no_reset = True  # No resetear estado de app
options.full_reset = False
options.new_command_timeout = 300

driver = webdriver.Remote("http://localhost:4723", options=options)

iOS Capabilities

from appium import webdriver
from appium.options.ios import XCUITestOptions

options = XCUITestOptions()
options.platform_name = "iOS"
options.device_name = "iPhone 14"
options.platform_version = "16.0"
options.app = "/path/to/app.app"
options.automation_name = "XCUITest"

# Para dispositivos reales
options.udid = "device-udid-here"

driver = webdriver.Remote("http://localhost:4723", options=options)

Testeando Apps Instaladas

# Android - usa app package y activity
options.app_package = "com.example.myapp"
options.app_activity = "com.example.myapp.MainActivity"

# iOS - usa bundle ID
options.bundle_id = "com.example.myapp"

Encontrando Elementos

Estrategias de Locators

from appium.webdriver.common.appiumby import AppiumBy

# Accessibility ID (recomendado - funciona cross-platform)
element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")

# ID (Android resource-id)
element = driver.find_element(AppiumBy.ID, "com.example:id/login_button")

# XPath (más lento, pero flexible)
element = driver.find_element(AppiumBy.XPATH, "//android.widget.Button[@text='Login']")

# Class name
element = driver.find_element(AppiumBy.CLASS_NAME, "android.widget.EditText")

# Android UIAutomator
element = driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiSelector().text("Login")'
)

# iOS predicate string
element = driver.find_element(
    AppiumBy.IOS_PREDICATE,
    'label == "Login" AND type == "XCUIElementTypeButton"'
)

# iOS class chain
element = driver.find_element(
    AppiumBy.IOS_CLASS_CHAIN,
    '**/XCUIElementTypeButton[`label == "Login"`]'
)

Esperando Elementos

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)

# Esperar que elemento sea visible
element = wait.until(
    EC.visibility_of_element_located((AppiumBy.ACCESSIBILITY_ID, "welcome_message"))
)

# Esperar que elemento sea clickeable
element = wait.until(
    EC.element_to_be_clickable((AppiumBy.ACCESSIBILITY_ID, "submit_button"))
)

# Esperar que elemento desaparezca
wait.until(
    EC.invisibility_of_element_located((AppiumBy.ACCESSIBILITY_ID, "loading_spinner"))
)

Interacciones Básicas

Tap, Escribir, Limpiar

# Tap/Click
login_button = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")
login_button.click()

# Escribir texto
username_field = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "username")
username_field.send_keys("testuser")

# Limpiar campo
username_field.clear()

# Obtener texto
message = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "message")
print(message.text)

# Verificar si está mostrado/habilitado
assert login_button.is_displayed()
assert login_button.is_enabled()

Manejando Teclado

# Ocultar teclado
driver.hide_keyboard()

# Verificar si teclado está mostrado (Android)
is_keyboard_shown = driver.is_keyboard_shown()

Gestos

Swipe y Scroll

from appium.webdriver.common.touch_action import TouchAction

# Swipe simple (start_x, start_y, end_x, end_y, duration_ms)
driver.swipe(500, 1500, 500, 500, 800)

# Scroll a elemento (Android)
driver.find_element(
    AppiumBy.ANDROID_UIAUTOMATOR,
    'new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text("Target Text"))'
)

# Scroll por dirección
def scroll_down(driver):
    size = driver.get_window_size()
    start_x = size['width'] // 2
    start_y = size['height'] * 0.8
    end_y = size['height'] * 0.2
    driver.swipe(start_x, start_y, start_x, end_y, 500)

def scroll_up(driver):
    size = driver.get_window_size()
    start_x = size['width'] // 2
    start_y = size['height'] * 0.2
    end_y = size['height'] * 0.8
    driver.swipe(start_x, start_y, start_x, end_y, 500)

W3C Actions (Recomendado)

from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.actions import interaction
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.pointer_input import PointerInput

# Tap en coordenadas
actions = ActionChains(driver)
actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
actions.w3c_actions.pointer_action.move_to_location(100, 200)
actions.w3c_actions.pointer_action.pointer_down()
actions.w3c_actions.pointer_action.pause(0.1)
actions.w3c_actions.pointer_action.release()
actions.perform()

# Presión larga
element = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "item")
actions = ActionChains(driver)
actions.click_and_hold(element).pause(2).release().perform()

Ejemplo Completo de Test

import pytest
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class TestLoginFlow:
    @pytest.fixture(autouse=True)
    def setup(self):
        options = UiAutomator2Options()
        options.platform_name = "Android"
        options.device_name = "emulator-5554"
        options.app = "./app-debug.apk"
        options.automation_name = "UiAutomator2"
        options.no_reset = False

        self.driver = webdriver.Remote("http://localhost:4723", options=options)
        self.wait = WebDriverWait(self.driver, 15)
        yield
        self.driver.quit()

    def test_successful_login(self):
        # Ingresar username
        username = self.wait.until(
            EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "username_input"))
        )
        username.send_keys("testuser@example.com")

        # Ingresar password
        password = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password_input")
        password.send_keys("securepassword123")

        # Ocultar teclado
        self.driver.hide_keyboard()

        # Tap en botón login
        login_btn = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")
        login_btn.click()

        # Verificar mensaje de bienvenida
        welcome = self.wait.until(
            EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "welcome_message"))
        )
        assert "Welcome" in welcome.text

    def test_invalid_credentials(self):
        username = self.wait.until(
            EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "username_input"))
        )
        username.send_keys("wrong@example.com")

        password = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password_input")
        password.send_keys("wrongpassword")

        self.driver.hide_keyboard()

        login_btn = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button")
        login_btn.click()

        # Verificar mensaje de error
        error = self.wait.until(
            EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "error_message"))
        )
        assert "Invalid credentials" in error.text

Patrón Page Object

# pages/base_page.py
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 15)

    def find(self, locator):
        return self.wait.until(EC.presence_of_element_located(locator))

    def click(self, locator):
        self.find(locator).click()

    def type_text(self, locator, text):
        element = self.find(locator)
        element.clear()
        element.send_keys(text)


# pages/login_page.py
class LoginPage(BasePage):
    USERNAME = (AppiumBy.ACCESSIBILITY_ID, "username_input")
    PASSWORD = (AppiumBy.ACCESSIBILITY_ID, "password_input")
    LOGIN_BTN = (AppiumBy.ACCESSIBILITY_ID, "login_button")
    ERROR_MSG = (AppiumBy.ACCESSIBILITY_ID, "error_message")

    def login(self, username, password):
        self.type_text(self.USERNAME, username)
        self.type_text(self.PASSWORD, password)
        self.driver.hide_keyboard()
        self.click(self.LOGIN_BTN)

    def get_error_message(self):
        return self.find(self.ERROR_MSG).text


# tests/test_login.py
class TestLogin:
    def test_login_success(self, driver):
        login_page = LoginPage(driver)
        login_page.login("user@example.com", "password123")
        # assertions...

Testing en Dispositivos Reales

Dispositivo Android Real

# Conectar dispositivo por USB
adb devices

# Obtener nombre del dispositivo
adb shell getprop ro.product.model

# Habilitar depuración USB en opciones de desarrollador
options.device_name = "Pixel 6"
options.udid = "emulator-5554"  # o serial real del dispositivo

Dispositivo iOS Real

Requiere provisioning profile y firma:

options.udid = "00008030-001234567890"
options.xcode_org_id = "TEAM_ID"
options.xcode_signing_id = "iPhone Developer"

Granjas de Dispositivos Cloud

BrowserStack:

from appium import webdriver

options = {
    "platformName": "Android",
    "appium:deviceName": "Samsung Galaxy S23",
    "appium:platformVersion": "13.0",
    "appium:app": "bs://app-id",
    "bstack:options": {
        "userName": "YOUR_USERNAME",
        "accessKey": "YOUR_ACCESS_KEY",
        "projectName": "My Project",
        "buildName": "Build 1.0"
    }
}

driver = webdriver.Remote(
    "https://hub-cloud.browserstack.com/wd/hub",
    options=options
)

Integración CI/CD

GitHub Actions

name: Mobile Tests

on: [push, pull_request]

jobs:
  android-tests:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install dependencies
        run: |
          pip install appium-python-client pytest
          npm install -g appium
          appium driver install uiautomator2

      - name: Start Android emulator
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 33
          script: |
            appium &
            sleep 10
            pytest tests/android/ --junitxml=results.xml

      - name: Upload results
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: results.xml

Appium con Asistencia de IA

Las herramientas de IA pueden ayudar a escribir y depurar tests de Appium.

Lo que la IA hace bien:

  • Generar page objects desde screenshots de la app
  • Sugerir estrategias de locators
  • Depurar errores de elementos no encontrados
  • Convertir tests entre lenguajes

Lo que aún necesita humanos:

  • Entender lógica de negocio de la app
  • Elegir entre locators estables e inestables
  • Optimizar tiempo de ejecución de tests
  • Manejar edge cases específicos de plataforma

FAQ

¿Qué es Appium?

Appium es un framework open-source para automatización móvil de testing de aplicaciones iOS y Android. Usa el protocolo WebDriver, permitiendo escribir tests en cualquier lenguaje que tenga cliente WebDriver (Java, Python, JavaScript, Ruby, C#). Appium puede testear apps nativas, híbridas y web móviles sin requerir modificación — testeas builds reales de producción.

¿Appium es gratis?

Sí, Appium es completamente gratis y open-source bajo licencia Apache 2.0. No hay ediciones enterprise, funciones premium ni límites de uso. Todo el framework, incluyendo todos los drivers (UiAutomator2, XCUITest), está disponible sin costo.

¿Appium vs Espresso/XCUITest — cuál es mejor?

Appium sobresale en testing cross-platform — escribe tests una vez, corre en iOS y Android. Espresso (Android) y XCUITest (iOS) son específicos de plataforma pero más rápidos, estables y con integración más profunda con el OS. Usa Appium cuando necesites cobertura cross-platform con un código. Usa herramientas nativas cuando necesites máxima velocidad y confiabilidad para una plataforma.

¿Puede Appium testear iOS y Android?

Sí, esta es la ventaja principal de Appium. El mismo código de test puede correr en ambas plataformas con cambios mínimos — usualmente solo locators diferentes para elementos específicos de plataforma. Escribes tests en tu lenguaje preferido, y Appium traduce comandos a la automatización de plataforma apropiada (UiAutomator2 para Android, XCUITest para iOS).

Recursos Oficiales

Ver También