Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Espresso, Beyond the Basics. DroidCon Vietnam
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Iñaki Villar
April 15, 2017
Technology
130
0
Share
Espresso, Beyond the Basics. DroidCon Vietnam
Presentation of Espresso at Droidon Vietnam 2017
Iñaki Villar
April 15, 2017
More Decks by Iñaki Villar
See All by Iñaki Villar
Scaling Android Builds in Pandemic Times
cdsap
1
170
Building Android Projects with kts
cdsap
2
300
The Build Shrugged
cdsap
1
100
State of Testing in Kotlin
cdsap
0
280
Dexs, R8 and 3.3
cdsap
0
400
Deep Dive Work Manager
cdsap
0
360
Advanced Topics Android
cdsap
0
140
Kotlin: Server-Client
cdsap
0
120
Droidcon Dubai : Kotlin - Server - Client
cdsap
0
90
Other Decks in Technology
See All in Technology
最初の一歩を踏み出せなかった私が、誰かの背中を押したいと思うようになるまで / give someone a push
mii3king
0
160
Azure Static Web Apps の自動ビルドがタイムアウトしやすくなった状況に対応した件/global-azure2026
thara0402
0
390
Standards et agents IA : un tour d’horizon de MCP, A2A, ADK et plus encore
glaforge
0
160
サイボウズ 開発本部採用ピッチ / Cybozu Engineer Recruit
cybozuinsideout
PRO
10
78k
マルチプロダクトの信頼性を効率良く保っていくために
kworkdev
PRO
0
150
自分のハンドルは自分で握れ! ― 自分のケイパビリティを増やし、メンバーのケイパビリティ獲得を支援する ― / Take the wheel yourself
takaking22
1
890
The Journey of Box Building
tagomoris
4
2k
KGDC_13_Amazon Q Developerで挑む! 13事例から見えたAX組織変革の最前線_公開情報
kikugawa
0
120
AI バイブコーティングでキーボード不要?!
samakada
0
530
Digitization部 紹介資料
sansan33
PRO
1
7.3k
Introduction to Sansan, inc / Sansan Global Development Center, Inc.
sansan33
PRO
0
3k
ぼくがかんがえたさいきょうのあうとぷっと
yama3133
0
190
Featured
See All Featured
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.3k
Lessons Learnt from Crawling 1000+ Websites
charlesmeaden
PRO
1
1.2k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
180
Making the Leap to Tech Lead
cromwellryan
135
9.8k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.7k
Designing for humans not robots
tammielis
254
26k
Typedesign – Prime Four
hannesfritz
42
3k
Building an army of robots
kneath
306
46k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
320
Scaling GitHub
holman
464
140k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
220
The World Runs on Bad Software
bkeepers
PRO
72
12k
Transcript
Espresso Beyond the basics
None
onView(withId(R.id.button_take_photo)) .perform(click()) .check(matches(isDisplayed()));
onView(withId(R.id.button_take_photo)) .perform(click()) .check(matches(isDisplayed()));
Structure
Instrumentation
Instrumentation MonitorningInstrumentation
Instrumentation MonitorningInstrumentation AndroidJunitRunner
class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage
coverageFile suiteAss. debug listener package notPackage timeout_msec testFile disableAnal. appListener idle Instrumentation MonitorningInstrumentation AndroidJunitRunner
class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage
coverageFile suiteAss. debug listener package notPackage timeout_msec testFile disableAnal. appListener idle Instrumentation MonitorningInstrumentation AndroidJunitRunner
class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage
coverageFile suiteAss. debug listener package notPackage timeout_msec testFile disableAnal. appListener idle Instrumentation MonitorningInstrumentation AndroidJunitRunner
Instrumentation MonitorningInstrumentation AndroidJunitRunner class notClass size log annotation notAnnotation numShards
shardIndex delay_msec coverage coverageFile suiteAss. debug listener package notPackage timeout_msec testFile disableAnal. appListener idle
class notClass size log annotation notAnnotation numShards shardIndex delay_msec coverage
coverageFile suiteAss. debug listener package notPackage timeout_msec testFile disableAnal. appListener idle Instrumentation MonitorningInstrumentation AndroidJunitRunner
None
public static ViewInteraction onView(final Matcher<View> viewMatcher) { return BASE.plus(new ViewInteractionModule(viewMatcher)).viewInteraction();
}
BaseLayerComponent
BaseLayerModule UiControllerModule BaseLayerComponent
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent
BaseLayerModule ActivityLifeCycleMonitor Context Looper AsyncTaskPoolMonitor Executor ActivityRootLister EventInjector FailureHandler UiControllerModule
Recycler BaseLayerComponent UiController
BaseLayerComponent
FailureHolder BaseLayerComponent FailureHandler ActivityRootLister InteractionComp IdlingRegistry
FailureHolder BaseLayerComponent FailureHandler ActivityRootLister InteractionComp InteractionModule Matcher<Root> Matcher<View> ViewFinder View
IdlingRegistry
None
Synchronization
perform(click()); MainActivityTest
perform(click()); new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); MainActivityTest ViewActions
perform(click()); new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); MotionEvents.sendDown(uiController, coordinates, precision);
MainActivityTest ViewActions Tapper
perform(click()); new GeneralClickAction(Tap.SINGLE, VISIBLE_CENTER, Press.FINGER)); MotionEvents.sendDown(uiController, coordinates, precision);
uiController.injectMotionEvent(motionEvent); MainActivityTest ViewActions Tapper MotionEvents
InjectKeyEvent loopMainThreadUntilIdle
InjectKeyEvent loopMainThreadUntilIdle loopUntil Asynctask Idling
InjectKeyEvent loopMainThreadUntilIdle keyEventExecutor submit(event) loopUntil loopUntil Asynctask Idling KeyInject
@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()); } UiControllerImpl.java
@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()); } UiControllerImpl.java
private final AtomicReference<IdleMonitor> monitor = new AtomicReference<IdleMonitor>(null); private final ThreadPoolExecutor
pool; private final AtomicInteger activeBarrierChecks = new AtomicInteger(0); AsyncTaskPoolMonitor.java
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<IdleMonitor> monitor = new AtomicReference<IdleMonitor>(null); private final ThreadPoolExecutor pool; private final AtomicInteger activeBarrierChecks = new AtomicInteger(0); AsyncTaskPoolMonitor.java
IdlingResourceRegistry.java
public boolean registerResources(final List resourceList) { } public boolean unregisterResources(final
List resourceList) { } IdlingResourceRegistry.java
public boolean registerResources(final List resourceList) { } public boolean unregisterResources(final
List resourceList) { } IdlingResourceRegistry.java 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(); }
None
Guava
abstract class WrappingExecutorService implements ExecutorService { private final ExecutorService delegate;
protected GuavaWrappingExecutorService(ExecutorService delegate) { this.delegate = checkNotNull(delegate); } protected abstract <T> Callable<T> wrapTask(Callable<T> callable); protected Runnable wrapTask(Runnable command) { final Callable<Object> wrapped = wrapTask(Executors.callable(command); return new Runnable() { @Override public void run() { wrapped.call(); } }; } @Override public final void execute(Runnable command) { delegate.execute(wrapTask(command)); } } Guava
abstract class WrappingExecutorService implements ExecutorService { private final ExecutorService delegate;
protected GuavaWrappingExecutorService(ExecutorService delegate) { this.delegate = checkNotNull(delegate); } protected abstract <T> Callable<T> wrapTask(Callable<T> callable); protected Runnable wrapTask(Runnable command) { final Callable<Object> wrapped = wrapTask(Executors.callable(command); return new Runnable() { @Override public void run() { wrapped.call(); } }; } @Override public final void execute(Runnable command) { delegate.execute(wrapTask(command)); } } Guava
abstract class WrappingExecutorService implements ExecutorService { private final ExecutorService delegate;
protected GuavaWrappingExecutorService(ExecutorService delegate) { this.delegate = checkNotNull(delegate); } protected abstract <T> Callable<T> wrapTask(Callable<T> callable); protected Runnable wrapTask(Runnable command) { final Callable<Object> wrapped = wrapTask(Executors.callable(command); return new Runnable() { @Override public void run() { wrapped.call(); } }; } @Override public final void execute(Runnable command) { delegate.execute(wrapTask(command)); } } Guava
abstract class WrappingExecutorService implements ExecutorService { private final ExecutorService delegate;
protected GuavaWrappingExecutorService(ExecutorService delegate) { this.delegate = checkNotNull(delegate); } protected abstract <T> Callable<T> wrapTask(Callable<T> callable); protected Runnable wrapTask(Runnable command) { final Callable<Object> wrapped = wrapTask(Executors.callable(command); return new Runnable() { @Override public void run() { wrapped.call(); } }; } @Override public final void execute(Runnable command) { delegate.execute(wrapTask(command)); } } Guava
public class IdlingExecutorService extends GuavaWrappingExecutorService { private final CountingIdlingResource mIdlingResource;
public IdlingResourceExecutorService(ExecutorService delegate, CountingIdlingResource idling) { super(delegate); mIdlingResource = idling; }
@Override protected <T> Callable<T> wrapTask(Callable<T> callable) { return new WrappedCallable<>(callable);
} private final class WrappedCallable<T> implements Callable<T> { private final Callable<T> delegate; public WrappedCallable(Callable<T> delegate) { this.delegate = delegate; } @Override public T call() throws Exception { mIdlingResource.increment(); T call; try { call = delegate.call(); } finally { mIdlingResource.decrement(); } } }
@Override protected <T> Callable<T> wrapTask(Callable<T> callable) { return new WrappedCallable<>(callable);
} private final class WrappedCallable<T> implements Callable<T> { private final Callable<T> delegate; public WrappedCallable(Callable<T> delegate) { this.delegate = delegate; } @Override public T call() throws Exception { mIdlingResource.increment(); T call; try { call = delegate.call(); } finally { mIdlingResource.decrement(); } } }
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(); } }; } 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(); } }; } 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(); } }; } RxJava
@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
@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
@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
RxJavaPlugins.getInstance().registerSchedulersHook(RxSchedulerHook.get()); RxJava
OkHttp3IdlingResource
@Before public void setUp() { OkHttpClient client = new OkHttpClient();
IdlingResource resource = OkHttp3IdlingResource.create("OkHttp", client); Espresso.registerIdlingResources(resource); } OkHttp3IdlingResource
@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
Rules
@Rule public ActivityTestRule<Main> activityTestRule = new ActivityTestRule<>(Main){ @Override protected void
beforeActivityLaunched() { super.beforeActivityLaunched(); } @Override protected void afterActivityLaunched() { super.afterActivityLaunched(); } @Override protected void afterActivityFinished() { super.afterActivityFinished(); } }; ActivityTestRule
@Rule public ActivityTestRule<Main> activityTestRule = new ActivityTestRule<>(Main){ @Override protected void
beforeActivityLaunched() { super.beforeActivityLaunched(); } @Override protected void afterActivityLaunched() { super.afterActivityLaunched(); } @Override protected void afterActivityFinished() { super.afterActivityFinished(); } }; ActivityTestRule
@Rule public IntentTestRule<Main> activityTestRule = new IntentTestRule<>(Main) IntentTestRule
@Rule public IntentTestRule<Main> activityTestRule = new IntentTestRule<>(Main) public
IntentsTestRule(Class<T> 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
public class CustomActivityTestRule<T extends Activity> extends ActivityTestRule<T> { public
CustomActivityTestRule(Class<T> activity) { super(activity); } public CustomActivityTestRule(Class<T> activity, boolean initialTouchMode) { super(activity, initialTouchMode); } @Override protected void afterActivityLaunched() { Intents.init(); super.afterActivityLaunched(); } @Override protected void afterActivityFinished() { super.afterActivityFinished(); Intents.release(); } CustomActivityTestRule
public class CustomActivityTestRule<T extends Activity> extends ActivityTestRule<T> { public
CustomActivityTestRule(Class<T> activity) { super(activity); } public CustomActivityTestRule(Class<T> activity, boolean initialTouchMode) { super(activity, initialTouchMode); } @Override protected void afterActivityLaunched() { Intents.init(); super.afterActivityLaunched(); } @Override protected void afterActivityFinished() { super.afterActivityFinished(); Intents.release(); } CustomActivityTestRule
public interface TestRule { Statement apply(Statement base, Description description); }
TestRule
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
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(); } } } TestRule
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(); } } }; } } TestRule
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()); TestRule TestRule
public abstract class TestWatcher implements TestRule TestWatcher
return new Statement() { @Override public void evaluate() throws Throwable
{ List<Throwable> errors = new ArrayList<Throwable>(); 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 TestWatcher
return new Statement() { @Override public void evaluate() throws Throwable
{ List<Throwable> errors = new ArrayList<Throwable>(); 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 TestWatcher
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); } TestWatcher
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); } TestWatcher
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); } TestWatcher
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”)))); RuleChain
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”)))); RuleChain
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 rule Starting middle rule Starting inner rule Finishing inner rule Finishing middle rule Finishing outer rule RuleChain
public NewActivityTestRule<Main> activityRule = new NewActivityTestRule<>(Main.class);
public NewActivityTestRule<Main> activityRule = new NewActivityTestRule<>(Main.class); @Rule public
RuleChain chain = RuleChain.outerRule(new FailedTest() .around(activityRule);
public NewActivityTestRule<Main> 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);
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
Permissions
None
@Before public void grantPhonePermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand( "pm grant " + getTargetContext().getPackageName() + " android.permission.CALL_PHONE"); } }
afterEvaluate { tasks.each { task -> if (task.name.startsWith('connectedAndroidTest')) { task.dependsOn
grantAnimationPermissions } } }
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
None
ATSL Spoon Fork TestButler Burst RxPresso AndroidTestRules
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
Cảm ơn
Cảm ơn Agoda đang tuyển dụng
+IñakiVillar @inyaki_mwc Cảm ơn Agoda đang tuyển dụng