Slide 1

Slide 1 text

Espresso Beyond the basics

Slide 2

Slide 2 text

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


Slide 3

Slide 3 text

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


Slide 4

Slide 4 text

Structure

Slide 5

Slide 5 text

Instrumentation

Slide 6

Slide 6 text

Instrumentation MonitorningInstrumentation

Slide 7

Slide 7 text

Instrumentation MonitorningInstrumentation AndroidJunitRunner

Slide 8

Slide 8 text

Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle

Slide 9

Slide 9 text

Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle

Slide 10

Slide 10 text

Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle

Slide 11

Slide 11 text

Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle

Slide 12

Slide 12 text

Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage coverageFile suiteAssignment debug listener package notPackage timeout_msec testFile disableAnalytics appListener idle

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

BaseLayerComponent

Slide 16

Slide 16 text

BaseLayerModule UiControllerModule BaseLayerComponent

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

BaseLayerComponent

Slide 22

Slide 22 text

FailureHandlerHolder BaseLayerComponent FailureHandler ActivityRootLister IdlingResourceRegistry ViewInteractionComponent

Slide 23

Slide 23 text

FailureHandlerHolder BaseLayerComponent FailureHandler ActivityRootLister ViewInteractionComponent ViewInteractionModule IdlingResourceRegistry

Slide 24

Slide 24 text

FailureHandlerHolder BaseLayerComponent FailureHandler ActivityRootLister ViewInteractionComponent ViewInteractionModule Matcher Matcher ViewFinder View IdlingResourceRegistry

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Synchronization

Slide 27

Slide 27 text


 perform(click());
 
 MainActivityTest


Slide 28

Slide 28 text


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


Slide 29

Slide 29 text


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


Slide 30

Slide 30 text


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


Slide 31

Slide 31 text

InjectKeyEvent loopMainThreadUntilIdle

Slide 32

Slide 32 text

InjectKeyEvent loopMainThreadUntilIdle loopUntil Asynctask Idling

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

@Override
 public void loopMainThreadUntilIdle() {
 do {
 EnumSet condChecks = EnumSet.noneOf(IdleCondition.class);
 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 (!asyncTaskMonitor.isIdleNow() || !compatTaskMonitor.isIdleNow()()
 || !idlingResourceRegistry.allResourcesAreIdle());
 } UiControllerImpl.java

Slide 35

Slide 35 text

@Override
 public void loopMainThreadUntilIdle() {
 do {
 EnumSet condChecks = EnumSet.noneOf(IdleCondition.class);
 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 (!asyncTaskMonitor.isIdleNow() || !compatIdle()
 || !idlingResourceRegistry.allResourcesAreIdle());
 } UiControllerImpl.java

Slide 36

Slide 36 text

@Override
 public void loopMainThreadUntilIdle() {
 do {
 EnumSet condChecks = EnumSet.noneOf(IdleCondition.class);
 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 (!asyncTaskMonitor.isIdleNow() || !compatIdle()
 || !idlingResourceRegistry.allResourcesAreIdle());
 } UiControllerImpl.java

Slide 37

Slide 37 text

AsyncTaskPoolMonitor

Slide 38

Slide 38 text

AsyncTaskPoolMonitor AsyncTask CompatAsyncTask

Slide 39

Slide 39 text

private final AtomicReference monitor = new AtomicReference(null); private final ThreadPoolExecutor pool;
 private final AtomicInteger activeBarrierChecks = new AtomicInteger(0);
 AsyncTaskPoolMonitor.java

Slide 40

Slide 40 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;
 }
 } private final AtomicReference monitor = new AtomicReference(null); private final ThreadPoolExecutor pool;
 private final AtomicInteger activeBarrierChecks = new AtomicInteger(0);
 AsyncTaskPoolMonitor.java

Slide 41

Slide 41 text

IdlingResourceRegistry.java

Slide 42

Slide 42 text

public boolean registerResources(final List extends IdlingResource> resourceList) { } public boolean unregisterResources(final List extends IdlingResource> resourceList) {
 }
 IdlingResourceRegistry.java

Slide 43

Slide 43 text

boolean allResourcesAreIdle() {
 for (int i = idleState.nextSetBit(0); i >= 0 && i < resources.size();
 i = idleState.nextSetBit(i + 1)) {
 idleState.set(i, resources.get(i).isIdleNow());
 }
 return idleState.cardinality() == resources.size();
 } public boolean registerResources(final List extends IdlingResource> resourceList) { } public boolean unregisterResources(final List extends IdlingResource> resourceList) {
 }
 IdlingResourceRegistry.java

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

abstract class WrappingExecutorService implements ExecutorService {
 private final ExecutorService delegate;
 
 protected GuavaWrappingExecutorService(ExecutorService delegate) {
 this.delegate = checkNotNull(delegate);
 }
 
 protected abstract Callable wrapTask(Callable callable);
 
 protected Runnable wrapTask(Runnable command) {
 final Callable wrapped = wrapTask(Executors.callable(command, null));
 return new Runnable() {
 @Override
 public void run() {
 try {
 wrapped.call();
 } catch (Exception e) {
 }
 }
 };
 }
 
 @Override
 public final void execute(Runnable command) {
 delegate.execute(wrapTask(command));
 }
 
 @Override
 public final Future submit(Callable task) {
 return delegate.submit(wrapTask(checkNotNull(task)));
 }
 
 @Override
 public final List> invokeAll(Collection extends Callable> tasks)
 throws InterruptedException {
 return delegate.invokeAll(wrapTasks(tasks));
 }
 }
 Guava

Slide 46

Slide 46 text

public class IdlingResourceExecutorService extends GuavaWrappingExecutorService {
 
 private final CountingIdlingResource mIdlingResource;
 
 public IdlingResourceExecutorService(ExecutorService delegate,
 CountingIdlingResource idlingResource) {
 super(delegate);
 mIdlingResource = idlingResource;
 }
 
 @Override
 protected Callable wrapTask(Callable callable) {
 return new WrappedCallable<>(callable);
 }
 
 private final class WrappedCallable implements Callable {
 private final Callable delegate;
 
 
 public WrappedCallable(Callable delegate) {
 this.delegate = delegate;
 }
 
 @Override
 public T call() throws Exception {
 mIdlingResource.increment();
 T call;
 try {
 call = delegate.call();
 } finally {
 mIdlingResource.decrement();
 }
 return call;
 }
 }
 }

Slide 47

Slide 47 text

public class IdlingResourceExecutorService extends GuavaWrappingExecutorService {
 
 private final CountingIdlingResource mIdlingResource;
 
 public IdlingResourceExecutorService(ExecutorService delegate,
 CountingIdlingResource idlingResource) {
 super(delegate);
 mIdlingResource = idlingResource;
 }
 
 @Override
 protected Callable wrapTask(Callable callable) {
 return new WrappedCallable<>(callable);
 }
 
 private final class WrappedCallable implements Callable {
 private final Callable delegate;
 
 
 public WrappedCallable(Callable delegate) {
 this.delegate = delegate;
 }
 
 @Override
 public T call() throws Exception {
 mIdlingResource.increment();
 T call;
 try {
 call = delegate.call();
 } finally {
 mIdlingResource.decrement();
 }
 return call;
 }
 }
 }

Slide 48

Slide 48 text

public class IdlingResourceExecutorService extends GuavaWrappingExecutorService {
 
 private final CountingIdlingResource mIdlingResource;
 
 public IdlingResourceExecutorService(ExecutorService delegate,
 CountingIdlingResource idlingResource) {
 super(delegate);
 mIdlingResource = idlingResource;
 }
 
 @Override
 protected Callable wrapTask(Callable callable) {
 return new WrappedCallable<>(callable);
 }
 
 private final class WrappedCallable implements Callable {
 private final Callable delegate;
 
 
 public WrappedCallable(Callable delegate) {
 this.delegate = delegate;
 }
 
 @Override
 public T call() throws Exception {
 mIdlingResource.increment();
 T call;
 try {
 call = delegate.call();
 } finally {
 mIdlingResource.decrement();
 }
 return call;
 }
 }
 }

Slide 49

Slide 49 text

RxJava

Slide 50

Slide 50 text

RxJava 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 51

Slide 51 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();
 }
 }
 });
 } RxJava

Slide 52

Slide 52 text

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


Slide 53

Slide 53 text

OkHttp3IdlingResource

Slide 54

Slide 54 text

@Before
 public void setUp() {
 OkHttpClient client = new OkHttpClient();
 IdlingResource resource = OkHttp3IdlingResource.create("OkHttp", client);
 Espresso.registerIdlingResources(resource);
 } OkHttp3IdlingResource

Slide 55

Slide 55 text

@Before
 public void setUp() {
 OkHttpClient client = new OkHttpClient();
 IdlingResource resource = OkHttp3IdlingResource.create("OkHttp", client);
 Espresso.registerIdlingResources(resource);
 } private OkHttp3IdlingResource(String name, Dispatcher dispatcher) {
 this.name = name;
 this.dispatcher = dispatcher;
 dispatcher.setIdleCallback(new Runnable() {
 public void run() {
 ResourceCallback callback = OkHttp3IdlingResource.this.callback;
 if(callback != null) {
 callback.onTransitionToIdle();
 }
 
 }
 });
 }
 
 public boolean isIdleNow() {
 return this.dispatcher.runningCallsCount() == 0;
 }
 
 OkHttp3IdlingResource

Slide 56

Slide 56 text

Rules

Slide 57

Slide 57 text

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

Slide 58

Slide 58 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();
 }
 }; ActivityTestRule

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

@Rule public IntentTestRule activityTestRule = new IntentTestRule<>(Main) public IntentsTestRule(Class activityClass) {
 super(activityClass);
 }
 public IntentsTestRule(Class activityClass, boolean initialTouchMode) {
 super(activityClass, initialTouchMode);
 }
 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();
 }
 IntentTestRule

Slide 61

Slide 61 text

public class CustomActivityTestRule extends ActivityTestRule CustomActivityTestRule

Slide 62

Slide 62 text

public class CustomActivityTestRule extends ActivityTestRule {
 
 public CustomActivityTestRule(Class activityClass) {
 super(activityClass);
 }
 
 public CustomActivityTestRule(Class activityClass, boolean initialTouchMode) {
 super(activityClass, initialTouchMode);
 }
 
 
 @Override
 protected void afterActivityLaunched() {
 Intents.init();
 super.afterActivityLaunched();
 }
 
 @Override
 protected void afterActivityFinished() {
 super.afterActivityFinished();
 Intents.release();
 }
 CustomActivityTestRule

Slide 63

Slide 63 text

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

Slide 64

Slide 64 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;
 } } TestRule

Slide 65

Slide 65 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) {
 mActivity = launchActivity(getActivityIntent());
 }
 mBase.evaluate();
 } finally {
 finishActivity();
 }
 }
 } TestRule

Slide 66

Slide 66 text

public class TraceTestRule implements TestRule {
 
 private Trace trace;
 
 public TraceTestRule(Context context) {
 this.trace = new Trace(context);
 }
 
 @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());
 TestRule

Slide 67

Slide 67 text

public abstract class TestWatcher implements TestRule TestWatcher

Slide 68

Slide 68 text

return new Statement() {
 @Override
 public void evaluate() throws Throwable {
 List errors = new ArrayList();
 
 startingQuietly(description, errors);
 try {
 base.evaluate();
 succeededQuietly(description, errors);
 } catch (@SuppressWarnings("deprecation") 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 TestWatcher

Slide 69

Slide 69 text

public class FailedTest extends TestWatcher {
 
 TestWatcher

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

public class FailedTest extends TestWatcher {
 
 public FailedTest() {
 uiAutomation = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
 }
 
 
 @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);
 }
 TestWatcher

Slide 72

Slide 72 text

public class RuleChain implements TestRule{
 RuleChain

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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


Slide 76

Slide 76 text

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


Slide 77

Slide 77 text

public CustomActivityTestRule activityRule = new CustomActivityTestRule<>(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 78

Slide 78 text

public class Device {
 public Device() {
 }
 
 private static boolean deviceEquals(String device) {
 return Build.DEVICE.equals(device);
 }
 
 public static class Genymotion implements Condition {
 public Genymotion() {
 }
 
 public boolean isSatisfied() {
 return Device.deviceEquals("vbox86p");
 }
 }
 }
 AndroidTestRules

Slide 79

Slide 79 text

Permissions

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

@Before
 public void grantPhonePermission() {
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 getInstrumentation().getUiAutomation().executeShellCommand(
 "pm grant " + getTargetContext().getPackageName()
 + " android.permission.CALL_PHONE");
 }
 }

Slide 82

Slide 82 text

afterEvaluate {
 tasks.each { task ->
 if (task.name.startsWith('connectedAndroidTest')) {
 task.dependsOn grantAnimationPermissions
 }
 }
 }


Slide 83

Slide 83 text

afterEvaluate {
 tasks.each { task ->
 if (task.name.startsWith('connectedAndroidTest')) {
 task.dependsOn grantAnimationPermissions
 }
 }
 }
 devices=$($adb devices | grep -v 'List of devices' | cut -f1 | grep '.')
 
 for device in $devices; do
 echo "Setting permissions to device" $device "for package" $package
 $adb -s $device shell pm grant $package android.permission.xxxx done

Slide 84

Slide 84 text

No content

Slide 85

Slide 85 text

ATSL Spoon Fork TestButler Burst RxPresso AndroidTestRules

Slide 86

Slide 86 text

Anton Batishchev Anton Malinskiy Chiu-ki Chan Erik Hellman Friedger Müffke Iordanis Giannakakis Jake Wharton Jose Alcérreca Michael Bailey Paul Blundell Stephan Linzner Valera Zakharov Xavi Rigau ATSL Spoon Fork TestButler Burst RxPresso AndroidTestRules

Slide 87

Slide 87 text

Thanks

Slide 88

Slide 88 text

+IñakiVillar @inyaki_mwc Thanks