使用 Espresso 在 Android 上测试进度条
Testing progress bar on Android with Espresso
工作流程应如下所示:
- Activity 开始
- 进度条可见
- 网络请求触发(空闲资源已注册,因此 espresso 知道如何等待它)。
- 隐藏进度条
- 显示来自网络的文本。
到目前为止,我已经为步骤 1、3、5 编写了断言并且它完美地工作:
onView(withText("foo 1"))
.check(matches(isDisplayed()));
问题是,我不知道如何让 espresso 知道如何验证进度条的可见性 before request is made and after 提出要求。
考虑 onCreate()
方法如下:
super.onCreate(...);
setContentView(...);
showProgressBar(true);
apiClient.getStuff(new Callback() {
public void onSuccess() {
showProgressBar(false);
}
});
我尝试了以下方法,但没有用:
// Activity is launched at this point.
activityRule.launchActivity(new Intent());
// Up to this point, the request has been fired and response was
// returned, so the progress bar is now GONE.
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()));
onView(withId(R.id.progress_bar))
.check(matches(not(isDisplayed())));
发生这种情况的原因是,由于客户端已注册为空闲资源,espresso 将等到 idle 再次 运行ning 第一次onView(...progressbar...)...
所以我需要一种方法让浓缩咖啡 运行 知道 在 闲置之前。
编辑: 这也不起作用:
idlingResource.registerIdleTransitionCallback(new IdlingResource.ResourceCallback() {
@Override
public void onTransitionToIdle() {
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()));
}
});
看起来这可能不是真的可能。虽然它是一个较老的组 posting,但在 Android Test Kit Discussion 中有一个相当决定性的答案,其中指出 UI 线程在进度条的动画期间不会休息,并且所以 Espresso 框架无法执行。
Marcus Klepp 建议跳过此 here through the use of build types. The Gradle plugin will permit you to define different build types。您可以在 androidTest
构建类型中设置不同的布局,用通用的东西替换有问题的 View
。如果您所做的只是确认小部件在一组条件下 isDisplayed()
,在另一组条件下 not(isDisplayed())
,那么您肯定可以通过不同的布局文件来实现它。倒不是一点提升都没有。
最后,这里可能还有另一个 post,其中包含一些附加信息:"java.lang.RuntimeException: Could not launch intent" for UI with indeterminate ProgressBar
Espresso 的动画有问题。您可以将进度条的可绘制对象设置为仅用于测试的静态对象,它会按预期工作。
Drawable notAnimatedDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.whatever);
((ProgressBar) getActivity().findViewById(R.id.progress_bar)).setIndeterminateDrawable(notAnimatedDrawable);
onView(withId(R.id.progress_bar)).check(matches(isDisplayed()));
正如我所见,Espresso
与跳过动态 UI 操作紧密相关,这就是为什么您无法使用 Espresso
测试 ProgressBar
的原因。但是,您可以使用另一个 Android Google 工具轻松完成此操作:UiAutomator
,如下所示:
saveButton().click(); // perform action opening ProgressBar with UiAutomator, not Espresso
assertTrue(progressBar().exists());
使用这些静态工具:
public static UiObject progressBar() {
return uiObjectWithText(R.string.my_progress);
}
public static UiObject saveButton() {
return uiObjectWithId(R.id.my_save_button);
}
public static UiObject uiObjectWithId(@IdRes int id) {
String resourceId = getTargetContext().getResources().getResourceName(id);
UiSelector selector = new UiSelector().resourceId(resourceId);
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
public static UiObject uiObjectWithText(@StringRes int stringRes) {
UiSelector selector = new UiSelector().text(getTargetContext().getString(stringRes));
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
确保您的 build.gradle
包括:
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
在我的案例中,上面提供的解决方案也适用,但我对其进行了简化,因此添加了 build.gradle
uiautomator 库
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
并创建了一个新的 class,它可以与进度条一起工作
public class ProgressBarHandler {
public static void waitUntilGoneProgressBar() {
progressBar().waitUntilGone(10000);
}
private static UiObject progressBar() {
return uiObjectWithId(R.id.progress_bar);
}
private static UiObject uiObjectWithId(@IdRes int id) {
String resourceId = getTargetContext().getResources().getResourceName(id);
UiSelector selector = new UiSelector().resourceId(resourceId);
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
}
并且在我的测试中使用所有 Espresso 方法,并且仅在需要时才在测试中访问 UiAutomator,例如
public class LoginTest extends AbstractTest {
@Rule
public ActivityTestRule<LoginActivity> createAccountActivityTestRule = new ActivityTestRule<>(LoginActivity.class);
@Test
public void loginTest() {
onView(withId(R.id.login_email)).perform(typeText("autotest666@gmail.com"));
onView(withId(R.id.input_password)).perform(typeText("Password123."));
onView(withId(R.id.login_log_in)).perform(click());
waitUntilGoneProgressBar();
onView(withId(R.id.fragment_home_title)).check(matches(isDisplayed()));
}
工作流程应如下所示:
- Activity 开始
- 进度条可见
- 网络请求触发(空闲资源已注册,因此 espresso 知道如何等待它)。
- 隐藏进度条
- 显示来自网络的文本。
到目前为止,我已经为步骤 1、3、5 编写了断言并且它完美地工作:
onView(withText("foo 1"))
.check(matches(isDisplayed()));
问题是,我不知道如何让 espresso 知道如何验证进度条的可见性 before request is made and after 提出要求。
考虑 onCreate()
方法如下:
super.onCreate(...);
setContentView(...);
showProgressBar(true);
apiClient.getStuff(new Callback() {
public void onSuccess() {
showProgressBar(false);
}
});
我尝试了以下方法,但没有用:
// Activity is launched at this point.
activityRule.launchActivity(new Intent());
// Up to this point, the request has been fired and response was
// returned, so the progress bar is now GONE.
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()));
onView(withId(R.id.progress_bar))
.check(matches(not(isDisplayed())));
发生这种情况的原因是,由于客户端已注册为空闲资源,espresso 将等到 idle 再次 运行ning 第一次onView(...progressbar...)...
所以我需要一种方法让浓缩咖啡 运行 知道 在 闲置之前。
编辑: 这也不起作用:
idlingResource.registerIdleTransitionCallback(new IdlingResource.ResourceCallback() {
@Override
public void onTransitionToIdle() {
onView(withId(R.id.progress_bar))
.check(matches(isDisplayed()));
}
});
看起来这可能不是真的可能。虽然它是一个较老的组 posting,但在 Android Test Kit Discussion 中有一个相当决定性的答案,其中指出 UI 线程在进度条的动画期间不会休息,并且所以 Espresso 框架无法执行。
Marcus Klepp 建议跳过此 here through the use of build types. The Gradle plugin will permit you to define different build types。您可以在 androidTest
构建类型中设置不同的布局,用通用的东西替换有问题的 View
。如果您所做的只是确认小部件在一组条件下 isDisplayed()
,在另一组条件下 not(isDisplayed())
,那么您肯定可以通过不同的布局文件来实现它。倒不是一点提升都没有。
最后,这里可能还有另一个 post,其中包含一些附加信息:"java.lang.RuntimeException: Could not launch intent" for UI with indeterminate ProgressBar
Espresso 的动画有问题。您可以将进度条的可绘制对象设置为仅用于测试的静态对象,它会按预期工作。
Drawable notAnimatedDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.whatever);
((ProgressBar) getActivity().findViewById(R.id.progress_bar)).setIndeterminateDrawable(notAnimatedDrawable);
onView(withId(R.id.progress_bar)).check(matches(isDisplayed()));
正如我所见,Espresso
与跳过动态 UI 操作紧密相关,这就是为什么您无法使用 Espresso
测试 ProgressBar
的原因。但是,您可以使用另一个 Android Google 工具轻松完成此操作:UiAutomator
,如下所示:
saveButton().click(); // perform action opening ProgressBar with UiAutomator, not Espresso
assertTrue(progressBar().exists());
使用这些静态工具:
public static UiObject progressBar() {
return uiObjectWithText(R.string.my_progress);
}
public static UiObject saveButton() {
return uiObjectWithId(R.id.my_save_button);
}
public static UiObject uiObjectWithId(@IdRes int id) {
String resourceId = getTargetContext().getResources().getResourceName(id);
UiSelector selector = new UiSelector().resourceId(resourceId);
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
public static UiObject uiObjectWithText(@StringRes int stringRes) {
UiSelector selector = new UiSelector().text(getTargetContext().getString(stringRes));
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
确保您的 build.gradle
包括:
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
在我的案例中,上面提供的解决方案也适用,但我对其进行了简化,因此添加了 build.gradle
uiautomator 库
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
并创建了一个新的 class,它可以与进度条一起工作
public class ProgressBarHandler {
public static void waitUntilGoneProgressBar() {
progressBar().waitUntilGone(10000);
}
private static UiObject progressBar() {
return uiObjectWithId(R.id.progress_bar);
}
private static UiObject uiObjectWithId(@IdRes int id) {
String resourceId = getTargetContext().getResources().getResourceName(id);
UiSelector selector = new UiSelector().resourceId(resourceId);
return UiDevice.getInstance(getInstrumentation()).findObject(selector);
}
}
并且在我的测试中使用所有 Espresso 方法,并且仅在需要时才在测试中访问 UiAutomator,例如
public class LoginTest extends AbstractTest {
@Rule
public ActivityTestRule<LoginActivity> createAccountActivityTestRule = new ActivityTestRule<>(LoginActivity.class);
@Test
public void loginTest() {
onView(withId(R.id.login_email)).perform(typeText("autotest666@gmail.com"));
onView(withId(R.id.input_password)).perform(typeText("Password123."));
onView(withId(R.id.login_log_in)).perform(click());
waitUntilGoneProgressBar();
onView(withId(R.id.fragment_home_title)).check(matches(isDisplayed()));
}