Skip to content

Commit

Permalink
Merge pull request wix#1150 from wix/idleWaitBeforeReady
Browse files Browse the repository at this point in the history
Idle wait before ready
  • Loading branch information
d4vidi authored Feb 11, 2019
2 parents 6a1d700 + d01b904 commit 46de13d
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.wix.detox

import android.content.Context

interface DetoxActionHandler {
fun handle(params: String, messageId: Long)
}

class ReadyActionHandler(
private val wsClient: WebSocketClient,
private val testEngineFacade: TestEngineFacade)
: DetoxActionHandler {

override fun handle(params: String, messageId: Long) {
testEngineFacade.awaitIdle()
wsClient.sendAction("ready", emptyMap<Any, Any>(), messageId)
}
}

class ReactNativeReloadActionHandler(
private val rnContext: Context,
private val wsClient: WebSocketClient,
private val testEngineFacade: TestEngineFacade)
: DetoxActionHandler {

override fun handle(params: String, messageId: Long) {
testEngineFacade.syncIdle()
testEngineFacade.reloadReactNative(rnContext)
wsClient.sendAction("ready", emptyMap<Any, Any>(), messageId)
}
}
54 changes: 37 additions & 17 deletions detox/android/detox/src/main/java/com/wix/detox/DetoxManager.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.wix.detox;

import android.app.Application;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
Expand All @@ -11,7 +10,6 @@
import android.support.test.espresso.IdlingResource;
import android.util.Log;

import com.wix.detox.espresso.UiAutomatorHelper;
import com.wix.detox.systeminfo.Environment;
import com.wix.invoke.MethodInvocation;

Expand All @@ -25,6 +23,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;


/**
Expand All @@ -37,12 +36,16 @@ class DetoxManager implements WebSocketClient.ActionHandler {

private final static String DETOX_SERVER_ARG_KEY = "detoxServer";
private final static String DETOX_SESSION_ID_ARG_KEY = "detoxSessionId";

private String detoxServerUrl;
private String detoxSessionId;

private WebSocketClient wsClient;
private Handler handler;

private Map<String, DetoxActionHandler> actionHandlers = new HashMap<>();
private ReadyActionHandler readyActionHandler = null;

private Context reactNativeHostHolder;

DetoxManager(@NonNull Context context) {
Expand All @@ -68,12 +71,9 @@ class DetoxManager implements WebSocketClient.ActionHandler {

void start() {
if (detoxServerUrl != null && detoxSessionId != null) {
if (ReactNativeSupport.isReactNativeApp()) {
ReactNativeCompat.waitForReactNativeLoad(reactNativeHostHolder);
}

wsClient = new WebSocketClient(this);
wsClient.connectToServer(detoxServerUrl, detoxSessionId);
initReactNativeIfNeeded();
initWSClient();
initActionHandlers();
}
}

Expand Down Expand Up @@ -102,6 +102,14 @@ public void onAction(final String type, final String params, final long messageI
handler.post(new Runnable() {
@Override
public void run() {

final DetoxActionHandler handler = actionHandlers.get(type);
if (handler != null) {
handler.handle(params, messageId);
return;
}

// TODO migrate these to external actions
switch (type) {
case "invoke":
try {
Expand All @@ -127,9 +135,6 @@ public void run() {
wsClient.sendAction("testFailed", m, messageId);
}
break;
case "isReady":
wsClient.sendAction("ready", Collections.emptyMap(), messageId);
break;
case "cleanup":
ReactNativeSupport.currentReactContext = null;
try {
Expand All @@ -144,11 +149,6 @@ public void run() {
}
wsClient.sendAction("cleanupDone", Collections.emptyMap(), messageId);
break;
case "reactNativeReload":
UiAutomatorHelper.espressoSync();
ReactNativeSupport.reloadApp(reactNativeHostHolder);
wsClient.sendAction("ready", Collections.emptyMap(), messageId);
break;
case "currentStatus":
// Ugly, deeply nested, because have to follow
// EarlGrey/Detox iOS here.
Expand Down Expand Up @@ -213,14 +213,34 @@ public void run() {

@Override
public void onConnect() {
wsClient.sendAction("ready", Collections.emptyMap(), -1000L);
readyActionHandler.handle("", -1000L);
}

@Override
public void onClosed() {
stop();
}

private void initReactNativeIfNeeded() {
if (ReactNativeSupport.isReactNativeApp()) {
ReactNativeCompat.waitForReactNativeLoad(reactNativeHostHolder);
}
}

private void initWSClient() {
wsClient = new WebSocketClient(this);
wsClient.connectToServer(detoxServerUrl, detoxSessionId);
}

private void initActionHandlers() {
final TestEngineFacade testEngineFacade = new TestEngineFacade();

readyActionHandler = new ReadyActionHandler(wsClient, testEngineFacade);
actionHandlers.clear();
actionHandlers.put("isReady", readyActionHandler);
actionHandlers.put("reactNativeReload", new ReactNativeReloadActionHandler(reactNativeHostHolder, wsClient, testEngineFacade));
}

private static final class SyncRunnable implements Runnable {
private final Runnable mTarget;
private boolean mComplete;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.wix.detox

import android.content.Context
import android.support.test.espresso.Espresso
import com.wix.detox.espresso.UiAutomatorHelper

class TestEngineFacade {
fun awaitIdle() = Espresso.onIdle()
fun syncIdle() = UiAutomatorHelper.espressoSync() // TODO Check whether this can be replaced with #awaitIdle()
fun reloadReactNative(rnContext: Context) = ReactNativeSupport.reloadApp(rnContext)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.wix.detox

import com.facebook.react.bridge.ReactContext
import com.nhaarman.mockito_kotlin.*
import com.wix.detox.UTHelpers.yieldToOtherThreads
import org.junit.Before
import org.junit.Test
import java.util.*
import java.util.concurrent.Executors

open class DetoxActionHandlerTestBase {
val params = ""
val messageId = 666L

lateinit var rnContext: ReactContext
lateinit var wsClientMock: WebSocketClient
lateinit var testEngineFacade: TestEngineFacade

@Before open fun setUp() {
rnContext = mock()
wsClientMock = mock()

testEngineFacade = mock()
whenever(testEngineFacade.awaitIdle()).then {
synchronized(this) {}
}
whenever(testEngineFacade.syncIdle()).then {
synchronized(this) {}
}
}
}

class ReadyActionHandlerTest : DetoxActionHandlerTestBase() {
@Test fun `should reply with a 'ready' ACK if ready`() {
uut().handle(params, messageId)
verify(wsClientMock).sendAction(eq("ready"), eq(Collections.emptyMap<Any, Any>()), eq(messageId))
}

@Test fun `should block waiting for idle before ACK-ing`() {
val executor = Executors.newSingleThreadExecutor()

synchronized(this) {
executor.submit {
uut().handle(params, messageId)
}
yieldToOtherThreads(executor)
verify(testEngineFacade).awaitIdle()
verify(wsClientMock, never()).sendAction(any(), any(), any())
}
yieldToOtherThreads(executor)
verify(wsClientMock, times(1)).sendAction(any(), any(), any())
}

private fun uut() = ReadyActionHandler(wsClientMock, testEngineFacade)
}

class ReactNativeReloadActionHandlerTest : DetoxActionHandlerTestBase() {
@Test fun `should reload the app`() {
uut().handle(params, messageId)
verify(testEngineFacade).reloadReactNative(rnContext)
}

@Test fun `should reply with a 'ready' ACK when ready`() {
uut().handle(params, messageId)
verify(wsClientMock).sendAction(eq("ready"), eq(Collections.emptyMap<Any, Any>()), eq(messageId))
}

@Test fun `should sync before ACK-ing`() {
val executor = Executors.newSingleThreadExecutor()

synchronized(this) {
executor.submit {
uut().handle(params, messageId)
}
yieldToOtherThreads(executor)
verify(testEngineFacade).syncIdle()
verify(testEngineFacade, never()).reloadReactNative(any())
verify(wsClientMock, never()).sendAction(any(), any(), any())
}
yieldToOtherThreads(executor)
verify(testEngineFacade, times(1)).reloadReactNative(any())
verify(wsClientMock, times(1)).sendAction(any(), any(), any())
}

private fun uut() = ReactNativeReloadActionHandler(rnContext, wsClientMock, testEngineFacade)
}
8 changes: 8 additions & 0 deletions detox/android/detox/src/test/java/com/wix/detox/UTHelpers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.wix.detox

import java.util.concurrent.ExecutorService
import java.util.concurrent.TimeUnit

object UTHelpers {
fun yieldToOtherThreads(executor: ExecutorService) = executor.awaitTermination(100L, TimeUnit.MILLISECONDS)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import android.view.Choreographer
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.modules.core.Timing
import com.nhaarman.mockito_kotlin.*
import com.wix.detox.UTHelpers.yieldToOtherThreads
import org.assertj.core.api.Assertions.assertThat
import org.joor.Reflect
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

const val BUSY_INTERVAL_MS = 1500
const val MEANINGFUL_TIMER_INTERVAL = BUSY_INTERVAL_MS
Expand Down Expand Up @@ -228,10 +228,10 @@ class ReactNativeTimersIdlingResourceTest {
executor.submit {
isIdle = uut().isIdleNow
}
executor.awaitTermination(100L, TimeUnit.MILLISECONDS)
yieldToOtherThreads(executor)
assertThat(isIdle).isNull()
}
executor.awaitTermination(100L, TimeUnit.MILLISECONDS)
yieldToOtherThreads(executor)
assertThat(isIdle).isNotNull()
}

Expand Down

0 comments on commit 46de13d

Please sign in to comment.