Разработка E2E-тестов для мобильного приложения (Appium)
Appium — единственный инструмент в мобильном тестировании, который покрывает iOS и Android одним тест-кодом. Это его главное преимущество и одновременно источник большинства проблем. За универсальностью стоит слой абстракции, который иногда ведёт себя непредсказуемо. Написать стабильный Appium-тест сложнее, чем кажется.
Архитектура: Appium 2 vs Appium 1
Appium 2 — не просто версия, это другая концепция. Вместо монолитного сервера — ядро плюс отдельно устанавливаемые драйверы:
npm install -g appium@next
appium driver install uiautomator2 # Android
appium driver install xcuitest # iOS
UIAutomator2Driver для Android, XCUITestDriver для iOS. Оба поддерживают W3C WebDriver Protocol, что делает их совместимыми с WebdriverIO, Selenium Grid и стандартными client-библиотеками.
Версии, с которыми работаем в 2024–2025:
- Appium 2.5+
-
appium-uiautomator2-driver3.x -
appium-xcuitest-driver7.x -
WebdriverIO8.x (JS/TS) илиAppium-Python-Client4.x (Python)
Настройка сервера и Capabilities
Самое болезненное место — правильный набор Capabilities. Неверный platformVersion или отсутствующий automationName — и сессия не поднимается с неинформативной ошибкой.
Минимальный рабочий конфиг для Android (WebdriverIO):
const capabilities = {
platformName: 'Android',
'appium:automationName': 'UiAutomator2',
'appium:deviceName': 'emulator-5554',
'appium:app': path.resolve('./apps/myapp.apk'),
'appium:newCommandTimeout': 240,
'appium:noReset': false,
};
Для iOS добавляется udid устройства, xcodeOrgId и xcodeSigningId для реального девайса. На симуляторе проще — но симулятор не даёт результаты по производительности и Push Notifications.
Page Object и структура тестов
Сырой Appium-код без паттерна — кошмар для поддержки. Каждый $('//XCUIElementTypeButton[@name="Login"]') дублируется в десятках тестов, и при изменении UI нужно исправлять всё сразу.
Page Object с WebdriverIO:
class LoginPage {
get emailField() { return $('~email_input'); } // accessibilityId
get passwordField() { return $('~password_input'); }
get submitButton() { return $('~login_button'); }
async login(email: string, password: string) {
await this.emailField.setValue(email);
await this.passwordField.setValue(password);
await this.submitButton.click();
}
}
export default new LoginPage();
~ перед строкой — это accessibilityId-локатор. Работает и на iOS (accessibilityIdentifier), и на Android (contentDescription). Предпочитаем его над XPath — стабильнее при изменениях в иерархии View.
XPath используем только когда нет других вариантов: //android.widget.TextView[contains(@text,'Добавить')]. Но длинные XPath-цепочки — первопричина нестабильных тестов.
Ожидания вместо sleep
driver.pause(3000) — антипаттерн. Заменяем на явные ожидания:
await $('~submit_btn').waitForDisplayed({ timeout: 10000 });
await $('~success_screen').waitForExist({ timeout: 15000 });
waitForDisplayed ждёт появления элемента в видимой области. waitForExist — просто существования в DOM. Для элементов, которые появляются после анимации — waitForDisplayed c { timeout: 5000, interval: 500 }.
Интеграция с CI
Appium в CI требует запущенного эмулятора или подключённого устройства. Типичная схема для GitHub Actions:
- name: Start Android Emulator
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
script: |
appium &
sleep 5
npx wdio run wdio.conf.ts
Для iOS в CI нужен macOS-раннер. Используем actions/runner на macOS с Xcode pre-installed. Симулятор создаём через xcrun simctl create.
Альтернатива — облачные фермы устройств: Firebase Test Lab, BrowserStack App Automate, Sauce Labs. Там эмулятор/устройство предоставляет платформа, Appium-сервер тоже.
Типичные проблемы
StaleElementReferenceError — элемент нашли, но до клика UI перестроился. Оборачиваем в retry-логику: await browser.waitUntil(async () => { ... }).
Keyboard covers element — на iOS экранная клавиатура перекрывает поле. Перед вводом текста — await driver.hideKeyboard() или скролл к элементу: await element.scrollIntoView().
App не отвечает на команды после background — 'appium:forceAppLaunch': true в capabilities или явный driver.activateApp(bundleId).
Разные локаторы на iOS и Android — даже с accessibilityId иногда нужны платформо-специфичные локаторы. Решаем через условие:
const selector = driver.isIOS ? '~ios_id' : '~android_id';
Что входит в работу
- Настройка Appium 2 сервера и драйверов для iOS и Android
- Написание тестов (WebdriverIO/TypeScript или Python)
- Page Object паттерн для всех ключевых экранов
- Интеграция в CI (GitHub Actions / GitLab CI)
- Настройка запуска на облачной ферме по запросу
- Отчёт Allure или HTML с скриншотами по каждому шагу
Сроки
5 дней — базовая конфигурация + покрытие 3–5 ключевых флоу. Полное E2E-покрытие большого приложения (15–20 сценариев) — 2–3 недели. Стоимость рассчитывается индивидуально после анализа приложения и инфраструктуры.







