skip to content

A Detox suite passes on android.emu.debug but the android.emu.release configuration hangs at launch — why, and why test release builds at all?

level: seniorimportance: should knowfreq 30%

answer

  1. where the JS bundle comes from
  2. debug needs Metro, reversePorts 8081
  3. CI guide: test a release build
  4. reflection vs shrinking
  5. proguard-rules-app.pro in release

basics

~20 s

A release build with shrinking on strips React Native classes Detox reaches by reflection, so the app hangs or crashes; adding Detox's proguard-rules-app.pro fixes it. CI still tests release because it is Metro-free, deterministic and closest to what ships.

solid answer

~40 s

A debug configuration loads its JavaScript from Metro at runtime; on Android the default `android.debug` app entry reverses port 8081 for that, which makes it ideal for the local edit-and-rerun loop. A release configuration embeds the bundle, needs no Metro and matches what users get, so the Detox CI guide says to test release builds there. The Android catch is shrinking: release builds often set `minifyEnabled true`, and Detox reaches into React Native through reflection, so stripped or renamed classes make Detox crash or hang at launch while debug passes. The fix is adding `node_modules/detox/android/detox/proguard-rules-app.pro` to the release build type, and building the test APK with `-DtestBuildType=release`.

code

javascript · 15 lines
javascript
module.exports = {
  apps: {
    'android.debug': {
      type: 'android.apk',
      binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
      build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
      reversePorts: [8081], // device localhost:8081 -> Metro on the host
    },
    'android.release': {
      type: 'android.apk',
      binaryPath: 'android/app/build/outputs/apk/release/app-release.apk',
      build: 'cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
    },
  },
};

go deeper

for a junior

Recall that debug builds load JavaScript from Metro while release builds embed it, and that CI should test the release build.

for a middle

Explain reversePorts, the release build string with -DtestBuildType=release, and why the release binary is the more faithful test target.

for a senior

Recognise the debug-passes, release-hangs pattern as shrinking removing reflection targets, and apply Detox's ProGuard rules or a dedicated test variant.

for a principal

Decide whether e2e tests should run against the exact shipping artifact or a test variant, weighing fidelity against binary size and build time.

## Debug and release configurations serve different jobs The `.detoxrc.js` that `detox init` generates already pairs every device with a debug and a release app: `ios.sim.debug` and `ios.sim.release`, `android.emu.debug` and `android.emu.release`, plus the attached-device `android.att.*` pair. The difference between the debug and release pairs is not cosmetic; it changes where the JavaScript comes from and what the native build strips. | | Debug configuration | Release configuration | |---|---|---| | JavaScript bundle | Loaded from Metro on the host at runtime | Embedded in the binary at build time | | Needs Metro running | Yes (Android debug app uses `reversePorts: [8081]`) | No | | Iteration speed | Edit JS, reload, re-run | Rebuild for every JS change | | Code shrinking on Android | Off | On whenever the release build type sets `minifyEnabled true` | | Closest to what ships | No | Yes | ## Why local runs use debug - **Fast loop.** Detox's "developing while writing tests" workflow keeps a debug build connected to Metro, so JS changes arrive over the network and the binary does not change. - **Port reversal.** On Android, the default `android.debug` app entry lists `reversePorts: [8081]`, so `localhost:8081` on the device reaches Metro on the host. - **Longer startup budget.** The `DetoxTest.java` template gives debug builds 180 seconds to load the React Native context and release builds 60, because fetching the bundle from Metro is slower. ## Why CI runs release The Detox CI guide lists testing a release build rather than a debug build as the first difference from local runs. A release binary: 1. Needs no Metro process on the agent, removing a moving part and a port that can be busy. 2. Runs the same bundled, optimised JavaScript and the same native configuration that users get. 3. Is deterministic: the JavaScript cannot change between the build step and the test step. On iOS, a "release" configuration here is still a **simulator** build (`-configuration Release -sdk iphonesimulator`), not a signed device `.ipa`; Detox installs it on a simulator like the debug one. ## The Android release trap: shrinking Detox reaches into React Native's Android internals through the **Java reflection API**. Many teams turn on code shrinking and obfuscation for release builds (`minifyEnabled true` with ProGuard-format rules files), and shrinking removes or renames classes that are only reached by reflection. The symptom is specific and confusing: `android.emu.debug` passes, while `android.emu.release` crashes or **hangs indefinitely** at launch, because Detox cannot find the React Native pieces it needs. The fix from the Detox setup guide is one line in the release build type of `android/app/build.gradle`: ```groovy release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } ``` That file keeps most of React Native's code unminified and unobfuscated in the release APK. The trade-off is a somewhat larger release binary; some teams therefore build a dedicated test variant instead of adding the rules to the shipping build type — a decision about Android build types rather than Detox itself. ## Keeping both pairs usable 1. Build the debug binary once with `detox build -c android.emu.debug`; `--if-missing` skips the build when the binaries already exist. 2. Keep Metro running for debug runs. An app entry can also carry an optional `start` command that `detox test` runs before the test runner, which is a convenient place to launch Metro. 3. On CI, run `detox build -c android.emu.release` and then `detox test -c android.emu.release`, with no Metro process at all. 4. Rebuild the debug binary whenever native code or a native dependency changes; JavaScript edits alone do not need it. ## Other release-configuration checks - **Matching test APK.** The release build string should be `assembleRelease assembleAndroidTest -DtestBuildType=release`, so the instrumentation APK targets the release app. - **Timeouts.** A release app loads faster, but a slow emulator can still exceed the 60-second context-load budget set in `DetoxTest.java`; raising it is a legitimate change for slow agents. A candidate who can say why debug is for the inner loop, why release belongs on CI, and why an Android release run hangs without Detox's ProGuard rules has shown real Detox configuration experience.

  • Is an ios.sim.release configuration a signed App Store build?
    No. Its build string runs `xcodebuild -configuration Release -sdk iphonesimulator`, producing a simulator `.app` with the Release configuration's bundled JavaScript and optimisations. Detox installs it on a simulator like the debug build; a signed device `.ipa` is a separate artifact from the release pipeline.
  • Why does the DetoxTest.java template give debug builds a longer context-load timeout?
    Its `rnContextLoadTimeoutSec` is 180 seconds when `BuildConfig.DEBUG` is true and 60 otherwise. A debug app has to fetch and evaluate the bundle from Metro before the React Native context is ready, which is slower than loading the embedded bundle of a release build.

saying these in an interview costs you the question

  • Release configurations are only needed for signed store builds.
  • A debug Detox run on CI proves the shipped bundle works.
  • The release hang is a Detox synchronization bug, not a build setting.
  • Disabling minification in the shipping build is the only fix.
  • Release Android runs still need Metro reachable on port 8081.