Разработка E2E-тестов для мобильного приложения (Detox)
Detox — это gray-box тестирование. В отличие от Appium, который работает снаружи приложения как чёрный ящик, Detox встраивает тест-сервер непосредственно в процесс приложения. Это даёт ему одно критичное преимущество: синхронизацию с event loop React Native. Detox знает, когда JS-поток занят, когда идут анимации, когда выполняются сетевые запросы — и ждёт автоматически. Отсюда значительно меньше sleep() и нестабильных тестов по сравнению с Appium на React Native.
Конфигурация: первые грабли
Настройка Detox занимает больше времени, чем кажется. Особенно на iOS.
package.json (Detox 20.x):
{
"detox": {
"testRunner": {
"args": { "$0": "jest", "config": "e2e/jest.config.js" },
"jest": { "setupTimeout": 120000 }
},
"apps": {
"ios.debug": {
"type": "ios.app",
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/MyApp.app",
"build": "xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build"
},
"android.debug": {
"type": "android.apk",
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug"
}
},
"devices": {
"simulator": {
"type": "ios.simulator",
"device": { "type": "iPhone 15", "os": "iOS 17.4" }
},
"emulator": {
"type": "android.emulator",
"device": { "avdName": "Pixel_7_API_34" }
}
}
}
}
Частая проблема при первой настройке: бинарь собирается с неверными флагами. Для Detox на Android APK должен быть собран вместе с assembleAndroidTest — без этого синхронизация не работает. На iOS — только симулятор билд (-sdk iphonesimulator), реальные устройства требуют отдельного профилирования и signing.
Написание тестов
Detox использует матчеры, похожие на Espresso/XCTest, но с единым API:
describe('Auth flow', () => {
beforeAll(async () => {
await device.launchApp({ newInstance: true });
});
beforeEach(async () => {
await device.reloadReactNative(); // быстрый ресет без перезапуска
});
it('should login successfully', async () => {
await element(by.id('email_input')).typeText('[email protected]');
await element(by.id('password_input')).typeText('password123');
await element(by.id('login_button')).tap();
await expect(element(by.id('home_screen'))).toBeVisible();
});
});
by.id() ищет по testID (React Native prop). Это основной способ — не XPath, не текст. Договорённость: все интерактивные элементы в компонентах получают testID с соглашением об именовании (screen_component_action).
device.reloadReactNative() быстрее, чем device.launchApp({ newInstance: true }). Используем reloadReactNative() между тестами одного describe-блока, newInstance: true — когда нужен чистый native state (например, после тестов с push-уведомлениями).
Работа с анимациями и асинхронностью
Detox автоматически ждёт завершения:
- JS-операций (Promise, setTimeout, setInterval)
- Запросов через
fetchиXMLHttpRequest(если URL не в whitelist) - Нативных анимаций через React Native Animated
Но бесконечные анимации (loop: true) блокируют ожидание. Решение — disable animations в test build через флаг:
await device.launchApp({
launchArgs: { detoxDisableHierarchyDump: 'YES' },
permissions: { notifications: 'YES', camera: 'YES' },
});
Или отключаем через React Native: в __DEV__ режиме или через кастомный флаг IS_TESTING передаём AnimationTesting.setEnabled(false).
Параллельное тестирование
Detox поддерживает параллельный запуск через шардирование jest:
detox test --configuration android.debug --workers 3
При --workers 3 Detox запустит 3 эмулятора параллельно и распределит тест-файлы между ними. AVD должны быть созданы заранее (Pixel_7_API_34_1, Pixel_7_API_34_2, Pixel_7_API_34_3) или Detox создаст их сам при наличии прав.
На iOS то же самое — 3 симулятора одновременно. Требует macOS-машины с достаточным RAM (16 GB минимум для 3 симуляторов).
Интеграция в CI
GitHub Actions с macOS-раннером для iOS:
jobs:
e2e-ios:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npx pod-install
- run: npx detox build --configuration ios.debug
- run: npx detox test --configuration ios.debug --headless
Для Android — ubuntu-latest с reactivecircus/android-emulator-runner@v2:
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
target: google_apis
arch: x86_64
profile: pixel_7
script: npx detox test --configuration android.debug --headless
--headless — обязательно в CI. Без него на Ubuntu эмулятор ищет дисплей и падает.
Сроки
5 дней — конфигурация Detox + покрытие основных пользовательских флоу (авторизация, навигация, ключевые экраны). При наличии сложных нативных модулей (Push, Biometrics, Camera) добавляется 1–2 дня. Стоимость рассчитывается индивидуально.







