Slide 1

Slide 1 text

Beyond the basics Espresso Beyond the basics @inyaki_mwc

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

onView(withId(R.id.button_take_photo))
 .perform(click())
 .check(matches(isDisplayed()));


Slide 4

Slide 4 text

onView(withId(R.id.button_take_photo))
 .perform(click())
 .check(matches(isDisplayed()));


Slide 5

Slide 5 text

Structure

Slide 6

Slide 6 text

public static ViewInteraction onView(final Matcher viewMatcher) {
 return BASE.plus(new ViewInteractionModule(viewMatcher)).viewInteraction();
 }

Slide 7

Slide 7 text

BaseLayerComponent

Slide 8

Slide 8 text

BaseLayerModule UiControllerModule BaseLayerComponent

Slide 9

Slide 9 text

BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule Recycler BaseLayerComponent UiController

Slide 10

Slide 10 text

BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule Recycler BaseLayerComponent UiController

Slide 11

Slide 11 text

ActivityLifeCycleMonitor

Slide 12

Slide 12 text

ActivityLifeCycleMonitor PRE_ON_CREATE, CREATED, STARTED, RESUMED, PAUSED, STOPPED, RESTARTED, DESTROYED

Slide 13

Slide 13 text

ActivityLifeCycleMonitor MonitoringInstrumentation

Slide 14

Slide 14 text

ActivityLifeCycleMonitor AndroidJUnitRunner MonitoringInstrumentation ExposedInstrumentationApi Instrumentation MonitoringInstrumentation

Slide 15

Slide 15 text

ActivityLifeCycleMonitor MonitoringInstrumentation mLifecycleMonitor.signalLifecycleChange(Stage.DESTROYED, activity);

Slide 16

Slide 16 text

BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule Recycler BaseLayerComponent UiController

Slide 17

Slide 17 text

ActivityRootLister

Slide 18

Slide 18 text

ActivityRootLister WindowManagerGlobal

Slide 19

Slide 19 text

ActivityRootLister WindowManagerGlobal mViews mParams new Root.Builder() .withDecorView(views.get(i)) .withWindowLayoutParams(params.get(i)) .build());

Slide 20

Slide 20 text

BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule Recycler BaseLayerComponent UiController

Slide 21

Slide 21 text

AsyncTaskPoolMonitor

Slide 22

Slide 22 text

@CompatAsyncTask @SdkAsyncTask AsyncTaskPoolMonitor

Slide 23

Slide 23 text

boolean isIdleNow() { if (!pool.getQueue().isEmpty()) { return false; } else { int activeCount = pool.getActiveCount(); if (0 != activeCount) { if (monitor.get() == null) { activeCount = activeCount - activeBarrierChecks.get(); } } return 0 == activeCount; } } AsyncTaskPoolMonitor

Slide 24

Slide 24 text

private final ThreadPoolExecutor pool; AsyncTaskPoolMonitor

Slide 25

Slide 25 text

private final ThreadPoolExecutor pool; MODERN_ASYNC_TASK_CLASS_NAME = “android.support.v4.content.ModernAsyncTask” MODERN_ASYNC_TASK_FIELD_NAME = "THREAD_POOL_EXECUTOR"; AsyncTaskPoolMonitor ThreadPoolExtractor

Slide 26

Slide 26 text

BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule Recycler BaseLayerComponent UiController

Slide 27

Slide 27 text

EventInjector

Slide 28

Slide 28 text

EventInjector EventInjectionStrategy

Slide 29

Slide 29 text

EventInjector EventInjectionStrategy InputManager injectInputEventMethod.invoke(instanceInputManagerObject,motionEvent,motionEventMode);

Slide 30

Slide 30 text

BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule Recycler BaseLayerComponent UiController

Slide 31

Slide 31 text

UiController

Slide 32

Slide 32 text

UiController boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException; boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException; boolean injectString(String str) throws InjectEventSecurityException;

Slide 33

Slide 33 text

UiController boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException; boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException; boolean injectString(String str) throws InjectEventSecurityException; EventInjector AsyncTaskMonitor IdlingResourceRegistry

Slide 34

Slide 34 text

UiController boolean injectMotionEvent(MotionEvent event) throws InjectEventSecurityException; boolean injectKeyEvent(KeyEvent event) throws InjectEventSecurityException; boolean injectString(String str) throws InjectEventSecurityException; EventInjector AsyncTaskMonitor IdlingResourceRegistry boolean allResourcesAreIdle()

Slide 35

Slide 35 text

ViewInteractionComponent

Slide 36

Slide 36 text

ViewInteractionModule Matcher ViewFinder Matcher View ViewInteractionComponent

Slide 37

Slide 37 text

ViewInteractionModule Matcher ViewFinder Matcher View ViewInteractionComponent throw new AmbiguousViewMatcherException throw new NoMatchingViewException

Slide 38

Slide 38 text

ViewInteractionModule Matcher ViewFinder Matcher View ViewInteractionComponent public static ViewInteraction onView(final Matcher viewMatcher) { return BASE.plus(new ViewInteractionModule(viewMatcher)).viewInteraction(); }

Slide 39

Slide 39 text

ViewInteraction

Slide 40

Slide 40 text

ViewInteraction public ViewInteraction perform(final ViewAction... viewActions) public ViewInteraction check(final ViewAssertion viewAssert)

Slide 41

Slide 41 text

Synchronization

Slide 42

Slide 42 text


 perform(click());
 
 MainActivityTest


Slide 43

Slide 43 text


 perform(click());
 new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); 
 MainActivityTest
 
 ViewActions


Slide 44

Slide 44 text


 perform(click());
 new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); 
 MotionEvents.sendDown(uiController, coordinates, precision);
 
 MainActivityTest
 
 ViewActions
 
 Tapper


Slide 45

Slide 45 text


 perform(click());
 new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); 
 MotionEvents.sendDown(uiController, coordinates, precision);
 
 uiController.injectMotionEvent(motionEvent);
 
 MainActivityTest
 
 ViewActions
 
 Tapper
 
 MotionEvents


Slide 46

Slide 46 text

InjectKeyEvent

Slide 47

Slide 47 text

InjectKeyEvent loopMainThreadUntilIdle loopUntil Asynctask Idling

Slide 48

Slide 48 text

InjectKeyEvent loopMainThreadUntilIdle loopUntil Asynctask Idling keyEventExecutor submit(event) loopUntil KeyInject

Slide 49

Slide 49 text

@Override
 public void loopMainThreadUntilIdle() {
 do {
 if (!asyncTaskMonitor.isIdleNow()) {
 asyncTaskMonitor.notifyWhenIdle(…);
 condChecks.add(IdleCondition.ASYNC_TASKS_HAVE_IDLED);
 }
 
 if (!compatTaskMonitor.isIdleNow()) {
 compatTaskMonitor.notifyWhenIdle(…);
 condChecks.add(IdleCondition.COMPAT_TASKS_HAVE_IDLED);
 }
 
 if (!idlingResourceRegistry.allResourcesAreIdle()) {
 idlingResourceRegistry.notifyWhenAllResourcesAreIdle(…);
 condChecks.add(IdleCondition.DYNAMIC_TASKS_HAVE_IDLED);
 }
 
 loopUntil(condChecks);
 } while (!sIdleNow()); }

Slide 50

Slide 50 text

@Override
 public void loopMainThreadUntilIdle() {
 do {
 if (!asyncTaskMonitor.isIdleNow()) {
 asyncTaskMonitor.notifyWhenIdle(…);
 condChecks.add(IdleCondition.ASYNC_TASKS_HAVE_IDLED);
 }
 
 if (!compatTaskMonitor.isIdleNow()) {
 compatTaskMonitor.notifyWhenIdle(…);
 condChecks.add(IdleCondition.COMPAT_TASKS_HAVE_IDLED);
 }
 
 if (!idlingResourceRegistry.allResourcesAreIdle()) {
 idlingResourceRegistry.notifyWhenAllResourcesAreIdle(…);
 condChecks.add(IdleCondition.DYNAMIC_TASKS_HAVE_IDLED);
 }
 
 loopUntil(condChecks); } while (!sIdleNow()); }

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

abstract class WrappingES(delegate: ExecutorService) : ExecutorService { val delegate: ExecutorService = checkNotNull(delegate) abstract fun wrapTask(callable: Callable): Callable fun wrapTask(command: Runnable): Runnable { val wrapped = wrapTask(Executors.callable(command, null)) return Runnable { try { wrapped.call() } catch (e: Exception) { throw RuntimeException(e) } } }

Slide 57

Slide 57 text

abstract class WrappingES(delegate: ExecutorService) : ExecutorService { val delegate: ExecutorService = checkNotNull(delegate) abstract fun wrapTask(callable: Callable): Callable fun wrapTask(command: Runnable): Runnable { val wrapped = wrapTask(Executors.callable(command, null)) return Runnable { try { wrapped.call() } catch (e: Exception) { throw RuntimeException(e) } } }

Slide 58

Slide 58 text

abstract class WrappingES(delegate: ExecutorService) : ExecutorService { val delegate: ExecutorService = checkNotNull(delegate) abstract fun wrapTask(callable: Callable): Callable fun wrapTask(command: Runnable): Runnable { val wrapped = wrapTask(Executors.callable(command, null)) return Runnable { try { wrapped.call() } catch (e: Exception) { throw RuntimeException(e) } } }

Slide 59

Slide 59 text

override fun execute(command: Runnable) { delegate.execute(wrapTask(command)) } override fun submit(task: Callable): Future { return delegate.submit(wrapTask(checkNotNull(task))) } override fun submit(task: Runnable): Future<*> { return delegate.submit(wrapTask(task)) } override fun submit(task: Runnable, result: T): Future { return delegate.submit(wrapTask(task), result) }

Slide 60

Slide 60 text

class IdlingResourceExecutorService(delegate: ExecutorService, val mIdlingResource: CountingIdlingResource) : GuavaWrappingExecutorService(delegate) { override fun wrapTask(callable: Callable): Callable { return WrappedCallable(callable) } private inner class WrappedCallable(private val delegate: Callable) : Callable { @Throws(Exception::class) override fun call(): T { mIdlingResource.increment() val call: T try { call = delegate.call() } finally { mIdlingResource.decrement() } return call } } } 
 }

Slide 61

Slide 61 text

class IdlingResourceExecutorService(delegate: ExecutorService, val mIdlingResource: CountingIdlingResource) : GuavaWrappingExecutorService(delegate) { override fun wrapTask(callable: Callable): Callable { return WrappedCallable(callable) } private inner class WrappedCallable(private val delegate: Callable) : Callable { @Throws(Exception::class) override fun call(): T { mIdlingResource.increment() val call: T try { call = delegate.call() } finally { mIdlingResource.decrement() } return call } } } 
 }

Slide 62

Slide 62 text

class IdlingResourceExecutorService(delegate: ExecutorService, val mIdlingResource: CountingIdlingResource) : GuavaWrappingExecutorService(delegate) { override fun wrapTask(callable: Callable): Callable { return WrappedCallable(callable) } inner class WrappedCallable(val delegate: Callable) : Callable { @Throws(Exception::class) override fun call(): T { mIdlingResource.increment() val call: T try { call = delegate.call() } finally { mIdlingResource.decrement() } return call } } } 
 }

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

public class RxSchedulerHook extends RxJavaSchedulersHook {
 
 private Scheduler customScheduler;
 private CountingIdlingResource idling;
 private static RxSchedulerHook sInstance;
 public RxSchedulerHook(CountingIdlingResource countingIdlingResource) {
 FailedTest watcher = new FailedTest();
 idling = countingIdlingResource;
 customScheduler = new Scheduler() {
 @Override public Scheduler.Worker createWorker() {
 return new CustomWorker();
 }
 };
 }


Slide 65

Slide 65 text

@Override public Subscription schedule(final Action0 action, final long delayTime, TimeUnit unit) {
 return super.schedule(new Action0() {
 @Override public void call() {
 
 action.call();
 }
 }, delayTime, unit);
 }
 
 @Override public Subscription schedule(final Action0 action) {
 return super.schedule(new Action0() {
 @Override public void call() {
 idling.increment();
 try {
 action.call();
 } finally {
 idling.decrement();
 }
 }
 });
 }

Slide 66

Slide 66 text

RxJavaPlugins.getInstance().registerSchedulersHook(RxSchedulerHook.get());


Slide 67

Slide 67 text

RxIdler

Slide 68

Slide 68 text

RxIdler https://github.com/square/RxIdler

Slide 69

Slide 69 text

Rules

Slide 70

Slide 70 text

@Rule public ActivityTestRule activityTestRule = new ActivityTestRule<>(Main){
 @Override
 protected void beforeActivityLaunched() {
 super.beforeActivityLaunched();
 }
 
 @Override
 protected void afterActivityLaunched() {
 super.afterActivityLaunched();
 }
 
 @Override
 protected void afterActivityFinished() {
 super.afterActivityFinished();
 }
 };

Slide 71

Slide 71 text

@Rule public ActivityTestRule activityTestRule = new ActivityTestRule<>(Main){
 @Override
 protected void beforeActivityLaunched() {
 super.beforeActivityLaunched();
 }
 
 @Override
 protected void afterActivityLaunched() {
 super.afterActivityLaunched();
 }
 
 @Override
 protected void afterActivityFinished() {
 super.afterActivityFinished();
 }
 };

Slide 72

Slide 72 text

@Rule public IntentTestRule activityTestRule = new IntentTestRule<>(Main)

Slide 73

Slide 73 text

@Rule public IntentTestRule activityTestRule = new IntentTestRule<>(Main) 
 
 public IntentsTestRule(Class activityClass, boolean initialTouchMode,
 boolean launchActivity) {
 super(activityClass, initialTouchMode, launchActivity);
 }
 
 @Override
 protected void afterActivityLaunched() {
 Intents.init();
 super.afterActivityLaunched();
 }
 
 @Override
 protected void afterActivityFinished() {
 super.afterActivityFinished();
 Intents.release();
 }
 
 


Slide 74

Slide 74 text

public interface TestRule {
 Statement apply(Statement base, Description description);
 }

Slide 75

Slide 75 text

public interface TestRule {
 Statement apply(Statement base, Description description);
 } public class UiThreadTestRule implements TestRule {
 private static final String LOG_TAG = "UiThreadTestRule";
 
 @Override
 public Statement apply(final Statement base, Description description) {
 return new UiThreadStatement(base, shouldRunOnUiThread(description));
 }
 
 protected boolean shouldRunOnUiThread(Description description) {
 return description.getAnnotation(UiThreadTest.class) != null;
 } }

Slide 76

Slide 76 text

private class ActivityStatement extends Statement {
 
 private final Statement mBase;
 
 public ActivityStatement(Statement base) {
 mBase = base;
 }
 
 @Override
 public void evaluate() throws Throwable {
 try {
 if (mLaunchActivity) {
 act = launchActivity(getActivityIntent());
 }
 mBase.evaluate();
 } finally {
 finishActivity();
 }
 }
 }

Slide 77

Slide 77 text

public class TraceTestRule implements TestRule {
 
 private Trace trace;
 
 @Override
 public Statement apply(final Statement base, Description description) {
 return new Statement() {
 @Override
 public void evaluate() throws Throwable {
 try {
 trace.start();
 base.evaluate();
 } finally {
 trace.end();
 }
 }
 };
 }
 }

Slide 78

Slide 78 text

public class TraceTestRule implements TestRule {
 
 private Trace trace;
 
 @Override
 public Statement apply(final Statement base, Description description) {
 return new Statement() {
 @Override
 public void evaluate() throws Throwable {
 try {
 trace.start();
 base.evaluate();
 } finally {
 trace.end();
 }
 }
 };
 }
 } @Rule
 public TraceTestRule traceTestRule = new TraceTestRule(getTargetContext());


Slide 79

Slide 79 text

public abstract class TestWatcher implements TestRule

Slide 80

Slide 80 text

return new Statement() {
 @Override
 public void evaluate() throws Throwable {
 List errors = new ArrayList();
 
 startingQuietly(description, errors);
 try {
 base.evaluate();
 succeededQuietly(description, errors);
 } catch (AssumptionViolatedException e) {
 errors.add(e);
 skippedQuietly(e, description, errors);
 } catch (Throwable e) {
 errors.add(e);
 failedQuietly(e, description, errors);
 } finally {
 finishedQuietly(description, errors);
 }
 
 MultipleFailureException.assertEmpty(errors);
 }
 }; public abstract class TestWatcher implements TestRule

Slide 81

Slide 81 text

return new Statement() {
 @Override
 public void evaluate() throws Throwable {
 List errors = new ArrayList();
 
 startingQuietly(description, errors);
 try {
 base.evaluate();
 succeededQuietly(description, errors);
 } catch (AssumptionViolatedException e) {
 errors.add(e);
 skippedQuietly(e, description, errors);
 } catch (Throwable e) {
 errors.add(e);
 failedQuietly(e, description, errors);
 } finally {
 finishedQuietly(description, errors);
 }
 
 MultipleFailureException.assertEmpty(errors);
 }
 }; public abstract class TestWatcher implements TestRule

Slide 82

Slide 82 text

public class FailedTest extends TestWatcher {
 
 public FailedTest() {
 uiAutomation = UiDevice.getInstance();
 }
 
 
 @Override
 protected void failed(Throwable e, Description description) {
 super.failed(e, description);
 String fileNameBase = getFileNameWithoutExtension(description);
 saveScreenshot(fileNameBase);
 saveInfo(fileNameBase);
 }
 
 @Override
 protected void succeeded(Description description) {
 super.succeeded(description);
 }
 
 @Override
 protected void skipped(AssumptionViolatedException e, Description description) {
 super.skipped(e, description);
 }


Slide 83

Slide 83 text

public class FailedTest extends TestWatcher {
 
 public FailedTest() {
 uiAutomation = UiDevice.getInstance();
 }
 
 
 @Override
 protected void failed(Throwable e, Description description) {
 super.failed(e, description);
 String fileNameBase = getFileNameWithoutExtension(description);
 saveScreenshot(fileNameBase);
 saveInfo(fileNameBase);
 }
 
 @Override
 protected void succeeded(Description description) {
 super.succeeded(description);
 }
 
 @Override
 protected void skipped(AssumptionViolatedException e, Description description) {
 super.skipped(e, description);
 }


Slide 84

Slide 84 text

public class FailedTest extends TestWatcher {
 
 public FailedTest() {
 uiAutomation = UiDevice.getInstance();
 }
 
 
 @Override
 protected void failed(Throwable e, Description description) {
 super.failed(e, description);
 String fileNameBase = getFileNameWithoutExtension(description);
 saveScreenshot(fileNameBase);
 saveInfo(fileNameBase);
 }
 
 @Override
 protected void succeeded(Description description) {
 super.succeeded(description);
 }
 
 @Override
 protected void skipped(AssumptionViolatedException e, Description description) {
 super.skipped(e, description);
 }


Slide 85

Slide 85 text

public class RuleChain implements TestRule


Slide 86

Slide 86 text

public class RuleChain implements TestRule
 public RuleChain ruleChain = RuleChain.outerRule(new LogRule("outer rule")
 .around(new LogRule("middle around rule")
 .around(new LogRule("inner around rule”))));

Slide 87

Slide 87 text

public class RuleChain implements TestRule
 public RuleChain ruleChain = RuleChain.outerRule(new LogRule("outer rule")
 .around(new LogRule("middle around rule")
 .around(new LogRule("inner around rule”)))); Starting outer Starting middle Starting inner Finishing inner Finishing middle Finishing outer

Slide 88

Slide 88 text

public NewActivityTestRule activityRule = new NewActivityTestRule<>(Main.class);
 


Slide 89

Slide 89 text

public NewActivityTestRule activityRule = new NewActivityTestRule<>(Main.class);
 
 
 @Rule
 public RuleChain chain = RuleChain.outerRule(new FailedTest() .around(activityRule);


Slide 90

Slide 90 text

public NewActivityTestRule activityRule = new NewActivityTestRule<>(Main.class);
 
 
 @Rule
 public RuleChain chain = RuleChain.outerRule(new FailedTest() .around(activityRule);
 
 @Rule
 public RuleChain chain = RuleChain.outerRule(new FailedTest()) .around(new TraceTestRule()) .around(activityRule);


Slide 91

Slide 91 text

No content

Slide 92

Slide 92 text

What’s next?

Slide 93

Slide 93 text

Android Test Orchestrator

Slide 94

Slide 94 text

Test Apk APK AndroidJunitRunner Android Test Orchestrator

Slide 95

Slide 95 text

Test Apk APK Orchestrator AndroidJunitRunner Android Test Orchestrator

Slide 96

Slide 96 text

Test Apk APK Orchestrator AndroidJunitRunner Test Test Test Android Test Orchestrator

Slide 97

Slide 97 text

Multiprocess Espresso

Slide 98

Slide 98 text

Espresso AndroidJunitRunner Multiprocess Espresso

Slide 99

Slide 99 text

Espresso AndroidJunitRunner Multiprocess Espresso Espresso AndroidJunitRunner

Slide 100

Slide 100 text

Espresso AndroidJunitRunner Multiprocess Espresso Espresso AndroidJunitRunner

Slide 101

Slide 101 text

dependencies { androidTestUtil 'com.linkedin.testbutler:test-butler-app:1.3.0@apk' }

Slide 102

Slide 102 text

No content

Slide 103

Slide 103 text

No content

Slide 104

Slide 104 text

@inyaki_mwc Thanks