Skip to content

Commit

Permalink
Initial native test suite for Android, this should help debugging iss…
Browse files Browse the repository at this point in the history
…ues in #608
  • Loading branch information
rotemmiz committed Mar 25, 2018
1 parent 792a782 commit e11760a
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 37 deletions.
6 changes: 3 additions & 3 deletions detox/android/detox/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ dependencies {
minReactNative46Implementation 'com.squareup.okhttp3:okhttp:3.6.0'
minReactNative46Implementation 'com.squareup.okio:okio:1.13.0'

implementation('com.android.support.test.espresso:espresso-core:3.0.0', {
api('com.android.support.test.espresso:espresso-core:3.0.1', {
exclude group: 'com.google.code.findbugs'
})

implementation 'org.apache.commons:commons-lang3:3.4'
implementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
api 'org.apache.commons:commons-lang3:3.4'
api 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'

testImplementation 'org.json:json:20140107'
testImplementation 'junit:junit:4.12'
Expand Down
18 changes: 13 additions & 5 deletions detox/android/detox/src/main/java/com/wix/detox/Detox.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private Detox() {}
* @param activityTestRule the activityTestRule
*/
public static void runTests(ActivityTestRule activityTestRule) {
Object appContext = InstrumentationRegistry.getTargetContext().getApplicationContext();
Context appContext = InstrumentationRegistry.getTargetContext().getApplicationContext();
runTests(activityTestRule, appContext);
}

Expand All @@ -106,7 +106,7 @@ public static void runTests(ActivityTestRule activityTestRule) {
* @param activityTestRule the activityTestRule
* @param reactActivityDelegate an object that has a {@code getReactNativeHost()} method
*/
public static void runTests(ActivityTestRule activityTestRule, @NonNull final Object reactActivityDelegate) {
public static void runTests(ActivityTestRule activityTestRule, @NonNull final Context reactActivityDelegate) {
sActivityTestRule = activityTestRule;
Intent intent = null;
Bundle arguments = InstrumentationRegistry.getArguments();
Expand Down Expand Up @@ -162,7 +162,7 @@ public static Intent intentWithUrl(String url) {

// TODO: Can't get to launch the app back to previous instance using only intents from inside instrumentation (not sure why).
// this is a (hopefully) temp solution. Should use intents instead.
public static void launchMainActivity() throws RemoteException, UiObjectNotFoundException {
public static void launchMainActivity() {
Context targetContext = InstrumentationRegistry.getTargetContext();

// Intent intent = targetContext.getPackageManager().getLaunchIntentForPackage(targetContext.getPackageName());
Expand All @@ -173,10 +173,18 @@ public static void launchMainActivity() throws RemoteException, UiObjectNotFound
// launchActivity(intent);

UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.pressRecentApps();
try {
device.pressRecentApps();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
UiSelector selector = new UiSelector();
String appName = targetContext.getApplicationInfo().loadLabel(targetContext.getPackageManager()).toString();
UiObject recentApp = device.findObject(selector.descriptionContains(appName));
recentApp.click();
try {
recentApp.click();
} catch (UiObjectNotFoundException e) {
throw new RuntimeException(e);
}
}
}
21 changes: 11 additions & 10 deletions detox/android/detox/src/main/java/com/wix/detox/DetoxManager.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wix.detox;

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
Expand Down Expand Up @@ -41,9 +42,9 @@ class DetoxManager implements WebSocketClient.ActionHandler {
private WebSocketClient wsClient;
private Handler handler;

private Object reactNativeHostHolder = null;
private Context reactNativeHostHolder = null;

DetoxManager(@NonNull Object reactNativeHostHolder) {
DetoxManager(@NonNull Context reactNativeHostHolder) {
this.reactNativeHostHolder = reactNativeHostHolder;
handler = new Handler();

Expand All @@ -54,14 +55,14 @@ class DetoxManager implements WebSocketClient.ActionHandler {
}
detoxSessionId = arguments.getString(DETOX_SESSION_ID_ARG_KEY);

if (detoxServerUrl == null || detoxSessionId == null) {
Log.i(LOG_TAG, "Missing arguments : detoxServer and/or detoxSession. Detox quits.");
stop();
return;
if (detoxServerUrl == null) {
throw new RuntimeException("Missing argument: session.server");
} else if (detoxSessionId == null) {
throw new RuntimeException("Missing argument: session.sessionId");
}

Log.i(LOG_TAG, "DetoxServerUrl : " + detoxServerUrl);
Log.i(LOG_TAG, "DetoxSessionId : " + detoxSessionId);
Log.i(LOG_TAG, "DetoxServerUrl: " + detoxServerUrl);
Log.i(LOG_TAG, "DetoxSessionId: " + detoxSessionId);
}

void start() {
Expand Down Expand Up @@ -222,7 +223,7 @@ private static final class SyncRunnable implements Runnable {
private final Runnable mTarget;
private boolean mComplete;

public SyncRunnable(Runnable target) {
SyncRunnable(Runnable target) {
mTarget = target;
}

Expand All @@ -234,7 +235,7 @@ public void run() {
}
}

public void waitForComplete() {
void waitForComplete() {
synchronized (this) {
while (!mComplete) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.IdlingRegistry;
import android.util.Log;

import com.wix.detox.espresso.AnimatedModuleIdlingResource;
import com.wix.detox.espresso.LooperIdlingResource;
import com.wix.detox.espresso.ReactBridgeIdlingResource;
import com.wix.detox.espresso.RNExperimentalNetworkIR;
import com.wix.detox.espresso.ReactBridgeIdlingResource;
import com.wix.detox.espresso.ReactNativeTimersIdlingResource;
import com.wix.detox.espresso.ReactNativeUIModuleIdlingResource;

Expand Down Expand Up @@ -70,7 +70,7 @@ private ReactNativeSupport() {
// static class
}

static boolean isReactNativeApp() {
public static boolean isReactNativeApp() {
Class<?> found = null;
try {
found = Class.forName("com.facebook.react.ReactApplication");
Expand Down Expand Up @@ -112,7 +112,7 @@ public static Object getInstanceManager(@NonNull Object reactNativeHostHolder) {
*
* @param reactNativeHostHolder the object that has a getReactNativeHost() method
*/
static void reloadApp(@NonNull Object reactNativeHostHolder) {
public static void reloadApp(@NonNull Object reactNativeHostHolder) {
if (!isReactNativeApp()) {
return;
}
Expand Down Expand Up @@ -150,7 +150,7 @@ public void run() {
* </p>
* @param reactNativeHostHolder the object that has a getReactNativeHost() method
*/
static void waitForReactNativeLoad(@NonNull Object reactNativeHostHolder) {
public static void waitForReactNativeLoad(@NonNull Object reactNativeHostHolder) {

if (!isReactNativeApp()) {
return;
Expand Down Expand Up @@ -282,7 +282,7 @@ private static void setupEspressoIdlingResources(
rnUIModuleIdlingResource = new ReactNativeUIModuleIdlingResource(reactContext);
animIdlingResource = new AnimatedModuleIdlingResource(reactContext);

Espresso.registerIdlingResources(
IdlingRegistry.getInstance().register(
rnTimerIdlingResource,
rnBridgeIdlingResource,
rnUIModuleIdlingResource,
Expand Down Expand Up @@ -318,7 +318,7 @@ private static void setupRNQueueInterrogator(
LooperIdlingResource looperIdlingResource = new LooperIdlingResource((Looper)looper, false);

looperIdlingResources.add(looperIdlingResource);
Espresso.registerIdlingResources(looperIdlingResource);
IdlingRegistry.getInstance().register(looperIdlingResource);
excludedLoopers.add((Looper)looper);
}
}
Expand Down Expand Up @@ -355,7 +355,7 @@ private static void removeEspressoIdlingResources(
&& rnTimerIdlingResource != null
&& rnUIModuleIdlingResource != null
&& animIdlingResource != null) {
Espresso.unregisterIdlingResources(
IdlingRegistry.getInstance().unregister(
rnTimerIdlingResource,
rnBridgeIdlingResource,
rnUIModuleIdlingResource,
Expand Down Expand Up @@ -386,7 +386,7 @@ private static void removeEspressoIdlingResources(
private static void removeReactNativeQueueInterrogators() {
for (LooperIdlingResource res : looperIdlingResources) {
res.stop();
Espresso.unregisterIdlingResources(res);
IdlingRegistry.getInstance().unregister(res);
}
looperIdlingResources.clear();
}
Expand Down Expand Up @@ -434,7 +434,7 @@ private static void setupNetworkIdlingResource() {
.field(FIELD_OKHTTP_CLIENT)
.get();
networkIR = new RNExperimentalNetworkIR(client.dispatcher());
Espresso.registerIdlingResources(networkIR);
IdlingRegistry.getInstance().register(networkIR);
} catch (ReflectException e) {
Log.e(LOG_TAG, "Can't set up Networking Module listener", e.getCause());
}
Expand All @@ -443,7 +443,7 @@ private static void setupNetworkIdlingResource() {
private static void removeNetworkIdlingResource() {
if (networkIR != null) {
networkIR.stop();
Espresso.unregisterIdlingResources(networkIR);
IdlingRegistry.getInstance().unregister(networkIR);
networkIR = null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public boolean isIdleNow() {
if (callback != null) {
callback.onTransitionToIdle();
}
// Log.i(LOG_TAG, "UIManagerModule is idle.");
Log.i(LOG_TAG, "UIManagerModule is idle.");
return true;
}

Expand All @@ -117,8 +117,10 @@ public boolean isIdleNow() {
}

if (callback != null) {
Log.i(LOG_TAG, "UIManagerModule is idle. (callback)");
callback.onTransitionToIdle();
}
Log.i(LOG_TAG, "UIManagerModule is idle.");
return true;
}

Expand Down
6 changes: 5 additions & 1 deletion detox/test/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
apply plugin: "com.android.application"

project.ext.react = [
nodeExecutableAndArgs : ["/usr/local/bin/node"]
];

apply from: "../../node_modules/react-native/react.gradle"

android {
Expand Down Expand Up @@ -62,7 +66,7 @@ android {
dependencies {
implementation "com.android.support:appcompat-v7:27.0.2"

fromSourceImplementation(project(path: ":ReactAndroid"))
// fromSourceImplementation(project(path: ":ReactAndroid"))
fromBinImplementation "com.facebook.react:react-native:+"

androidTestImplementation(project(path: ":detox"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
package com.example;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.action.ViewActions;
import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import com.wix.detox.Detox;
import com.wix.detox.ReactNativeSupport;
import com.wix.detox.espresso.DetoxAssertion;
import com.wix.detox.espresso.DetoxMatcher;
import com.wix.detox.espresso.EspressoDetox;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;



/**
* Created by simonracz on 28/05/2017.
*/
Expand All @@ -21,8 +32,72 @@ public class DetoxTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);

// @Test
// public void runDetoxTests() throws InterruptedException {
// Detox.runTests(mActivityRule);
// }

@Test
public void Sanity() {
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Sanity")), ViewActions.click());
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Say Hello")), ViewActions.click());
DetoxAssertion.assertMatcher(Espresso.onView(DetoxMatcher.matcherForText("Hello!!!")), DetoxMatcher.matcherForSufficientlyVisible());
}

@Test
public void Actions_should_tap_on_an_element() {
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Actions")), ViewActions.click());
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Tap Me")), ViewActions.click());
DetoxAssertion.assertMatcher(Espresso.onView(DetoxMatcher.matcherForText("Tap Working!!!")), DetoxMatcher.matcherForSufficientlyVisible());
}

@Test
public void Stress_should_handle_tap_during_busy_bridge() {
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Stress")), ViewActions.click());
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Bridge TwoWay Stress")), ViewActions.click());
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Next")), ViewActions.click());
DetoxAssertion.assertMatcher(Espresso.onView(DetoxMatcher.matcherForText("BridgeTwoWay")), DetoxMatcher.matcherForSufficientlyVisible());
}

@Test
public void Stress_should_handle_consecutive_taps() {
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Stress")), ViewActions.click());
for (int i=1; i<=20; i++) {
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Consecutive Stress " + i)), ViewActions.click());
}

DetoxAssertion.assertMatcher(Espresso.onView(DetoxMatcher.matcherForText("Consecutive Stress 21")), DetoxMatcher.matcherForSufficientlyVisible());
}

@Test
public void runDetoxTests() throws InterruptedException {
Detox.runTests(mActivityRule);
public void Animations_should_find_element_driver_JS() {
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForText("Animations")), ViewActions.click());
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherWithAncestor(DetoxMatcher.matcherForText("JS"), DetoxMatcher.matcherForTestId("UniqueId_AnimationsScreen_useNativeDriver"))), ViewActions.click());
EspressoDetox.perform(Espresso.onView(DetoxMatcher.matcherForTestId("UniqueId_AnimationsScreen_startButton")), ViewActions.click());
DetoxAssertion.assertMatcher(Espresso.onView(DetoxMatcher.matcherForTestId("UniqueId_AnimationsScreen_afterAnimationText")), DetoxMatcher.matcherForSufficientlyVisible());
}

@Test
public void Animations_should_find_element_driver_native() {

}

@Before
public void setup() {
Context appContext = InstrumentationRegistry.getTargetContext().getApplicationContext();
mActivityRule.launchActivity(null);
Detox.launchMainActivity();

// if (ReactNativeSupport.isReactNativeApp()) {
// ReactNativeSupport.waitForReactNativeLoad(appContext);
// }

ReactNativeSupport.reloadApp(appContext);
}
}


//{"type":"invoke","params":{"target":{"type":"Class","value":"com.wix.detox.espresso.EspressoDetox"},"method":"perform","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"android.support.test.espresso.Espresso"},"method":"onView","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"com.wix.detox.espresso.DetoxMatcher"},"method":"matcherForText","args":["Animations"]}}]}},{"type":"Invocation","value":{"target":{"type":"Class","value":"android.support.test.espresso.action.ViewActions"},"method":"click","args":[]}}]},"messageId":1}
//{"type":"invoke","params":{"target":{"type":"Class","value":"com.wix.detox.espresso.EspressoDetox"},"method":"perform","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"android.support.test.espresso.Espresso"},"method":"onView","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"com.wix.detox.espresso.DetoxMatcher"},"method":"matcherWithAncestor","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"com.wix.detox.espresso.DetoxMatcher"},"method":"matcherForText","args":["JS"]}},{"type":"Invocation","value":{"target":{"type":"Class","value":"com.wix.detox.espresso.DetoxMatcher"},"method":"matcherForTestId","args":["UniqueId_AnimationsScreen_useNativeDriver"]}}]}}]}},{"type":"Invocation","value":{"target":{"type":"Class","value":"android.support.test.espresso.action.ViewActions"},"method":"click","args":[]}}]},"messageId":2}
//{"type":"invoke","params":{"target":{"type":"Class","value":"com.wix.detox.espresso.EspressoDetox"},"method":"perform","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"android.support.test.espresso.Espresso"},"method":"onView","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"com.wix.detox.espresso.DetoxMatcher"},"method":"matcherForTestId","args":["UniqueId_AnimationsScreen_startButton"]}}]}},{"type":"Invocation","value":{"target":{"type":"Class","value":"android.support.test.espresso.action.ViewActions"},"method":"click","args":[]}}]},"messageId":3}
//{"type":"invoke","params":{"target":{"type":"Class","value":"com.wix.detox.espresso.DetoxAssertion"},"method":"assertMatcher","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"android.support.test.espresso.Espresso"},"method":"onView","args":[{"type":"Invocation","value":{"target":{"type":"Class","value":"com.wix.detox.espresso.DetoxMatcher"},"method":"matcherForTestId","args":["UniqueId_AnimationsScreen_afterAnimationText"]}}]}},{"type":"Invocation","value":{"target":{"type":"Class","value":"com.wix.detox.espresso.DetoxMatcher"},"method":"matcherForSufficientlyVisible","args":[]}}]},"messageId":4}
4 changes: 2 additions & 2 deletions detox/test/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ include ':app'
include ':detox'
project(':detox').projectDir = new File(rootProject.projectDir, '../../android/detox')

include ':ReactAndroid'
project(':ReactAndroid').projectDir = new File(rootProject.projectDir, '../node_modules/react-native/ReactAndroid')
//include ':ReactAndroid'
//project(':ReactAndroid').projectDir = new File(rootProject.projectDir, '../node_modules/react-native/ReactAndroid')
4 changes: 2 additions & 2 deletions detox/test/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@
}
},
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"binaryPath": "android/app/build/outputs/apk/fromBin/debug/app-fromBin-debug.apk",
"build": "cd android && ./gradlew assembleFromBinDebug assembleFromBinDebugAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"name": "Nexus_5X_API_26"
},
"android.emu.release": {
"binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
"binaryPath": "android/app/build/outputs/apk/fromBin/release/app-fromBin-release.apk",
"build": "cd android && ./gradlew assembleFromBinRelease assembleFromBinReleaseAndroidTest -DtestBuildType=release && cd ..",
"type": "android.emulator",
"name": "Nexus_5X_API_26"
Expand Down

0 comments on commit e11760a

Please sign in to comment.