Modern QA2026Основы Appium
Join

Course09 Mobile & Cross-Platform Testing

Cutting-edge · Chapter 09

Основы Appium

Updated Jul 2026

Что такое Appium и почему он доминирует

Appium — наиболее широко используемый кроссплатформенный фреймворк для мобильного тестирования. Он автоматизирует нативные, гибридные и мобильные веб-приложения на iOS и Android с использованием протокола W3C WebDriver. Ключевое преимущество: вы пишете тесты на любом языке (Python, Java, JavaScript, C#, Ruby) и запускаете их на реальных устройствах или эмуляторах без модификации вашего приложения.

Appium был создан Джонатаном Липпсом и значительно эволюционировал в направлении AI-нативных паттернов тестирования в релизе 3.x.

Архитектура Appium (2026)

+------------------+     +-------------------+     +---------------+
|  Test Script     |---->|  Appium Server    |---->|  Device/      |
|  (any language)  |     |  (W3C WebDriver)  |     |  Emulator     |
|                  |<----|                   |<----|               |
+------------------+     +-------------------+     +---------------+
                               |
                          UiAutomator2 (Android)
                          XCUITest (iOS)
                          Espresso (Android)
                          Mac2 (macOS)
                          Windows (WinApp)

Каждая платформа использует нативный драйвер автоматизации:

  • UiAutomator2: фреймворк Google для UI-тестирования Android. Используется чаще всего.
  • XCUITest: нативный фреймворк тестирования Apple для iOS. Обязателен для автоматизации iOS.
  • Espresso: внутрипроцессный фреймворк Google для тестирования Android. Быстрее, но требует исходный код приложения.

Написание тестов на Appium

Пример на Python: сценарий входа

# tests/mobile/test_login_flow.py
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
import pytest

@pytest.fixture
def driver():
    options = UiAutomator2Options()
    options.platform_name = "Android"
    options.device_name = "Pixel 7"
    options.app = "./builds/app-release.apk"
    options.automation_name = "UiAutomator2"
    options.no_reset = False  # Clean state for each test

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

def test_login_with_valid_credentials(driver):
    # Wait for splash screen to finish
    driver.implicitly_wait(10)

    # Enter credentials
    email_field = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "email-input")
    email_field.send_keys("test@example.com")

    password_field = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password-input")
    password_field.send_keys("secureP@ss123")

    # Tap login button
    login_btn = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-button")
    login_btn.click()

    # Verify navigation to dashboard
    dashboard = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "dashboard-screen")
    assert dashboard.is_displayed()

def test_login_with_invalid_credentials(driver):
    driver.implicitly_wait(10)

    email_field = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "email-input")
    email_field.send_keys("test@example.com")

    password_field = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password-input")
    password_field.send_keys("wrongpassword")

    login_btn = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-button")
    login_btn.click()

    # Verify error message is shown
    error_msg = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-error")
    assert error_msg.is_displayed()
    assert "Invalid" in error_msg.text

def test_biometric_login_prompt(driver):
    """Verify that biometric authentication is offered when available."""
    driver.implicitly_wait(10)

    biometric_btn = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "biometric-login")

    # Check if biometric is available on this device
    is_biometric_available = driver.execute_script(
        "mobile: isBiometricEnrolled"
    )

    if is_biometric_available:
        assert biometric_btn.is_displayed()
        biometric_btn.click()
        # Simulate successful fingerprint
        driver.execute_script("mobile: fingerprint", {"fingerprintId": 1})
        dashboard = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "dashboard-screen")
        assert dashboard.is_displayed()
    else:
        # Biometric button should be hidden or disabled
        assert not biometric_btn.is_enabled()

Стратегии поиска элементов

Выбор правильной стратегии поиска элементов критически важен для стабильности тестов:

Стратегия Синтаксис Стабильность Скорость Когда использовать
ACCESSIBILITY_ID AppiumBy.ACCESSIBILITY_ID Лучшая Быстро Выбор по умолчанию -- стабилен при изменении вёрстки
ID AppiumBy.ID Хорошая Быстро Когда accessibility ID не задан
CLASS_NAME AppiumBy.CLASS_NAME Низкая Средне Только с дополнительной фильтрацией
XPATH AppiumBy.XPATH Худшая Медленно Крайний случай -- хрупкий, медленный
IMAGE AppiumBy.IMAGE Хорошая Средне Визуальное сопоставление элементов

Лучшая практика: всегда используйте Accessibility ID

# GOOD: Accessibility IDs are stable and meaningful
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "checkout-button")

# BAD: XPath is fragile and slow
driver.find_element(
    AppiumBy.XPATH,
    "//android.widget.LinearLayout[3]/android.widget.Button[2]"
)

# BAD: Resource IDs are platform-specific and can change
driver.find_element(
    AppiumBy.ID,
    "com.myapp:id/btn_checkout_v2_redesign_final"
)

Accessibility ID имеют двойную пользу: они делают ваши тесты стабильными И улучшают доступность вашего приложения. Каждый тестируемый элемент должен иметь accessibility-метку.

Эволюция Appium в сторону AI-нативного тестирования

Appium 3.x вводит паттерны, согласованные с рабочими процессами AI-агентов:

Возможность Традиционный Appium AI-нативный Appium
Поиск элементов Явные селекторы (XPath, ID) Сопоставление по изображению, описания на естественном языке
Создание тестов Ручное написание скриптов Агент наблюдает за приложением, генерирует последовательности взаимодействий
Восстановление при сбоях Тест падает при первом неожиданном состоянии Агент рассуждает об альтернативных путях
Проверки Захардкоженные ожидаемые значения Модель оценивает «выглядит ли это корректно?»
Обслуживание Обновление селекторов при изменении UI Самовосстановление через визуальное/семантическое сопоставление

Поиск элементов по изображению

# AI-native element finding with Appium image plugin
from appium.webdriver.common.appiumby import AppiumBy
import base64

# Instead of fragile selectors:
# driver.find_element(AppiumBy.XPATH,
#     "//android.widget.Button[@resource-id='com.app:id/submit_btn']")

# Use image-based matching:
with open("reference_images/submit_button.png", "rb") as f:
    submit_button_b64 = base64.b64encode(f.read()).decode()

submit_btn = driver.find_element(AppiumBy.IMAGE, submit_button_b64)
submit_btn.click()

Самовосстанавливающиеся локаторы

# Pattern: try multiple locator strategies with fallback
def find_element_resilient(driver, strategies):
    """Try multiple locator strategies until one succeeds."""
    last_error = None
    for strategy, value in strategies:
        try:
            element = driver.find_element(strategy, value)
            if element.is_displayed():
                return element
        except Exception as e:
            last_error = e
            continue
    raise last_error

# Usage:
checkout_btn = find_element_resilient(driver, [
    (AppiumBy.ACCESSIBILITY_ID, "checkout-button"),
    (AppiumBy.ID, "com.myapp:id/btn_checkout"),
    (AppiumBy.XPATH, "//android.widget.Button[contains(@text, 'Checkout')]"),
])

Распространённые паттерны Appium

Page Object Model для мобильных приложений

# pages/login_page.py
class LoginPage:
    def __init__(self, driver):
        self.driver = driver

    @property
    def email_field(self):
        return self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "email-input")

    @property
    def password_field(self):
        return self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password-input")

    @property
    def login_button(self):
        return self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-button")

    @property
    def error_message(self):
        return self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login-error")

    def login(self, email, password):
        self.email_field.send_keys(email)
        self.password_field.send_keys(password)
        self.login_button.click()

    def is_error_displayed(self):
        try:
            return self.error_message.is_displayed()
        except:
            return False

Управление состоянием приложения

# Reset app state between tests
def reset_app(driver):
    """Reset the app to initial state without reinstalling."""
    driver.reset()

# Handle permission dialogs
def handle_permission_dialog(driver, allow=True):
    """Handle system permission dialogs (camera, location, etc.)."""
    try:
        if allow:
            driver.find_element(
                AppiumBy.ID, "com.android.permissioncontroller:id/permission_allow_button"
            ).click()
        else:
            driver.find_element(
                AppiumBy.ID, "com.android.permissioncontroller:id/permission_deny_button"
            ).click()
    except:
        pass  # No dialog present

# Wait for network operations
def wait_for_loading(driver, timeout=30):
    """Wait for loading spinner to disappear."""
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC

    try:
        WebDriverWait(driver, timeout).until(
            EC.invisibility_of_element_located(
                (AppiumBy.ACCESSIBILITY_ID, "loading-spinner")
            )
        )
    except:
        pass

Appium остаётся отраслевым стандартом для кроссплатформенного мобильного тестирования благодаря гибкости в выборе языка, поддержке реальных устройств и эволюции в сторону AI-нативных паттернов. Освойте его — и сможете автоматизировать тестирование на любой мобильной платформе.