0

[Android] Create AWAITING state, use in TabModelConditions

Gated Conditions's checks are now delayed until the gate can be
evaluated yet to tell if the gated Condition is required, returning
fulfilled() or notFulfilled() instead of awaiting().

The motivation is to remove dependencies on
ChromeTabbedActivityTestRule from Stations that use
TabModelConditions. These Stations right now assume the
ChromeTabbedActivity exists and has tab models initialized already.

Bug: 339499637
Change-Id: I07ba4acf44433e1df9f7472058943dbf24b8ac49
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5529863
Reviewed-by: Calder Kitagawa <ckitagawa@chromium.org>
Commit-Queue: Henrique Nakashima <hnakashima@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1299552}
This commit is contained in:
Henrique Nakashima
2024-05-10 22:05:14 +00:00
committed by Chromium LUCI CQ
parent 529e87c7dd
commit c6c0155629
12 changed files with 138 additions and 35 deletions

@ -82,11 +82,11 @@ public class ActivityElement<ActivityT extends Activity>
}
mMatchedActivity = candidate;
if (mMatchedActivity == null) {
return notFulfilled("No Activity with expected class");
return awaiting("No Activity with expected class");
}
@ActivityState int state = ApplicationStatus.getStateForActivity(mMatchedActivity);
return whether(
return fulfilledOrAwaiting(
state == ActivityState.RESUMED,
"matched: %s (state=%s)",
mMatchedActivity,

@ -127,7 +127,7 @@ public abstract class Condition {
Supplier<?> supplier = kv.getValue();
if (!supplier.hasValue()) {
if (suppliersMissing == null) {
suppliersMissing = new StringBuilder("waiting for suppliers for: ");
suppliersMissing = new StringBuilder("waiting for suppliers of: ");
} else {
suppliersMissing.append(", ");
}
@ -137,7 +137,7 @@ public abstract class Condition {
}
if (suppliersMissing != null) {
return notFulfilled(suppliersMissing.toString());
return awaiting(suppliersMissing.toString());
}
return null;
@ -208,4 +208,40 @@ public abstract class Condition {
public static ConditionStatus whether(boolean isFulfilled, String message, Object... args) {
return whether(isFulfilled, String.format(message, args));
}
/**
* {@link #checkWithSuppliers()} should return this when it does not have information to check
* the Condition yet.
*
* <p>It is considered not fulfilled for most purposes. The exception is that if the Condition
* is used as a gate Condition, the gated Condition will not be checked, considered FULFILLED,
* or considered NOT_FULFILLED until the gate resolves to FULFILLED or NOT_FULFILLED.
*
* @param message A short message stating what is being awaited for
*/
public static ConditionStatus awaiting(@Nullable String message) {
return new ConditionStatus(Status.AWAITING, message);
}
/** {@link #awaiting(String)} with format parameters. */
@FormatMethod
public static ConditionStatus awaiting(String message, Object... args) {
return new ConditionStatus(Status.AWAITING, String.format(message, args));
}
/** {@link #checkWithSuppliers()} can return this as a convenience method. */
public static ConditionStatus fulfilledOrAwaiting(
boolean isFulfilled, @Nullable String message) {
return isFulfilled ? fulfilled(message) : awaiting(message);
}
/**
* {@link #fulfilledOrAwaiting(boolean, String)} with more details to be logged as a short
* message.
*/
@FormatMethod
public static ConditionStatus fulfilledOrAwaiting(
boolean isFulfilled, String message, Object... args) {
return fulfilledOrAwaiting(isFulfilled, String.format(message, args));
}
}

@ -26,13 +26,15 @@ public class ConditionStatus {
@IntDef({
ConditionStatus.Status.NOT_FULFILLED,
ConditionStatus.Status.FULFILLED,
ConditionStatus.Status.ERROR
ConditionStatus.Status.ERROR,
ConditionStatus.Status.AWAITING
})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {
int NOT_FULFILLED = 0;
int FULFILLED = 1;
int ERROR = 2;
int AWAITING = 3;
}
private final long mTimestamp;
@ -59,6 +61,10 @@ public class ConditionStatus {
return mStatus == Status.ERROR;
}
public boolean isAwaiting() {
return mStatus == Status.AWAITING;
}
public @Status int getStatus() {
return mStatus;
}
@ -87,6 +93,7 @@ public class ConditionStatus {
case Status.FULFILLED -> "REQUIRED";
case Status.NOT_FULFILLED -> "NOT REQ";
case Status.ERROR -> "ERROR";
case Status.AWAITING -> "AWAITING";
default -> throw new IllegalStateException();
};
fullMessage.append(statusMessage);

@ -105,9 +105,10 @@ class StatusStore {
private String getStatusPrefix() {
return switch (mStatus) {
case Status.FULFILLED -> "OK |";
case Status.NOT_FULFILLED -> "NO |";
case Status.ERROR -> "ERR |";
case Status.FULFILLED -> "OK |";
case Status.NOT_FULFILLED -> "NO |";
case Status.ERROR -> "ERR |";
case Status.AWAITING -> "WAIT |";
default -> throw new IllegalStateException("Unexpected value: " + mStatus);
};
}

@ -58,6 +58,10 @@ public class ViewConditions {
protected ConditionStatus checkWithSuppliers() throws Exception {
ConditionStatus gateStatus = mGate.check();
String gateMessage = gateStatus.getMessageAsGate();
if (gateStatus.isAwaiting()) {
return notFulfilled(gateMessage);
}
if (!gateStatus.isFulfilled()) {
return fulfilled(gateMessage);
}

@ -224,6 +224,7 @@ android_library("chrome_java_transit") {
"javatests/src/org/chromium/chrome/test/transit/RegularTabSwitcherStation.java",
"javatests/src/org/chromium/chrome/test/transit/SettingsStation.java",
"javatests/src/org/chromium/chrome/test/transit/TabModelConditions.java",
"javatests/src/org/chromium/chrome/test/transit/TabModelSelectorCondition.java",
"javatests/src/org/chromium/chrome/test/transit/TabSwitcherActionMenuFacility.java",
"javatests/src/org/chromium/chrome/test/transit/TabSwitcherStation.java",
"javatests/src/org/chromium/chrome/test/transit/WebPageIncognitoAppMenuFacility.java",

@ -64,7 +64,8 @@ public abstract class HubBaseStation extends Station {
R.string.accessibility_tab_switcher_incognito_stack)));
protected final ChromeTabbedActivityTestRule mChromeTabbedActivityTestRule;
private ActivityElement<ChromeTabbedActivity> mActivityElement;
protected ActivityElement<ChromeTabbedActivity> mActivityElement;
protected TabModelSelectorCondition mTabModelSelectorCondition;
/**
* @param chromeTabbedActivityTestRule The {@link ChromeTabbedActivityTestRule} of the test.
@ -81,13 +82,15 @@ public abstract class HubBaseStation extends Station {
@Override
public void declareElements(Elements.Builder elements) {
mActivityElement = elements.declareActivity(ChromeTabbedActivity.class);
mTabModelSelectorCondition =
elements.declareEnterCondition(new TabModelSelectorCondition(mActivityElement));
elements.declareView(HUB_TOOLBAR);
elements.declareView(HUB_PANE_HOST);
elements.declareView(HUB_MENU_BUTTON);
Condition incognitoTabsExist =
TabModelConditions.anyIncognitoTabsExist(mChromeTabbedActivityTestRule);
TabModelConditions.anyIncognitoTabsExist(mTabModelSelectorCondition);
elements.declareViewIf(REGULAR_TOGGLE_TAB_BUTTON, incognitoTabsExist);
elements.declareViewIf(INCOGNITO_TOGGLE_TAB_BUTTON, incognitoTabsExist);
@ -169,8 +172,7 @@ public abstract class HubBaseStation extends Station {
// TODO(crbug.com/40287437): Content description seems reasonable for now, this might get
// harder
// once we use a recycler view with text based buttons.
String contentDescription =
mChromeTabbedActivityTestRule.getActivity().getString(contentDescriptionRes);
String contentDescription = mActivityElement.get().getString(contentDescriptionRes);
onView(
allOf(
isDescendantOfA(HUB_PANE_SWITCHER.getViewMatcher()),

@ -37,6 +37,6 @@ public class HubTabSwitcherStation extends HubTabSwitcherBaseStation {
elements.declareViewIf(
EMPTY_STATE_TEXT,
TabModelConditions.noRegularTabsExist(mChromeTabbedActivityTestRule));
TabModelConditions.noRegularTabsExist(mTabModelSelectorCondition));
}
}

@ -31,11 +31,11 @@ public class RegularTabSwitcherStation extends TabSwitcherStation {
super.declareElements(elements);
Condition noRegularTabsExist =
TabModelConditions.noRegularTabsExist(mChromeTabbedActivityTestRule);
TabModelConditions.noRegularTabsExist(mTabModelSelectorCondition);
elements.declareViewIf(EMPTY_STATE_TEXT, noRegularTabsExist);
Condition incognitoTabsExist =
TabModelConditions.anyIncognitoTabsExist(mChromeTabbedActivityTestRule);
TabModelConditions.anyIncognitoTabsExist(mTabModelSelectorCondition);
elements.declareViewIf(INCOGNITO_TOGGLE_TABS, incognitoTabsExist);
elements.declareViewIf(REGULAR_TOGGLE_TAB_BUTTON, incognitoTabsExist);
elements.declareViewIf(INCOGNITO_TOGGLE_TAB_BUTTON, incognitoTabsExist);

@ -4,49 +4,54 @@
package org.chromium.chrome.test.transit;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.transit.Condition;
import org.chromium.base.test.transit.ConditionStatus;
import org.chromium.base.test.transit.UiThreadCondition;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
/** {@link Condition}s regarding the state of TabModels. */
public class TabModelConditions {
/** Fulfilled when no regular tabs are open. */
public static Condition noRegularTabsExist(ChromeTabbedActivityTestRule testRule) {
return new NoTabsExistCondition(testRule, /* incognito= */ false);
public static Condition noRegularTabsExist(
Supplier<TabModelSelector> tabModelSelectorSupplier) {
return new NoTabsExistCondition(tabModelSelectorSupplier, /* incognito= */ false);
}
/** Fulfilled when no incognito tabs are open. */
public static Condition noIncognitoTabsExist(ChromeTabbedActivityTestRule testRule) {
return new NoTabsExistCondition(testRule, /* incognito= */ true);
public static Condition noIncognitoTabsExist(
Supplier<TabModelSelector> tabModelSelectorSupplier) {
return new NoTabsExistCondition(tabModelSelectorSupplier, /* incognito= */ true);
}
/** Fulfilled when one or more regular tabs are open. */
public static Condition anyRegularTabsExist(ChromeTabbedActivityTestRule testRule) {
return new AnyTabsExistCondition(testRule, /* incognito= */ false);
public static Condition anyRegularTabsExist(
Supplier<TabModelSelector> tabModelSelectorSupplier) {
return new AnyTabsExistCondition(tabModelSelectorSupplier, /* incognito= */ false);
}
/** Fulfilled when one or more incognito tabs are open. */
public static Condition anyIncognitoTabsExist(ChromeTabbedActivityTestRule testRule) {
return new AnyTabsExistCondition(testRule, /* incognito= */ true);
public static Condition anyIncognitoTabsExist(
Supplier<TabModelSelector> tabModelSelectorSupplier) {
return new AnyTabsExistCondition(tabModelSelectorSupplier, /* incognito= */ true);
}
private static class NoTabsExistCondition extends UiThreadCondition {
private final ChromeTabbedActivityTestRule mChromeTabbedActivityTestRule;
private final Supplier<TabModelSelector> mTabModelSelectorSupplier;
private final boolean mIncognito;
private final String mTabType;
public NoTabsExistCondition(
ChromeTabbedActivityTestRule chromeTabbedActivityTestRule, boolean incognito) {
mChromeTabbedActivityTestRule = chromeTabbedActivityTestRule;
Supplier<TabModelSelector> tabModelSelectorSupplier, boolean incognito) {
mTabModelSelectorSupplier =
dependOnSupplier(tabModelSelectorSupplier, "TabModelSelector");
mIncognito = incognito;
mTabType = incognito ? "incognito" : "regular";
}
@Override
protected ConditionStatus checkWithSuppliers() {
int tabCount = mChromeTabbedActivityTestRule.tabsCount(mIncognito);
int tabCount = mTabModelSelectorSupplier.get().getModel(mIncognito).getCount();
return whether(tabCount == 0, "%d %s tabs", tabCount, mTabType);
}
@ -57,21 +62,21 @@ public class TabModelConditions {
}
private static class AnyTabsExistCondition extends UiThreadCondition {
private final ChromeTabbedActivityTestRule mChromeTabbedActivityTestRule;
private final Supplier<TabModelSelector> mTabModelSelectorSupplier;
private final boolean mIncognito;
private final String mTabType;
public AnyTabsExistCondition(
ChromeTabbedActivityTestRule chromeTabbedActivityTestRule, boolean incognito) {
mChromeTabbedActivityTestRule = chromeTabbedActivityTestRule;
Supplier<TabModelSelector> tabModelSelectorSupplier, boolean incognito) {
mTabModelSelectorSupplier =
dependOnSupplier(tabModelSelectorSupplier, "TabModelSelector");
mIncognito = incognito;
mTabType = incognito ? "incognito" : "regular";
}
@Override
protected ConditionStatus checkWithSuppliers() {
int tabCount = mChromeTabbedActivityTestRule.tabsCount(mIncognito);
int tabCount = mTabModelSelectorSupplier.get().getModel(mIncognito).getCount();
return whether(tabCount > 0, "%d %s tabs", tabCount, mTabType);
}

@ -0,0 +1,44 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.transit;
import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.transit.ConditionStatus;
import org.chromium.base.test.transit.UiThreadCondition;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
/** Condition fulfilled when an initialized TabModel exists. */
public class TabModelSelectorCondition extends UiThreadCondition
implements Supplier<TabModelSelector> {
private final Supplier<ChromeTabbedActivity> mActivitySupplier;
private TabModelSelector mSelector;
public TabModelSelectorCondition(Supplier<ChromeTabbedActivity> activitySupplier) {
mActivitySupplier = activitySupplier;
}
@Override
protected ConditionStatus checkWithSuppliers() throws Exception {
TabModelSelector selector = mActivitySupplier.get().getTabModelSelectorSupplier().get();
if (selector != null) {
mSelector = selector;
return fulfilled();
} else {
return awaiting("Activity has no TabModelSelector");
}
}
@Override
public String buildDescription() {
return null;
}
@Override
public TabModelSelector get() {
return mSelector;
}
}

@ -96,7 +96,8 @@ public abstract class TabSwitcherStation extends Station {
protected final ChromeTabbedActivityTestRule mChromeTabbedActivityTestRule;
protected final boolean mIsIncognito;
private ActivityElement<ChromeTabbedActivity> mActivityElement;
protected ActivityElement<ChromeTabbedActivity> mActivityElement;
protected TabModelSelectorCondition mTabModelSelectorCondition;
/** Instantiate one of the subclasses instead. */
protected TabSwitcherStation(
@ -112,6 +113,8 @@ public abstract class TabSwitcherStation extends Station {
@CallSuper
public void declareElements(Elements.Builder elements) {
mActivityElement = elements.declareActivity(ChromeTabbedActivity.class);
mTabModelSelectorCondition =
elements.declareEnterCondition(new TabModelSelectorCondition(mActivityElement));
elements.declareView(TOOLBAR);
elements.declareView(TOOLBAR_NEW_TAB_BUTTON);