Skip to content

Latest commit

 

History

History
384 lines (261 loc) · 16.4 KB

Introduction.Android.md

File metadata and controls

384 lines (261 loc) · 16.4 KB

Detox for Android

Breaking Changes ⚠️

If you are installing Detox for Android for the first time, you can skip right over to the setup section.

Follow our Migration Guide for instructions on how to upgrade from older versions.

  • In version 11 we switched to using Android Espresso of Android's new androidx.* support libraries. We did this in order to stay up to date with Google's latest features and bug fixes, in the hopes of using them to improve our own Android support (which gets better every day!).

  • In version 10, we've made Kotlin mandatory for integrating Detox into your Android project. In the very least, you must include the Kotlin gradle plugin in your project, as we shall see later on. Nevertheless, this is a breaking change so bear that in mind when upgrading. In any case, worry not of the impact on your app, as - unless you effectively use Kotlin in your own native code, there will be no impact on the final APK, in terms of size and methods count.

  • As of version 7 we require Android gradle plugin 3.0.0 or newer. This is a breaking change that makes it impossible to support previous Android gradle plugin versions.

    https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html

    For older Android gradle plugin support use [email protected] instead (previous setup guide here).

    Note: As a rule of thumb, we consider all old major versions discontinued; We only support the latest Detox major version.

Setup ⚙️

1. Run through the initial Getting Started Guide

2. Add Detox dependency to an Android project

Starting Detox 12.5.0, Detox is shipped as a precompiled .aar. To configure Detox as a compiling dependency, nevertheless -- refer to the Setting Detox up as a compiling dependency section at the bottom.

In your root buildscript (i.e. build.gradle), register both google() and detox as repository lookup points in all projects:

// Note: add the 'allproject' section if it doesn't exist
allprojects {
    repositories {
        // ...
        google()
        maven {
            // All of Detox' artifacts are provided via the npm module
            url "$rootDir/../node_modules/detox/Detox-android"
        }
    }
}

In your app's buildscript (i.e. app/build.gradle) add this in dependencies section:

dependencies {
	  // ...
    androidTestImplementation('com.wix:detox:+') { transitive = true } 
    androidTestImplementation 'junit:junit:4.12'
}

In your app's buildscript (i.e. app/build.gradle) add this to the defaultConfig subsection:

android {
  // ...
  
  defaultConfig {
      // ...
      testBuildType System.getProperty('testBuildType', 'debug')  // This will later be used to control the test apk build type
      testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
  }
}

Please be aware that the minSdkVersion needs to be at least 18.

3. Add Kotlin

If your project does not already support Kotlin, add the Kotlin Gradle-plugin to your classpath in the root build-script (i.e.android/build.gradle):

buildscript {
    // ...
    ext.kotlinVersion = '1.3.0'

    dependencies {
        // ...
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
    }
}

Note: most guides advise of defining a global kotlinVersion constant - as in this example, but that is not mandatory.

Note that Detox has been tested for version 1.1.0 of Kotlin, and higher!

4. Create Android Test class

Add the file android/app/src/androidTest/java/com/[your.package]/DetoxTest.java and fill as in the detox example app for NR. Don't forget to change the package name to your project's.

5. Add Android configuration

Add this part to your package.json:

"detox" : {
    "configurations": {
        "android.emu.debug": {
            "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
            "build":
            "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
            "type": "android.emulator",
            "device": {
              "avdName": "Nexus_5X_API_24"
            }
        },
        "android.emu.release": {
            "binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
            "build": "cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..",
            "type": "android.emulator",
            "device": {
              "avdName": "Nexus_5X_API_26"
            }
        }
    }
}

Pay attention to -DtestBuildType, set either to debug or release according to the main apk type.

Following device types could be used to control Android devices:

android.emulator. Boot stock SDK emulator with provided name, for example Nexus_5X_API_25. After booting connect to it.

android.attached. Connect to already-attached android device. The device should be listed in the output of adb devices command under provided name. Use this type to connect to Genymotion emulator.

5a. Using product flavors

If you are using custom productFlavors the config needs to be applied a bit differently. This example shows how a beta product flavor would look for both debug and release build types:

"detox" : {
    "configurations": {
        "android.emu.beta.debug": {
        "binaryPath": "android/app/build/outputs/apk/beta/debug/app-beta-debug.apk",
        "build": "cd android && ./gradlew assembleBetaDebug assembleBetaDebugAndroidTest -DtestBuildType=debug && cd ..",
        "type": "android.emulator",
        "device": {
          "avdName": "Pixel_API_29"
        }
      },
      "android.emu.beta.release": {
        "binaryPath": "android/app/build/outputs/apk/beta/release/app-beta-release.apk",
        "build": "cd android && ./gradlew assembleBetaRelease assembleBetaReleaseAndroidTest -DtestBuildType=release && cd ..",
        "type": "android.emulator",
        "device": {
          "avdName": "Pixel_API_29"
        }
      }
    }
}

6. Run the tests

Using the android.emu.debug configuration from above, you can invoke it in the standard way.

detox test -c android.emu.debug

7. Fix your Android emulators

While everything may seem on the working side right now, there are many issues with Google's Android emulators causing flakiness and inefficient usage, we recommend addressing. We highly recommend you take the time and apply the practices we've summed up in our Emulators Best Practices guide, in all relevant machines (dev computers, CI agents and scripts) and AVD's.

Proguard (Minification)

In apps running minification using Proguard, in order for Detox to work well on release builds, please enable some Detox proguard-configuration rules by applying the custom configuration file on top of your own. Typically, this is defined using the proguardFiles statement in the minification-enabled build-type in your app/build.gradle:

    buildTypes {
        // 'release' is typically the default proguard-enabled build-type
        release {
            minifyEnabled true

            // Typical pro-guard definitions
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            // Detox-specific additions to pro-guard
            proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
        }
    }

Setting Detox up as a compiling dependency

This is an alternative to the setup process described under the previous section, on adding Detox as a dependency.

In your project's settings.gradle add:

include ':detox'
project(':detox').projectDir = new File(rootProject.projectDir, '../node_modules/detox/android/detox')

In your root buildscript (i.e. build.gradle), register google() as a repository lookup point in all projects:

// Note: add the 'allproject' section if it doesn't exist
allprojects {
    repositories {
        // ...
        google()
    }
}

In your app's buildscript (i.e. app/build.gradle) add this in dependencies section:

dependencies {
  	// ...
    androidTestImplementation(project(path: ":detox"))
    androidTestImplementation 'junit:junit:4.12'
}

In your app's buildscript (i.e. app/build.gradle) add this to the defaultConfig subsection:

android {
  // ...
  
  defaultConfig {
      // ...
      testBuildType System.getProperty('testBuildType', 'debug')  // This will later be used to control the test apk build type
      testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
  }
}

Please be aware that the minSdkVersion needs to be at least 18.

Troubleshooting

Problem: Duplicate files copied in ...

If you get an error like this:

Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'.
> com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: Duplicate files copied in APK META-INF/LICENSE

You need to add this to the android section of your android/app/build.gradle:

packagingOptions {
    exclude 'META-INF/LICENSE'
}

Problem: Kotlin stdlib version conflicts

The problems and resolutions here are different if you're using Detox as a precompiled dependency artifact (i.e. an .aar) - which is the default, or compiling it yourself.

Resolving for a precompiled dependency (.aar)

Of all Kotlin implementation flavours, Detox assumes the most recent one, namely kotlin-stdlib-jdk8. If your Android build fails due to conflicts with implementations coming from other dependencies or even your own app, consider adding an exclusion to either the "other" dependencies or detox itself, for example:

dependencies {
-    androidTestImplementation('com.wix:detox:+') { transitive = true } 
+    androidTestImplementation('com.wix:detox:+') { 
+        transitive = true
+        exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
+    }
}

Detox should work with kotlin-stdlib-jdk7, as well.

A typical error output formed by Gradle in this case is as provided, for example, in #1380:

Could not determine the dependencies of task ':detox:compileDebugAidl'.
> Could not resolve all task dependencies for configuration ':detox:debugCompileClasspath'.
   > Could not resolve org.jetbrains.kotlin:kotlin-stdlib:1.3.0.
     Required by:
         project :detox
      > Cannot find a version of 'org.jetbrains.kotlin:kotlin-stdlib' that satisfies the version constraints:
           Dependency path 'OurApp:detox:unspecified' --> 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --> 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30'
           Dependency path 'OurApp:detox:unspecified' --> 'com.squareup.okio:okio:2.2.2' --> 'org.jetbrains.kotlin:kotlin-stdlib:1.2.60'
           Dependency path 'OurApp:detox:unspecified' --> 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.0' --> 'org.jetbrains.kotlin:kotlin-stdlib:1.3.0'
           Dependency path 'OurApp:detox:unspecified' --> 'com.facebook.react:react-native:0.59.5' --> 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --> 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30'
           Dependency path 'OurApp:detox:unspecified' --> 'com.facebook.react:react-native:0.59.5' --> 'com.squareup.okio:okio:2.2.2' --> 'org.jetbrains.kotlin:kotlin-stdlib:1.2.60'
           Dependency path 'OurApp:detox:unspecified' --> 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.0' --> 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.0' --> 'org.jetbrains.kotlin:kotlin-stdlib:1.3.0'
           Constraint path 'OurApp:detox:unspecified' --> 'org.jetbrains.kotlin:kotlin-stdlib' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0
           Constraint path 'OurApp:detox:unspecified' --> 'org.jetbrains.kotlin:kotlin-stdlib' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0

   > Could not resolve org.jetbrains.kotlin:kotlin-stdlib-common:1.3.0.
     Required by:
         project :detox
      > Cannot find a version of 'org.jetbrains.kotlin:kotlin-stdlib-common' that satisfies the version constraints:
           Dependency path 'OurApp:detox:unspecified' --> 'com.squareup.okhttp3:okhttp:4.0.0-alpha01' --> 'org.jetbrains.kotlin:kotlin-stdlib:1.3.30' --> 'org.jetbrains.kotlin:kotlin-stdlib-common:1.3.30'
           Constraint path 'OurApp:detox:unspecified' --> 'org.jetbrains.kotlin:kotlin-stdlib-common' strictly '1.3.0' because of the following reason: debugRuntimeClasspath uses version 1.3.0

(i.e. the project indirectly depends on different versions of kotlin-stdlib, such as 1.3.0, 1.3.30, 1.2.60)

Resolving for a compiling subproject

Detox requires the Kotlin standard-library as it's own dependency. Due to the many flavours by which Kotlin has been released, multiple dependencies often create a conflict.

For that, Detox allows for the exact specification of the standard library to use using two Gradle globals: detoxKotlinVersion and detoxKotlinStdlib. You can define both in your root build-script file (i.e.android/build.gradle):

buildscript {
    // ...
    ext.detoxKotlinVersion = '1.3.0' // Detox' default is 1.2.0
    ext.detoxKotlinStdlib = 'kotlin-stdlib-jdk7' // Detox' default is kotlin-stdlib-jdk8
}

Problem: The app loads but tests fail to start in SDK >= 28

As reported in issue #1450, sometimes the application under test would properly launch on an emulator/device when running Detox, but the test runner will hang and will not start running the actual tests.

More specifically, when this happens:

  1. Detox and the tests runner launch successfully, alongside the app being run (unless launchApp: false has been passed to detox.init()), but the first test simply hangs forever (as explained).
  2. Eventually, the test runner would time-out.
  3. The last reported Detox-logs before time-out would indicate the device failing to connect to the Detox tester on the host. For example:
detox[12345] DEBUG: [DetoxServer.js/CANNOT_FORWARD] role=testee not connected, cannot fw action (sessionId=11111111-2222-3333-4444-555555555555)

A common reason for this set of symptoms to take place is the lack of a clear-text network traffic policy configuration in app - the kind that would allow both the app and Detox to work properly. These Android developers blog posts (1, 2) elaborate on what should be configured for apps and how, in great length. The pain-point in this context is that in SDK 28, clear-text traffic was set to be denied by Android, by default. In that mode, Detox, which requires such traffic while testing, is blocked.

The solution here is therefore to first properly configure clear-text traffic and for all app flavors (e.g. release / debug) -- if you haven't already. Second, the app needs to be configured to allow for clear-text traffic between the emulator and the running host. Examples:

  1. Applying android:usesCleartextTraffic="true"" in the application tag of the main AndroidManifest.xml (not recommended).
  2. (Untested) Applying a base config denying clear-text (i.e. using cleartextTrafficPermitted=false, or just by omitting it altogether), and enabling it for the special-localhost domain:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">10.0.2.2</domain>
    </domain-config>
</network-security-config>

10.0.2.2 is the IP equivalent to the localhost on the computer hosting the Android emulator, on Google emulators. On Genymotion ones, it is 10.0.3.2.