Розробка E2E-тестів для мобільних додатків (Detox)
Detox — це gray-box тестування. На відміну від Appium, який працює зовні додатку як чорна скринька, Detox вбудовує тест-сервер безпосередньо у процес додатку. Це дає йому одну критичну перевагу: синхронізацію з React Native event loop. 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 — тільки simulator-білд (-sdk iphonesimulator), реальні пристрої вимагають окремого профілювання та підписання.
Написання тестів
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-білді через флаг:
await device.launchApp({
launchArgs: { detoxDisableHierarchyDump: 'YES' },
permissions: { notifications: 'YES', camera: 'YES' },
});
Або відключаємо через React Native: у __DEV__ режимі або через кастомний флаг IS_TESTING передаємо AnimationTesting.setEnabled(false).
Паралельне тестування
Detox підтримує паралельний запуск через jest sharding:
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 дні. Вартість розраховується індивідуально.







