# E2E Testing with Detox ## Installation ```bash yarn add -D detox npx detox init ``` ## Configuration **detox.config.js:** ```javascript module.exports = { testRunner: 'jest', runnerConfig: 'e2e/config.json', configurations: { 'ios.sim.debug': { device: { type: 'ios.simulator', device: { type: 'iPhone 12', }, }, app: { 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.emu.debug': { device: { type: 'android.emulator', device: { avdName: 'Pixel_4_API_30', }, }, app: { type: 'android.apk', binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug', reversePorts: [8081], }, }, }, }; ``` **e2e/config.json:** ```json { "maxWorkers": 1, "testTimeout": 120000, "retries": 2, "bail": false, "verbose": true, "setupFilesAfterEnv": ["./init.js"] } ``` ## Test Setup ```javascript // e2e/init.js const detox = require('detox'); const config = require('../detox.config.js'); const adapter = require('detox/runners/jest/adapter'); jest.setTimeout(300000); jasmine.getEnv().addReporter(adapter); beforeAll(async () => { await detox.init(config); }); beforeEach(async () => { await adapter.beforeEach(); }); afterAll(async () => { await adapter.afterAll(); await detox.cleanup(); }); ``` ## Login Flow Test ```javascript // e2e/loginFlow.e2e.js describe('Login Flow', () => { beforeEach(async () => { await device.reloadReactNative(); }); it('should show login screen on app launch', async () => { await expect(element(by.id('loginScreen'))).toBeVisible(); await expect(element(by.id('emailInput'))).toBeVisible(); await expect(element(by.id('passwordInput'))).toBeVisible(); await expect(element(by.id('loginButton'))).toBeVisible(); }); it('should show validation errors for empty fields', async () => { await element(by.id('loginButton')).tap(); await expect(element(by.text('Email is required'))).toBeVisible(); await expect(element(by.text('Password is required'))).toBeVisible(); }); it('should login successfully with valid credentials', async () => { await element(by.id('emailInput')).typeText('test@example.com'); await element(by.id('passwordInput')).typeText('password123'); await element(by.id('loginButton')).tap(); // Wait for navigation to home screen await waitFor(element(by.id('homeScreen'))) .toBeVisible() .withTimeout(10000); }); it('should show error for invalid credentials', async () => { await element(by.id('emailInput')).typeText('invalid@example.com'); await element(by.id('passwordInput')).typeText('wrongpassword'); await element(by.id('loginButton')).tap(); await expect(element(by.text('Invalid credentials'))).toBeVisible(); }); }); ``` ## Navigation Test ```javascript // e2e/navigation.e2e.js describe('Navigation', () => { beforeEach(async () => { await device.reloadReactNative(); // Login first await element(by.id('emailInput')).typeText('test@example.com'); await element(by.id('passwordInput')).typeText('password123'); await element(by.id('loginButton')).tap(); await waitFor(element(by.id('homeScreen'))).toBeVisible().withTimeout(10000); }); it('should navigate between tabs', async () => { // Test Home tab await expect(element(by.id('homeScreen'))).toBeVisible(); // Navigate to Profile tab await element(by.id('profileTab')).tap(); await expect(element(by.id('profileScreen'))).toBeVisible(); // Navigate to Settings tab await element(by.id('settingsTab')).tap(); await expect(element(by.id('settingsScreen'))).toBeVisible(); // Navigate back to Home tab await element(by.id('homeTab')).tap(); await expect(element(by.id('homeScreen'))).toBeVisible(); }); it('should navigate to detail screen and back', async () => { await element(by.id('firstRequestItem')).tap(); await expect(element(by.id('requestDetailScreen'))).toBeVisible(); await element(by.id('backButton')).tap(); await expect(element(by.id('homeScreen'))).toBeVisible(); }); }); ``` ## Form Testing ```javascript // e2e/createRequest.e2e.js describe('Create Request Flow', () => { beforeEach(async () => { await device.reloadReactNative(); // Login and navigate to create request await element(by.id('emailInput')).typeText('test@example.com'); await element(by.id('passwordInput')).typeText('password123'); await element(by.id('loginButton')).tap(); await waitFor(element(by.id('homeScreen'))).toBeVisible().withTimeout(10000); await element(by.id('createRequestButton')).tap(); }); it('should create a new request successfully', async () => { await element(by.id('titleInput')).typeText('Help for Medical Treatment'); await element(by.id('descriptionInput')).typeText('Need urgent help for medical treatment'); await element(by.id('targetAmountInput')).typeText('50000'); // Select category await element(by.id('categoryDropdown')).tap(); await element(by.text('Health')).tap(); // Add image await element(by.id('addImageButton')).tap(); await element(by.text('Camera')).tap(); // Submit form await element(by.id('submitButton')).tap(); await waitFor(element(by.text('Request created successfully'))) .toBeVisible() .withTimeout(10000); }); it('should show validation errors for incomplete form', async () => { await element(by.id('submitButton')).tap(); await expect(element(by.text('Title is required'))).toBeVisible(); await expect(element(by.text('Description is required'))).toBeVisible(); await expect(element(by.text('Target amount is required'))).toBeVisible(); }); }); ``` ## Scroll and List Testing ```javascript // e2e/requestList.e2e.js describe('Request List', () => { beforeEach(async () => { await device.reloadReactNative(); // Login first await element(by.id('emailInput')).typeText('test@example.com'); await element(by.id('passwordInput')).typeText('password123'); await element(by.id('loginButton')).tap(); await waitFor(element(by.id('homeScreen'))).toBeVisible().withTimeout(10000); }); it('should load and display request list', async () => { await expect(element(by.id('requestList'))).toBeVisible(); await expect(element(by.id('requestItem-0'))).toBeVisible(); }); it('should scroll through request list', async () => { await element(by.id('requestList')).scroll(300, 'down'); await expect(element(by.id('requestItem-5'))).toBeVisible(); }); it('should pull to refresh', async () => { await element(by.id('requestList')).swipe('down', 'slow', 0.8); await waitFor(element(by.id('refreshIndicator'))) .toBeVisible() .withTimeout(2000); }); it('should filter requests by category', async () => { await element(by.id('filterButton')).tap(); await element(by.text('Health')).tap(); await element(by.id('applyFilterButton')).tap(); await waitFor(element(by.id('requestList'))) .toBeVisible() .withTimeout(5000); }); }); ``` ## Device Interactions ```javascript // e2e/deviceInteractions.e2e.js describe('Device Interactions', () => { beforeEach(async () => { await device.reloadReactNative(); }); it('should handle device rotation', async () => { await device.setOrientation('landscape'); await expect(element(by.id('loginScreen'))).toBeVisible(); await device.setOrientation('portrait'); await expect(element(by.id('loginScreen'))).toBeVisible(); }); it('should handle app backgrounding and foregrounding', async () => { await device.sendToHome(); await device.launchApp({newInstance: false}); await expect(element(by.id('loginScreen'))).toBeVisible(); }); it('should handle deep links', async () => { await device.openURL({url: 'myapp://request/123'}); await waitFor(element(by.id('requestDetailScreen'))) .toBeVisible() .withTimeout(10000); }); }); ``` ## Test Utilities ```javascript // e2e/utils/helpers.js export const loginUser = async (email = 'test@example.com', password = 'password123') => { await element(by.id('emailInput')).typeText(email); await element(by.id('passwordInput')).typeText(password); await element(by.id('loginButton')).tap(); await waitFor(element(by.id('homeScreen'))).toBeVisible().withTimeout(10000); }; export const navigateToScreen = async (screenId) => { await element(by.id(screenId)).tap(); await waitFor(element(by.id(`${screenId}Screen`))).toBeVisible().withTimeout(5000); }; export const fillForm = async (formData) => { for (const [fieldId, value] of Object.entries(formData)) { await element(by.id(fieldId)).typeText(value); } }; export const waitForElementToDisappear = async (elementId, timeout = 5000) => { await waitFor(element(by.id(elementId))).not.toBeVisible().withTimeout(timeout); }; ``` ## Running Tests ```bash # Build and run iOS tests detox build --configuration ios.sim.debug detox test --configuration ios.sim.debug # Build and run Android tests detox build --configuration android.emu.debug detox test --configuration android.emu.debug # Run specific test file detox test --configuration ios.sim.debug e2e/loginFlow.e2e.js # Run tests with verbose output detox test --configuration ios.sim.debug --loglevel verbose ```