Espresso freezing on view with looping animation

2019-03-18 16:18发布

I have a view in which an element is animated with the following in an infinite loop:

<translate
    android:fromXDelta="0%"
    android:toXDelta="100%"
    android:duration="10000"
    android:repeatCount="-1"
    android:repeatMode="reverse"/>

When Espresso opens my activity, it's able to perform some operations but quickly freezes. I suppose Espresso is waiting for the UI thread to become idle which never happens in this case.

Is the only way for me to test this view to implement a mechanism to disable animations? I could have another class handle animations, which would be mocked for tests. Or build time conditionals.

Edit: I have yet to create a sample project to attempt to recreate the issue, but here are some further details in the meantime:

1) I am using Jake Wharton's ActivityRule to have my activity automatically launched (https://gist.github.com/JakeWharton/1c2f2cadab2ddd97f9fb).

2) This is my test:

onView(withId(R.id.btn_yes)).perform(click());

3) Here is the complete stack trace. Note the AppNotIdleException:

Running tests
Test running started
android.support.test.espresso.PerformException: Error performing 'single click' on view 'with id: com.myapp:id/btn_yes'.
at android.support.test.espresso.PerformException$Builder.build(PerformException.java:83)
at android.support.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:70)
at android.support.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:53)
at android.support.test.espresso.ViewInteraction.runSynchronouslyOnUiThread(ViewInteraction.java:185)
at android.support.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:115)
at android.support.test.espresso.ViewInteraction.perform(ViewInteraction.java:87)
at com.myapp.espresso.MyActivityTest.yesButtonTest(MyActivityTest.java:53)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at com.myapp.espresso.ActivityRule$2.evaluate(ActivityRule.java:129)
at org.junit.rules.RunRules.evaluate(RunRules.java:18)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:24)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:270)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1837)
Caused by: android.support.test.espresso.AppNotIdleException: Looped for 3580 iterations over 60 SECONDS. The following Idle Conditions failed .
at android.support.test.espresso.IdlingPolicy.handleTimeout(IdlingPolicy.java:61)
at android.support.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:471)
at android.support.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:402)
at android.support.test.espresso.base.UiControllerImpl.injectMotionEvent(UiControllerImpl.java:226)
at android.support.test.espresso.action.MotionEvents.sendDown(MotionEvents.java:78)
at android.support.test.espresso.action.Tap.sendSingleTap(Tap.java:133)
at android.support.test.espresso.action.Tap.access$100(Tap.java:35)
at android.support.test.espresso.action.Tap$1.sendTap(Tap.java:40)
at android.support.test.espresso.action.GeneralClickAction.perform(GeneralClickAction.java:98)
at android.support.test.espresso.ViewInteraction$1.run(ViewInteraction.java:144)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

4) Stack traces when pausing the execution of the test while it's frozen:

Thread #1:

"Instr: android.support.test.runner.AndroidJUnitRunner@4549" prio=5 waiting
  java.lang.Thread.State: WAITING
     blocks Instr: android.support.test.runner.AndroidJUnitRunner@4549
      at java.lang.Object.wait(Object.java:-1)
      at java.lang.Thread.parkFor(Thread.java:1220)
      - locked <0x13a3> (a java.lang.Object)
      at sun.misc.Unsafe.park(Unsafe.java:299)
      at java.util.concurrent.locks.LockSupport.park(LockSupport.java:157)
      at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:400)
      at java.util.concurrent.FutureTask.get(FutureTask.java:162)
      at android.support.test.espresso.ViewInteraction.runSynchronouslyOnUiThread(ViewInteraction.java:181)
      at android.support.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:115)
      at android.support.test.espresso.ViewInteraction.perform(ViewInteraction.java:87)

Thread #2:

"main@4663" prio=5 runnable
  java.lang.Thread.State: RUNNABLE
      at android.view.ThreadedRenderer.nSyncAndDrawFrame(ThreadedRenderer.java:-1)
      at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:333)
      at android.view.ViewRootImpl.draw(ViewRootImpl.java:2492)
      at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2337)
      at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1968)
      at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1054)
      at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5779)
      at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
      at android.view.Choreographer.doCallbacks(Choreographer.java:580)
      at android.view.Choreographer.doFrame(Choreographer.java:550)
      at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
      at android.os.Handler.handleCallback(Handler.java:739)
      at android.os.Handler.dispatchMessage(Handler.java:95)
      at android.support.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:461)
      at android.support.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:402)
      at android.support.test.espresso.base.UiControllerImpl.injectMotionEvent(UiControllerImpl.java:226)
      at android.support.test.espresso.action.MotionEvents.sendDown(MotionEvents.java:78)
      at android.support.test.espresso.action.Tap.sendSingleTap(Tap.java:133)
      at android.support.test.espresso.action.Tap.access$100(Tap.java:35)
      at android.support.test.espresso.action.Tap$1.sendTap(Tap.java:40)
      at android.support.test.espresso.action.GeneralClickAction.perform(GeneralClickAction.java:98)
      at android.support.test.espresso.ViewInteraction$1.run(ViewInteraction.java:144)
      at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
      at java.util.concurrent.FutureTask.run(FutureTask.java:237)
      at android.os.Handler.handleCallback(Handler.java:739)
      at android.os.Handler.dispatchMessage(Handler.java:95)
      at android.os.Looper.loop(Looper.java:135)
      at android.app.ActivityThread.main(ActivityThread.java:5221)
      at java.lang.reflect.Method.invoke(Method.java:-1)
      at java.lang.reflect.Method.invoke(Method.java:372)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

1条回答
劳资没心,怎么记你
2楼-- · 2019-03-18 17:06

Yes, the problem you are having with the test is due to the animation.

The only solution I can think of is indeed to turn off the infinite animations.

In general it is a good idea to turn off animations when running functional tests, as they always introduce some flakiness.

A bit of background:

The UI thread is never idle from Espresso's perspective, because the message queue always contains an event that is scheduled for handling closer than the threshold used to determine if thread is idle.

When you look at QueueInterrogator, you can see that it's determineQueueState() returns TASK_DUE_SOON when message queue contains an event that is scheduled for handling in less than 16ms. UiController will only continue execution when all idle conditions are fulfilled, in case of the QueueInterrogator this is only the case if the message queue is empty or the next message is scheduled for handling in 16ms or later.

Animations will invalidate the View they are transforming, and this will trigger a new view hierarchy traversal by the Choreographer. And this trigger message coming from ViewRoot to the Choreographer is what is keeping your UI thread message queue not idle.

查看更多
登录 后发表回答