$npx -y skills add LambdaTest/agent-skills --skill detox-skillGenerates Detox E2E tests for React Native apps in JavaScript. Gray-box testing framework with automatic synchronization. Supports local simulators/emulators and TestMu AI cloud. Use when user mentions "Detox", "React Native test", "element(by.id())", "device.launchApp". Triggers
| 1 | # Detox Automation Skill |
| 2 | |
| 3 | You are a senior React Native QA engineer specializing in Detox. |
| 4 | |
| 5 | ## Core Patterns |
| 6 | |
| 7 | ### Basic Test |
| 8 | |
| 9 | ```javascript |
| 10 | describe('Login', () => { |
| 11 | beforeAll(async () => { |
| 12 | await device.launchApp(); |
| 13 | }); |
| 14 | |
| 15 | beforeEach(async () => { |
| 16 | await device.reloadReactNative(); |
| 17 | }); |
| 18 | |
| 19 | it('should login with valid credentials', async () => { |
| 20 | await element(by.id('emailInput')).typeText('user@test.com'); |
| 21 | await element(by.id('passwordInput')).typeText('password123'); |
| 22 | await element(by.id('loginButton')).tap(); |
| 23 | await expect(element(by.id('dashboardTitle'))).toBeVisible(); |
| 24 | await expect(element(by.text('Welcome'))).toBeVisible(); |
| 25 | }); |
| 26 | |
| 27 | it('should show error for invalid credentials', async () => { |
| 28 | await element(by.id('emailInput')).typeText('wrong@test.com'); |
| 29 | await element(by.id('passwordInput')).typeText('wrong'); |
| 30 | await element(by.id('loginButton')).tap(); |
| 31 | await expect(element(by.id('errorMessage'))).toBeVisible(); |
| 32 | }); |
| 33 | }); |
| 34 | ``` |
| 35 | |
| 36 | ### Matchers (Finding Elements) |
| 37 | |
| 38 | ```javascript |
| 39 | element(by.id('uniqueId')) // testID prop (best) |
| 40 | element(by.text('Login')) // Text content |
| 41 | element(by.label('Submit')) // Accessibility label |
| 42 | element(by.type('RCTTextInput')) // Native type |
| 43 | element(by.traits(['button'])) // iOS traits |
| 44 | |
| 45 | // Combined |
| 46 | element(by.id('list').withDescendant(by.text('Item 1'))) |
| 47 | element(by.id('item').withAncestor(by.id('list'))) |
| 48 | |
| 49 | // At index (multiple matches) |
| 50 | element(by.text('Delete')).atIndex(0) |
| 51 | ``` |
| 52 | |
| 53 | ### Actions |
| 54 | |
| 55 | ```javascript |
| 56 | await element(by.id('btn')).tap(); |
| 57 | await element(by.id('btn')).longPress(); |
| 58 | await element(by.id('btn')).multiTap(3); |
| 59 | await element(by.id('input')).typeText('hello'); |
| 60 | await element(by.id('input')).replaceText('new text'); |
| 61 | await element(by.id('input')).clearText(); |
| 62 | await element(by.id('scrollView')).scroll(200, 'down'); |
| 63 | await element(by.id('scrollView')).scrollTo('bottom'); |
| 64 | await element(by.id('item')).swipe('left', 'fast'); |
| 65 | await element(by.id('input')).tapReturnKey(); |
| 66 | ``` |
| 67 | |
| 68 | ### Expectations |
| 69 | |
| 70 | ```javascript |
| 71 | await expect(element(by.id('title'))).toBeVisible(); |
| 72 | await expect(element(by.id('title'))).not.toBeVisible(); |
| 73 | await expect(element(by.id('title'))).toExist(); |
| 74 | await expect(element(by.id('title'))).toHaveText('Welcome'); |
| 75 | await expect(element(by.id('toggle'))).toHaveToggleValue(true); |
| 76 | await expect(element(by.id('input'))).toHaveValue('hello'); |
| 77 | ``` |
| 78 | |
| 79 | ### Device Control |
| 80 | |
| 81 | ```javascript |
| 82 | await device.launchApp({ newInstance: true }); |
| 83 | await device.reloadReactNative(); |
| 84 | await device.sendToHome(); |
| 85 | await device.terminateApp(); |
| 86 | await device.installApp(); |
| 87 | await device.shake(); // Shake gesture |
| 88 | await device.setLocation(37.7749, -122.4194); // GPS |
| 89 | await device.setURLBlacklist(['.*cdn.example.*']); // Block URLs |
| 90 | ``` |
| 91 | |
| 92 | ### Anti-Patterns |
| 93 | |
| 94 | | Bad | Good | Why | |
| 95 | |-----|------|-----| |
| 96 | | `waitFor().withTimeout()` everywhere | Trust Detox auto-sync | Detox waits automatically | |
| 97 | | No `testID` on components | Add `testID` prop | Stable selectors | |
| 98 | | `device.launchApp()` in every test | `device.reloadReactNative()` | Faster | |
| 99 | | Manual delays | Detox synchronization | Built-in waiting | |
| 100 | |
| 101 | ### .detoxrc.js Configuration |
| 102 | |
| 103 | ```javascript |
| 104 | module.exports = { |
| 105 | testRunner: { args: { config: 'e2e/jest.config.js' } }, |
| 106 | apps: { |
| 107 | 'ios.debug': { |
| 108 | type: 'ios.app', |
| 109 | binaryPath: 'ios/build/MyApp.app', |
| 110 | build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build', |
| 111 | }, |
| 112 | 'android.debug': { |
| 113 | type: 'android.apk', |
| 114 | binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', |
| 115 | build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug', |
| 116 | reversePorts: [8081], |
| 117 | }, |
| 118 | }, |
| 119 | devices: { |
| 120 | simulator: { type: 'ios.simulator', device: { type: 'iPhone 16' } }, |
| 121 | emulator: { type: 'android.emulator', device: { avdName: 'Pixel_7_API_34' } }, |
| 122 | }, |
| 123 | configurations: { |
| 124 | 'ios.sim.debug': { device: 'simulator', app: 'ios.debug' }, |
| 125 | 'android.emu.debug': { device: 'emulator', app: 'android.debug' }, |
| 126 | }, |
| 127 | }; |
| 128 | ``` |
| 129 | |
| 130 | ## Cloud Execution on TestMu AI |
| 131 | |
| 132 | Detox generates native test runners (Espresso for Android, XCUITest for iOS). Run on TestMu AI cloud by uploading the built artifacts and triggering a framework build. |
| 133 | |
| 134 | ### Android (Espresso) Cloud Run |
| 135 | |
| 136 | ```bash |
| 137 | # 1. Build the app and test APKs |
| 138 | detox build --confi |