Android 自动化测试中的 Espresso 谓词?

Android Espresso predicates in automated testing?

有什么方法可以实现类似于 iOS Xcode 的 NSPredicate 中的谓词以等待 Android Espresso 中的某些事件?

对于 iOS,您可以调用 expectationForPredicatewaitForExpectationsWithTimeout 作为 XCTest 的一部分。

我有两个类似的应用程序,用于 iOS 和 Android,我的任务是测试它们。在 iOS 中,我可以执行以下操作:

  let app = XCUIApplication();
  let condition = app.staticTexts["Text That Displays After Event"]
  let exists = NSPredicate(format: "exists == true")
  expectationForPredicate(exists, evaluatedWithObject: condition, handler: nil)
  waitForExpectationsWithTimeout(5, handler: nil)

  XCTAssert(app.staticTexts["Text That Displays After Event"].exists)

在Android中,我能做的最好的事情就是注册一个空闲资源并等待几秒钟,然后检查事件是否已手动发生。

  long waitingTime = 3 * DateUtils.SECOND_IN_MILLIS;
  IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
  IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
  IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
  Espresso.registerIdlingResources(idlingResource);

  onView(withId(R.id.id_in_next_event)).check(matches(isDisplayed()));

  Espresso.unregisterIdlingResources(idlingResource);

我相信一定有更好的方法来做到这一点。

大多数事情都可以用 Instrumentation#waitForIdleSync() 方法完成。这将等待所有 UI 事件在继续之前得到处理。假设您有一个 ID 为 buttonId 的按钮,当按下该按钮时,它将显示一个 ID 为 hiddenView 的视图。

如果你这样做:

private final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();

@Test
public void checkViewIsRevealed() {
   onView(withId(R.id.buttonId)).perform(click());

   instrumentation.waitForIdleSync();

   onView(withId(R.id.hiddenView)).check(matches(isDisplayed());
}

所以在这个过程中,执行 "click" 会在 hiddenView 上调用 View#setVisibility()。然后,您等待空闲以确保所有 UI 操作都通过。然后检查是否显示了新视图。

您可以编写自定义 ViewAction 等待 Matcher<View> 变为真。 但这只有在 Espresso 不自行等待时才需要。 Espresso 等待,例如如果应用程序的主线程正在工作或者 AsyncTask 是 运行.

等待查看操作可以像这样实现:

public class WaitForCondition implements ViewAction {
    private static final long IDLING_INTERVAL = 50;
    private final Matcher<View> mCondition;
    private final long mTimeout;

    public WaitForCondition(final Matcher<View> condition, final long timeout) {
        mCondition = condition;
        mTimeout = timeout;
    }

    @Override
    public Matcher<View> getConstraints() {
        return isAssignableFrom(View.class);
    }

    @Override
    public String getDescription() {
        return String.format(Locale.US, "Waiting until the condition '%s' will become true (timeout %d ms)", getConditionDescription(), mTimeout);
    }

    private String getConditionDescription() {
        StringDescription description = new StringDescription();
        mCondition.describeTo(description);
        return description.toString();
    }

    @Override
    public void perform(final UiController uiController, final View view) {

        if (mCondition.matches(view)) {
            return;
        }
        final long timeOut = System.currentTimeMillis() + mTimeout;

        // Wait for the condition to become true
        while (System.currentTimeMillis() <= timeOut) {
            uiController.loopMainThreadForAtLeast(IDLING_INTERVAL);
            if (mCondition.matches(view)) {
                return;
            }
        }
        throw new PerformException.Builder()
                .withActionDescription(this.getDescription())
                .withViewDescription(HumanReadables.describe(view))
                .withCause(new RuntimeException(String.format(Locale.US,
                        "Condition '%s' did not become true after %d ms of waiting.", getConditionDescription(), mTimeout)))
                .build();
    }
}

然后使用它:

onView(withId(R.id.id_in_next_event)).perform(new WaitForCondition(matches(isDisplayed()), 3000));