/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.react.testing;
import javax.annotation.Nullable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import android.graphics.Bitmap;
import android.test.ActivityInstrumentationTestCase2;
import android.view.View;
import android.view.ViewGroup;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
/**
* Base class for instrumentation tests that runs React based react application in UI mode
*/
public abstract class ReactAppInstrumentationTestCase extends
ActivityInstrumentationTestCase2<ReactAppTestActivity> implements IdleWaiter {
public ReactAppInstrumentationTestCase() {
super(ReactAppTestActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
final ReactAppTestActivity activity = getActivity();
try {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
activity.loadApp(
getReactApplicationKeyUnderTest(),
createReactInstanceSpecForTest(),
getEnableDevSupport());
}
});
} catch (Throwable t) {
throw new Exception("Unable to load react app", t);
}
waitForBridgeAndUIIdle();
assertTrue("Layout never occurred!", activity.waitForLayout(5000));
waitForBridgeAndUIIdle();
}
@Override
protected void tearDown() throws Exception {
ReactAppTestActivity activity = getActivity();
super.tearDown();
activity.waitForDestroy(5000);
}
public ViewGroup getRootView() {
return (ViewGroup) getActivity().getRootView();
}
/**
* This method isn't safe since it doesn't factor in layout-only view removal. Use
* {@link #getViewByTestId(String)} instead.
*/
@Deprecated
public <T extends View> T getViewAtPath(int... path) {
return ReactTestHelper.getViewAtPath((ViewGroup) getRootView().getParent(), path);
}
public <T extends View> T getViewByTestId(String testID) {
return (T) ReactTestHelper
.getViewWithReactTestId((ViewGroup) getRootView().getParent(), testID);
}
public SingleTouchGestureGenerator createGestureGenerator() {
return new SingleTouchGestureGenerator(getRootView(), this);
}
public void waitForBridgeAndUIIdle() {
getActivity().waitForBridgeAndUIIdle();
}
public void waitForBridgeAndUIIdle(long timeoutMs) {
getActivity().waitForBridgeAndUIIdle(timeoutMs);
}
protected Bitmap getScreenshot() {
// Wait for the UI to settle. If the UI is doing animations, this may be unsafe!
getInstrumentation().waitForIdleSync();
final CountDownLatch latch = new CountDownLatch(1);
final BitmapHolder bitmapHolder = new BitmapHolder();
final Runnable getScreenshotRunnable = new Runnable() {
private static final int MAX_TRIES = 1000;
// This is the constant used in the support library for APIs that don't have Choreographer
private static final int FRAME_DELAY_MS = 10;
private int mNumRuns = 0;
@Override
public void run() {
mNumRuns++;
ReactAppTestActivity activity = getActivity();
if (!activity.isScreenshotReady()) {
if (mNumRuns > MAX_TRIES) {
throw new RuntimeException(
"Waited " + MAX_TRIES + " frames to get screenshot but it's still not ready!");
}
activity.postDelayed(this, FRAME_DELAY_MS);
return;
}
bitmapHolder.bitmap = getActivity().getCurrentScreenshot();
latch.countDown();
}
};
getActivity().runOnUiThread(getScreenshotRunnable);
try {
if (!latch.await(5000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Timed out waiting for screenshot runnable to run!");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return Assertions.assertNotNull(bitmapHolder.bitmap);
}
/**
* Implement this method to provide application key to be launched. List of available
* application is located in TestBundle.js file
*/
protected abstract String getReactApplicationKeyUnderTest();
protected boolean getEnableDevSupport() {
return false;
}
/**
* Override this method to provide extra native modules to be loaded before the app starts
*/
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
return new ReactInstanceSpecForTest();
}
protected ReactContext getReactContext() {
return getActivity().getReactContext();
}
/**
* Helper class to pass the bitmap between execution scopes in {@link #getScreenshot()}.
*/
private static class BitmapHolder {
public @Nullable volatile Bitmap bitmap;
}
}