Espresso测试中全局禁用非Animator类动画的方案咨询
这确实是个挺头疼的问题——我之前在做一款动画密集型应用的Espresso测试时,也碰到过一模一样的状况:无限循环的非标准动画把Espresso卡得死死的,常规的动画缩放设置完全没用,还不能改应用代码。不过折腾了一阵后,倒是找到了几个可行的方案,不用碰应用代码就能解决:
方案1:用ADB命令强制修改系统全局动画缩放
虽然你说常规方法无效,但可能是没用到覆盖所有动画类型的命令?试试直接通过ADB修改系统的三个核心动画缩放参数,这对绝大多数非标准实现的动画也能生效:
测试前执行这三条命令:
adb shell settings put global window_animation_scale 0.0 adb shell settings put global transition_animation_scale 0.0 adb shell settings put global animator_duration_scale 0.0
测试结束后记得恢复默认值(把0.0改成1.0),避免影响设备日常使用。
注意:极少数硬编码时长的自定义动画可能不受这个设置影响,但大部分场景下都能解决问题,而且不需要设备root权限。
方案2:自定义IdlingResource绕过Espresso的空闲等待
Espresso无限挂起的核心原因是它一直在等待界面进入“空闲”状态,而无限循环动画永远不会让界面空闲。我们可以自己定义一个IdlingResource,告诉Espresso什么时候可以继续执行测试,不用等动画结束:
比如写一个简单的自定义Idle资源:
public class AnimationIgnoringIdlingResource implements IdlingResource { private ResourceCallback callback; private boolean isIdle = false; @Override public String getName() { return "AnimationIgnoringIdlingResource"; } @Override public boolean isIdleNow() { return isIdle; } @Override public void registerIdleTransitionCallback(ResourceCallback callback) { this.callback = callback; } // 在测试中调用这个方法,手动标记界面为可测试状态 public void markAsIdle() { isIdle = true; if (callback != null) { callback.onTransitionToIdle(); } } }
测试时的用法:
@Rule public ActivityScenarioRule<MainActivity> activityRule = new ActivityScenarioRule<>(MainActivity.class); private AnimationIgnoringIdlingResource idleResource; @Before public void setup() { idleResource = new AnimationIgnoringIdlingResource(); Espresso.registerIdlingResources(idleResource); } @Test public void testWithLoopAnimation() { // 执行触发动画的操作 onView(withId(R.id.start_animation_btn)).perform(click()); // 手动标记界面为空闲,让Espresso继续执行 idleResource.markAsIdle(); // 接下来的测试操作就不会被动画卡住了 onView(withId(R.id.target_view)).check(matches(isDisplayed())); } @After public void teardown() { Espresso.unregisterIdlingResources(idleResource); }
如果能知道动画容器的状态(比如某个属性停止变化),还可以优化isIdleNow()方法,让它自动判断,不用手动调用markAsIdle()。
方案3:混合使用UI Automator跳过动画等待
UI Automator的等待逻辑和Espresso不同,它不会严格等待界面空闲,而是通过超时机制来控制等待时长。你可以在Espresso测试中混合使用UI Automator,绕过动画带来的挂起问题:
示例代码:
@Test public void testWithLoopAnimationUsingUiAutomator() { UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); // 等待目标控件出现,超时时间设为5秒(根据实际情况调整) device.wait(Until.hasObject(By.res("com.your.app:id/target_view")), 5000); // 直接操作控件,不用等动画结束 UiObject targetView = device.findObject(By.res("com.your.app:id/target_view")); targetView.click(); // 后续可以继续用Espresso操作 onView(withId(R.id.result_text)).check(matches(withText("Success"))); }
这种方法适合那些系统缩放设置无效、自定义Idle资源又不好实现的场景,缺点是UI Automator的定位精度不如Espresso,需要注意控件的资源ID或文本是否唯一。
方案4:用Test Rule自动管理动画设置
如果需要在多个测试用例中禁用动画,可以写一个Test Rule,自动在测试前后执行ADB命令修改系统设置,不用每个测试都重复写代码:
public class DisableAnimationRule implements TestRule { @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { // 测试前禁用所有动画 runAdbCommand("settings put global window_animation_scale 0.0"); runAdbCommand("settings put global transition_animation_scale 0.0"); runAdbCommand("settings put global animator_duration_scale 0.0"); try { // 执行测试逻辑 base.evaluate(); } finally { // 测试后恢复动画设置 runAdbCommand("settings put global window_animation_scale 1.0"); runAdbCommand("settings put global transition_animation_scale 1.0"); runAdbCommand("settings put global animator_duration_scale 1.0"); } } }; } private void runAdbCommand(String command) throws IOException { Runtime.getRuntime().exec("adb shell " + command); } }
然后在测试类中添加这个Rule:
@Rule public DisableAnimationRule disableAnimationRule = new DisableAnimationRule(); @Rule public ActivityScenarioRule<MainActivity> activityRule = new ActivityScenarioRule<>(MainActivity.class); // 你的测试用例...
总结一下,如果系统缩放命令能解决问题,方案1是最省心的;如果不行,方案2或3是更可靠的选择;方案4适合批量测试用例的场景。
内容的提问来源于stack exchange,提问作者arvere

