blob: 756f65434351a0f2808e708446ebf13e29982301 [file] [log] [blame]
// Copyright 2019 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.browser.tasks.tab_management;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.action.ViewActions.pressImeActionButton;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.BundleMatchers.hasEntry;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtras;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasType;
import static androidx.test.espresso.matcher.RootMatchers.withDecorView;
import static androidx.test.espresso.matcher.ViewMatchers.isClickable;
import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withParent;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.flags.ChromeFeatureList.ANDROID_HUB;
import static org.chromium.chrome.browser.flags.ChromeFeatureList.ANDROID_TAB_GROUP_STABLE_IDS;
import static org.chromium.chrome.browser.flags.ChromeFeatureList.TAB_GROUP_PARITY_ANDROID;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.addBlankTabs;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.clickFirstCardFromTabSwitcher;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.clickFirstTabInDialog;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.clickNthTabInDialog;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.closeFirstTabInDialog;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.closeNthTabInDialog;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.createTabGroup;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.createTabs;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.enterTabSwitcher;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.finishActivity;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.getSwipeToDismissAction;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.getTabSwitcherAncestorId;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.leaveTabSwitcher;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.mergeAllIncognitoTabsToAGroup;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.mergeAllNormalTabsToAGroup;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.prepareTabsWithThumbnail;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyAllTabsHaveThumbnail;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyTabStripFaviconCount;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.verifyTabSwitcherCardCount;
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.waitForThumbnailsToFetch;
import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.createTabStatesAndMetadataFile;
import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile;
import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.espresso.intent.Intents;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.Callback;
import org.chromium.base.test.params.ParameterAnnotations;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.RequiresRestart;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.app.bookmarks.BookmarkEditActivity;
import org.chromium.chrome.browser.app.bookmarks.BookmarkFolderPickerActivity;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.homepage.HomepagePolicyManager;
import org.chromium.chrome.browser.hub.HubContainerView;
import org.chromium.chrome.browser.hub.HubFieldTrial;
import org.chromium.chrome.browser.layouts.LayoutTestUtils;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.pseudotab.TabAttributeCache;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.chrome.test.util.ActivityTestUtils;
import org.chromium.chrome.test.util.BookmarkTestUtil;
import org.chromium.chrome.test.util.ChromeRenderTestRule;
import org.chromium.chrome.test.util.MenuUtils;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.KeyboardVisibilityDelegate;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.test.util.DeviceRestriction;
import org.chromium.ui.test.util.NightModeTestUtils;
import org.chromium.ui.test.util.UiDisableIf;
import org.chromium.ui.test.util.ViewUtils;
import org.chromium.ui.util.ColorUtils;
import org.chromium.url.GURL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
/** End-to-end tests for TabGridDialog component. */
@RunWith(ParameterizedRunner.class)
@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Restriction({Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@EnableFeatures({ANDROID_HUB})
@DisableFeatures({TAB_GROUP_PARITY_ANDROID})
@Batch(Batch.PER_CLASS)
public class TabGridDialogTest {
private static final String CUSTOMIZED_TITLE1 = "wfh tips";
private static final String CUSTOMIZED_TITLE2 = "wfh funs";
private static final String PAGE_WITH_HTTPS_CANONICAL_URL =
"/chrome/test/data/android/share/link_share_https_canonical.html";
private static final String PAGE_WITH_HTTP_CANONICAL_URL =
"/chrome/test/data/android/share/link_share_http_canonical.html";
private static final ActivityLifecycleMonitor sMonitor =
ActivityLifecycleMonitorRegistry.getInstance();
private boolean mHasReceivedSourceRect;
private TabListEditorTestingRobot mSelectionEditorRobot = new TabListEditorTestingRobot();
private ModalDialogManager mModalDialogManager;
@ClassRule
public static ChromeTabbedActivityTestRule sActivityTestRule =
new ChromeTabbedActivityTestRule();
@Rule
public ChromeRenderTestRule mRenderTestRule =
ChromeRenderTestRule.Builder.withPublicCorpus()
.setBugComponent(
ChromeRenderTestRule.Component.UI_BROWSER_MOBILE_TAB_SWITCHER_GRID)
.setRevision(8)
.build();
// Must force tab re-creation to ensure tab group names make sense.
@Rule
public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
new BlankCTATabInitialStateRule(sActivityTestRule, true);
@Mock private HomepagePolicyManager mHomepagePolicyManager;
@Mock private BrowserControlsStateProvider mBrowserControlsStateProvider;
@BeforeClass
public static void setUpBeforeActivityLaunched() {
ChromeNightModeTestUtils.setUpNightModeBeforeChromeActivityLaunched();
}
@ParameterAnnotations.UseMethodParameterBefore(NightModeTestUtils.NightModeParams.class)
public void setupNightMode(boolean nightModeEnabled) {
TestThreadUtils.runOnUiThreadBlocking(
() -> {
ChromeNightModeTestUtils.setUpNightModeForChromeActivity(nightModeEnabled);
});
mRenderTestRule.setNightModeEnabled(nightModeEnabled);
final ChromeTabbedActivity ctaNightMode =
ActivityTestUtils.waitForActivity(
InstrumentationRegistry.getInstrumentation(), ChromeTabbedActivity.class);
sActivityTestRule.setActivity(ctaNightMode);
CriteriaHelper.pollUiThread(
sActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized);
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Intents.init();
// Some of the tests may finish the activity using moveTaskToBack.
ChromeTabbedActivity.interceptMoveTaskToBackForTesting();
CriteriaHelper.pollUiThread(
sActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized);
mModalDialogManager =
TestThreadUtils.runOnUiThreadBlockingNoException(
sActivityTestRule.getActivity()::getModalDialogManager);
}
@After
public void tearDown() throws Exception {
try {
Intents.release();
} catch (NullPointerException e) {
// This will fail if the ChromeTabbedActivity is already finished.
// IntentsTestRule was created to avoid this, but it is deprecated and hard to integrate
// with batched tests.
}
ActivityTestUtils.clearActivityOrientation(sActivityTestRule.getActivity());
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
if (cta == null) return;
boolean isDestroyed =
TestThreadUtils.runOnUiThreadBlockingNoException(
() -> {
return sMonitor.getLifecycleStageOf(cta) == Stage.DESTROYED;
});
if (isDestroyed) return;
View dialogView = cta.findViewById(R.id.dialog_container_view);
if (dialogView != null) {
if (isDialogFullyVisible(cta)) {
clickScrimToExitDialog(cta);
}
waitForDialogHidingAnimation(cta);
}
dismissAllModalDialogs();
if (cta.getLayoutManager().isLayoutVisible(LayoutType.TAB_SWITCHER)
&& !cta.getLayoutManager().isLayoutStartingToHide(LayoutType.TAB_SWITCHER)) {
if (cta.getTabModelSelectorSupplier().get().getTotalTabCount() == 0) {
addBlankTabs(cta, false, 1);
LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
} else {
leaveTabSwitcher(cta);
}
} else {
LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
}
}
@Test
@MediumTest
public void testBackPressCloseDialog() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog from tab switcher and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
// Press back and dialog should be hidden.
Espresso.pressBack();
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
// Enter first tab page.
assertTrue(cta.getLayoutManager().isLayoutVisible(LayoutType.TAB_SWITCHER));
clickFirstCardFromTabSwitcher(cta);
clickFirstTabInDialog(cta);
waitForDialogHidingAnimation(cta);
if (isPhone()) {
// Open dialog from tab strip and verify dialog is showing correct content.
openDialogFromStripAndVerify(cta, 2, null);
// Press back and dialog should be hidden.
Espresso.pressBack();
waitForDialogHidingAnimation(cta);
}
}
@Test
@MediumTest
public void testBackPressCloseDialogViaGroupStrip() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
}
@Test
@MediumTest
public void testClickScrimCloseDialog() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog from tab switcher and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
// Click scrim view and dialog should be hidden.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
// Enter first tab page.
assertTrue(cta.getLayoutManager().isLayoutVisible(LayoutType.TAB_SWITCHER));
clickFirstCardFromTabSwitcher(cta);
clickFirstTabInDialog(cta);
waitForDialogHidingAnimation(cta);
if (isPhone()) {
// Open dialog from tab strip and verify dialog is showing correct content.
openDialogFromStripAndVerify(cta, 2, null);
// Click scrim view and dialog should be hidden.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
}
// Checkout the scrim view observer is correctly setup by closing dialog in tab switcher
// again.
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(cta, 2, null);
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
}
@Test
@MediumTest
public void testAddTabHidesDialog() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog from tab switcher and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
// Add a tab to hide the group. Any foreground launch type will also hide the tab switcher
// so use a background one to make testing easier.
TestThreadUtils.runOnUiThreadBlocking(
() -> {
Tab parentTab = cta.getTabModelSelector().getModel(false).getTabAt(0);
cta.getCurrentTabCreator()
.createNewTab(
new LoadUrlParams("about:blank"),
"About title",
TabLaunchType.FROM_LONGPRESS_BACKGROUND_IN_GROUP,
parentTab,
TabModel.INVALID_TAB_INDEX);
});
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
}
@Test
@MediumTest
public void testAddTabSkipsHideDialogForSync() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog from tab switcher and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
// Add a tab to hide the group.
TestThreadUtils.runOnUiThreadBlocking(
() -> {
TabModelSelector selector = cta.getTabModelSelector();
Tab destinationTab = selector.getModel(false).getTabAt(0);
Tab tab =
cta.getCurrentTabCreator()
.createNewTab(
new LoadUrlParams("about:blank"),
"About title",
TabLaunchType.FROM_SYNC_BACKGROUND,
null,
TabModel.INVALID_TAB_INDEX);
((TabGroupModelFilter)
selector.getTabModelFilterProvider().getTabModelFilter(false))
.mergeListOfTabsToGroup(
List.of(tab),
destinationTab,
/* isSameGroup= */ false,
/* notify= */ false);
});
CriteriaHelper.pollUiThread(() -> isDialogFullyVisible(cta));
verifyShowingDialog(cta, 3, null);
}
@Test
@MediumTest
@EnableFeatures(TAB_GROUP_PARITY_ANDROID)
@RequiresRestart("Group creation modal dialog is sometimes persistent after dismissing")
public void testTabGroupDialogUi() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
// Verify TabGroupsContinuation related functionality is exposed.
verifyTabGroupDialogUi(cta);
}
@Test
@MediumTest
@DisabledTest(message = "Flaky test - see: https://crbug.com/1177149")
public void testTabGridDialogAnimation() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Add 400px top margin to the recyclerView.
RecyclerView recyclerView = cta.findViewById(R.id.tab_list_recycler_view);
float tabGridCardPadding = TabUiThemeProvider.getTabGridCardMargin(cta);
int deltaTopMargin = 400;
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) recyclerView.getLayoutParams();
params.topMargin += deltaTopMargin;
TestThreadUtils.runOnUiThreadBlocking(() -> recyclerView.setLayoutParams(params));
CriteriaHelper.pollUiThread(() -> !recyclerView.isComputingLayout());
// Calculate expected values of animation source rect.
mHasReceivedSourceRect = false;
View parentView = cta.getCompositorViewHolderForTesting();
Rect parentRect = new Rect();
parentView.getGlobalVisibleRect(parentRect);
Rect sourceRect = new Rect();
recyclerView.getChildAt(0).getGlobalVisibleRect(sourceRect);
// TODO(yuezhanggg): Figure out why the sourceRect.left is wrong after setting the margin.
float expectedTop = sourceRect.top - parentRect.top + tabGridCardPadding;
float expectedWidth = sourceRect.width() - 2 * tabGridCardPadding;
float expectedHeight = sourceRect.height() - 2 * tabGridCardPadding;
// Setup the callback to verify the animation source Rect.
TabGridDialogView.setSourceRectCallbackForTesting(
(result -> {
mHasReceivedSourceRect = true;
assertEquals(expectedTop, result.top, 0.0);
assertEquals(expectedHeight, result.height(), 0.0);
assertEquals(expectedWidth, result.width(), 0.0);
}));
TabUiTestHelper.clickFirstCardFromTabSwitcher(cta);
CriteriaHelper.pollUiThread(() -> mHasReceivedSourceRect);
CriteriaHelper.pollUiThread(() -> isDialogFullyVisible(cta));
}
@Test
@MediumTest
public void testUndoClosureInDialog_DialogUndoBar() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(cta, 2, null);
// Verify close and undo in dialog from tab switcher.
closeFirstTabInDialog();
verifyShowingDialog(cta, 1, null);
verifyDialogUndoBarAndClick();
verifyShowingDialog(cta, 2, null);
if (isPhone()) {
// Verify close and undo in dialog from tab strip.
clickFirstTabInDialog(cta);
openDialogFromStripAndVerify(cta, 2, null);
closeFirstTabInDialog();
verifyShowingDialog(cta, 1, null);
verifyDialogUndoBarAndClick();
verifyShowingDialog(cta, 2, null);
clickScrimToExitDialog(cta);
verifyTabStripFaviconCount(cta, 2);
}
}
@Test
@MediumTest
@EnableFeatures(ANDROID_TAB_GROUP_STABLE_IDS)
public void testTabGroupDialogRemainsOpenOnSyncUpdate() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 3);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 3);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
leaveTabSwitcher(cta);
TabModel model = cta.getTabModelSelector().getModel(false);
addBlankTabs(cta, false, 3);
enterTabSwitcher(cta);
createTabGroup(
cta, false, List.of(model.getTabAt(3), model.getTabAt(4), model.getTabAt(5)));
// Open dialog and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 3, null);
Callback<Integer> closeTabAt =
(index) -> {
model.closeTab(model.getTabAt(index));
};
// Close two tabs in the current group
TestThreadUtils.runOnUiThreadBlocking(closeTabAt.bind(0));
verifyShowingDialog(cta, 2, null);
TestThreadUtils.runOnUiThreadBlocking(closeTabAt.bind(0));
verifyShowingDialog(cta, 1, null);
// Close two tabs in the GTS background group, dialog should still show.
TestThreadUtils.runOnUiThreadBlocking(closeTabAt.bind(1));
verifyShowingDialog(cta, 1, null);
TestThreadUtils.runOnUiThreadBlocking(closeTabAt.bind(1));
verifyShowingDialog(cta, 1, null);
TestThreadUtils.runOnUiThreadBlocking(closeTabAt.bind(1));
verifyShowingDialog(cta, 1, null);
}
@Test
@MediumTest
@EnableFeatures(TAB_GROUP_PARITY_ANDROID)
@RequiresRestart("Group creation modal dialog is sometimes persistent when dismissing")
public void testColorPickerOnIconClick() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
String blueColor =
cta.getString(R.string.accessibility_tab_group_color_picker_color_item_blue);
String notSelectedStringBlue =
cta.getString(
R.string
.accessibility_tab_group_color_picker_color_item_not_selected_description,
blueColor);
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
// Expect the color icon to be clicked.
var histograms =
HistogramWatcher.newBuilder()
.expectIntRecordTimes(
"Android.TabGroupParity.TabGroupColorChangeActionType", 0, 3)
.build();
// Open dialog and click the color icon to show the color picker.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
onView(withId(R.id.tab_group_color_icon)).perform(click());
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(matches(isDisplayed()));
// Select a non default color and assert the pop up closes.
onView(withContentDescription(notSelectedStringBlue)).perform(click());
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(doesNotExist());
// Back press should close the color picker pop up.
onView(withId(R.id.tab_group_color_icon)).perform(click());
Espresso.pressBack();
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(doesNotExist());
// Clicking ScrimView should close the color picker pop up.
onView(withId(R.id.tab_group_color_icon)).perform(click());
clickScrimToExitDialog(cta);
histograms.assertExpected();
}
@Test
@MediumTest
@EnableFeatures(TAB_GROUP_PARITY_ANDROID)
@RequiresRestart("Group creation modal dialog is sometimes persistent when dismissing")
public void testColorPickerOnToolbarMenuItemClick() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
String blueColor =
cta.getString(R.string.accessibility_tab_group_color_picker_color_item_blue);
String notSelectedStringBlue =
cta.getString(
R.string
.accessibility_tab_group_color_picker_color_item_not_selected_description,
blueColor);
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
// Expect the edit color menu item to be clicked.
HistogramWatcher watcher =
HistogramWatcher.newSingleRecordWatcher(
"Android.TabGroupParity.TabGroupColorChangeActionType", 1);
// Open dialog and click the toolbar menu item to show the color picker.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
openDialogToolbarMenuAndVerify(cta);
selectTabGridDialogToolbarMenuItem(cta, "Edit group color");
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(matches(isDisplayed()));
// Select a non default color and assert the pop up closes.
onView(withContentDescription(notSelectedStringBlue)).perform(click());
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(doesNotExist());
clickScrimToExitDialog(cta);
watcher.assertExpected();
}
@Test
@MediumTest
public void testSelectionEditorShowHide() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and open selection editor.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
openSelectionEditorAndVerify(cta, 2);
// Click navigation button should close selection editor but not tab grid dialog.
mSelectionEditorRobot.actionRobot.clickToolbarNavigationButton();
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
assertTrue(isDialogFullyVisible(cta));
// Back press should close both theselectioneditor.
openSelectionEditorAndVerify(cta, 2);
Espresso.pressBack();
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
assertTrue(isDialogFullyVisible(cta));
// Back press again to exit.
Espresso.pressBack();
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
// Clicking ScrimView should close both the dialog and selection editor.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
openSelectionEditorAndVerify(cta, 2);
clickScrimToExitDialog(cta);
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
}
@Test
@MediumTest
@EnableFeatures(TAB_GROUP_PARITY_ANDROID)
@RequiresRestart("Group creation modal dialog is sometimes persistent when dismissing")
public void testDialogToolbarSelectionEditor() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and open selection editor and confirm the share action isn't visible.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
openDialogToolbarMenuAndVerify(cta);
onView(withText("Share group"))
.inRoot(withDecorView(not(cta.getWindow().getDecorView())))
.check(doesNotExist());
Espresso.pressBack();
openSelectionEditorAndVerify(cta, 2);
// Click navigation button should close selection editor but not tab grid dialog.
mSelectionEditorRobot.actionRobot.clickToolbarNavigationButton();
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
assertTrue(isDialogFullyVisible(cta));
// Back press should close only the selection editor.
openSelectionEditorAndVerify(cta, 2);
Espresso.pressBack();
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
assertTrue(isDialogFullyVisible(cta));
// Clicking ScrimView should close both the dialog and selection editor.
openSelectionEditorAndVerify(cta, 2);
clickScrimToExitDialog(cta);
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
}
@Test
@MediumTest
public void testDialogSelectionEditor_LongPressTabAndVerifyNoSelectionOccurs()
throws ExecutionException {
TabUiFeatureUtilities.setTabListEditorLongPressEntryEnabledForTesting(true);
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open the selection editor with longpress.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
onView(
allOf(
withId(R.id.tab_list_recycler_view),
withParent(withId(R.id.dialog_container_view))))
.perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsVisible();
// Verify no selection action occurred to switch the selected tab in the tab model
Criteria.checkThat(
sActivityTestRule.getActivity().getCurrentTabModel().index(), Matchers.is(1));
}
@Test
@MediumTest
public void testDialogSelectionEditor_PostLongPressClickNoSelectionEditor()
throws ExecutionException {
TabUiFeatureUtilities.setTabListEditorLongPressEntryEnabledForTesting(true);
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open the selection editor with longpress.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
onView(
allOf(
withId(R.id.tab_list_recycler_view),
withParent(withId(R.id.dialog_container_view))))
.perform(RecyclerViewActions.actionOnItemAtPosition(0, longClick()));
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsVisible();
Espresso.pressBack();
assertTrue(isDialogFullyVisible(cta));
clickFirstTabInDialog(cta);
waitForDialogHidingAnimation(cta);
// Make sure tab switcher strip (and by extension a tab page) is showing to verify clicking
// the tab worked.
CriteriaHelper.pollUiThread(
() ->
sActivityTestRule
.getActivity()
.getBrowserControlsManager()
.getBottomControlOffset()
== 0);
ViewUtils.waitForVisibleView(
allOf(withId(R.id.toolbar_left_button), isCompletelyDisplayed()));
}
@Test
@MediumTest
public void testDialogSelectionEditor_BookmarkSingleTabView() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
SnackbarManager snackbarManager = cta.getSnackbarManager();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
TestThreadUtils.runOnUiThreadBlocking(() -> snackbarManager.dismissAllSnackbars());
verifyTabSwitcherCardCount(cta, 1);
// Open the selection editor.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
openSelectionEditorAndVerify(cta, 2);
// Bookmark one tab and verify edit snackbar.
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(0)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Bookmark tab");
onViewWaiting(
allOf(
withId(R.id.snackbar_button),
isDescendantOfA(withId(R.id.selectable_list)),
isDisplayed()));
onView(allOf(withId(R.id.snackbar), isDescendantOfA(withId(R.id.bottom_container))))
.check(doesNotExist());
onView(
allOf(
withId(R.id.snackbar_button),
isDescendantOfA(withId(R.id.selectable_list)),
isDisplayed()))
.perform(click());
BookmarkEditActivity activity = BookmarkTestUtil.waitForEditActivity();
activity.finish();
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsVisible();
}
@Test
@MediumTest
public void testDialogSelectionEditor_BookmarkTabsView() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
SnackbarManager snackbarManager = cta.getSnackbarManager();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
TestThreadUtils.runOnUiThreadBlocking(() -> snackbarManager.dismissAllSnackbars());
verifyTabSwitcherCardCount(cta, 1);
// Open the selection editor.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
openSelectionEditorAndVerify(cta, 2);
// Bookmark two tabs and verify edit snackbar.
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(0)
.clickItemAtAdapterPosition(1)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Bookmark tabs");
onViewWaiting(
allOf(
withId(R.id.snackbar_button),
isDescendantOfA(withId(R.id.selectable_list)),
isDisplayed()));
onView(allOf(withId(R.id.snackbar), isDescendantOfA(withId(R.id.bottom_container))))
.check(doesNotExist());
onView(
allOf(
withId(R.id.snackbar_button),
isDescendantOfA(withId(R.id.selectable_list)),
isDisplayed()))
.perform(click());
BookmarkFolderPickerActivity activity = BookmarkTestUtil.waitForFolderPickerActivity();
activity.finish();
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsVisible();
}
@Test
@MediumTest
@RequiresRestart("Share sheet is sometimes persistent when calling pressBack to retract")
public void testDialogSelectionEditor_ShareActionView() throws Exception {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
final String httpsCanonicalUrl =
sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTPS_CANONICAL_URL);
sActivityTestRule.loadUrl(httpsCanonicalUrl);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
openSelectionEditorAndVerify(cta, 2);
// Share tabs
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(1)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Share tab");
CriteriaHelper.pollUiThread(
() ->
Criteria.checkThat(
"Share sheet was not shown.",
sActivityTestRule
.getActivity()
.getRootUiCoordinatorForTesting()
.getBottomSheetController(),
notNullValue()));
intended(
allOf(
hasAction(equalTo(Intent.ACTION_CHOOSER)),
hasExtras(
hasEntry(
equalTo(Intent.EXTRA_INTENT),
allOf(
hasAction(equalTo(Intent.ACTION_SEND)),
hasType("text/plain"))))));
}
@Test
@MediumTest
@RequiresRestart("Share sheet is sometimes persistent when calling pressBack to retract")
public void testDialogSelectionEditor_ShareActionTabs() throws Exception {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
final String httpsCanonicalUrl =
sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTPS_CANONICAL_URL);
sActivityTestRule.loadUrlInNewTab(httpsCanonicalUrl);
final String httpCanonicalUrl =
sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTP_CANONICAL_URL);
sActivityTestRule.loadUrlInNewTab(httpCanonicalUrl);
ArrayList<String> urls = new ArrayList<String>();
urls.add(httpsCanonicalUrl);
urls.add(httpCanonicalUrl);
for (int i = 0; i < urls.size(); i++) {
urls.set(i, (i + 1) + ". " + urls.get(i));
}
urls.add("");
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 3);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 3, null);
openSelectionEditorAndVerify(cta, 3);
TabListEditorShareAction.setIntentCallbackForTesting(
(result -> {
assertEquals(Intent.ACTION_SEND, result.getAction());
assertEquals(String.join("\n", urls), result.getStringExtra(Intent.EXTRA_TEXT));
assertEquals("text/plain", result.getType());
assertEquals("2 links from Chrome", result.getStringExtra(Intent.EXTRA_TITLE));
}));
// Share tabs
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(1)
.clickItemAtAdapterPosition(2)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Share tabs");
}
@Test
@MediumTest
public void testDialogSelectionEditor_ShareActionAllFilterableTabs() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
openSelectionEditorAndVerify(cta, 2);
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(0)
.clickItemAtAdapterPosition(1)
.clickToolbarMenuButton();
// Check share tabs disabled
onView(withText("Share tabs")).check(matches(not(isClickable())));
}
@Test
@MediumTest
public void testDialogSelectionEditor_UndoClose() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 4);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 4);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open the selection editor.
openDialogFromTabSwitcherAndVerify(cta, 4, null);
openSelectionEditorAndVerify(cta, 4);
// Close two tabs and undo.
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(0)
.clickItemAtAdapterPosition(2)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Close tabs");
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
verifyShowingDialog(cta, 2, null);
verifyDialogUndoBarAndClick();
verifyShowingDialog(cta, 4, null);
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
}
@Test
@MediumTest
public void testDialogSelectionEditor_UndoCloseAll() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 4);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 4);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open the selection editor.
openDialogFromTabSwitcherAndVerify(cta, 4, null);
openSelectionEditorAndVerify(cta, 4);
// Close two tabs and undo.
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(0)
.clickItemAtAdapterPosition(1)
.clickItemAtAdapterPosition(2)
.clickItemAtAdapterPosition(3)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Close tabs");
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 0);
verifyGlobalUndoBarAndClick();
verifyTabSwitcherCardCount(cta, 1);
}
@Test
@MediumTest
// Swipe action Fails with espresso 3.2. b/329707221
@DisableIf.Build(sdk_is_greater_than = android.os.Build.VERSION_CODES.S)
public void testSwipeToDismiss_Dialog() {
ChromeTabbedActivity cta = sActivityTestRule.getActivity();
// Create 2 tabs and merge them into one group.
createTabs(cta, false, 2);
enterTabSwitcher(cta);
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(cta, 2, null);
// Swipe to dismiss two tabs in dialog.
onView(
allOf(
withId(R.id.tab_list_recycler_view),
withParent(withId(R.id.dialog_container_view))))
.perform(
RecyclerViewActions.actionOnItemAtPosition(
1, getSwipeToDismissAction(true)));
verifyShowingDialog(cta, 1, null);
onView(
allOf(
withId(R.id.tab_list_recycler_view),
withParent(withId(R.id.dialog_container_view))))
.perform(
RecyclerViewActions.actionOnItemAtPosition(
0, getSwipeToDismissAction(false)));
waitForDialogHidingAnimation(cta);
verifyTabSwitcherCardCount(cta, 0);
}
@Test
@MediumTest
@Restriction(DeviceRestriction.RESTRICTION_TYPE_NON_AUTO)
public void testSelectionEditorPosition() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
// Position in portrait mode.
ActivityTestUtils.rotateActivityToOrientation(cta, Configuration.ORIENTATION_PORTRAIT);
View parentView = cta.getCompositorViewHolderForTesting();
createTabs(cta, false, 3);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 3);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Verify the size and position of TabGridDialog in portrait mode.
openDialogFromTabSwitcherAndVerify(cta, 3, null);
checkPosition(cta, true, true);
// Verify the size and position of TabListEditor in portrait mode.
openSelectionEditorAndVerify(cta, 3);
checkPosition(cta, false, true);
// Verify the size and position of TabListEditor in landscape mode.
ActivityTestUtils.rotateActivityToOrientation(cta, Configuration.ORIENTATION_LANDSCAPE);
CriteriaHelper.pollUiThread(() -> parentView.getHeight() < parentView.getWidth());
checkPosition(cta, false, false);
// Verify the size and position of TabGridDialog in landscape mode.
mSelectionEditorRobot.actionRobot.clickToolbarNavigationButton();
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
assertTrue(isDialogFullyVisible(cta));
checkPosition(cta, true, false);
// Verify the positioning in multi-window mode. Adjusting the height of the root view to
// mock entering/exiting multi-window mode.
ActivityTestUtils.rotateActivityToOrientation(cta, Configuration.ORIENTATION_PORTRAIT);
CriteriaHelper.pollUiThread(() -> parentView.getHeight() > parentView.getWidth());
View rootView = cta.findViewById(R.id.coordinator);
int rootViewHeight = rootView.getHeight();
TestThreadUtils.runOnUiThreadBlocking(
() -> {
ViewGroup.LayoutParams params = rootView.getLayoutParams();
params.height = rootViewHeight / 2;
rootView.setLayoutParams(params);
});
checkPosition(cta, true, true);
openSelectionEditorAndVerify(cta, 3);
checkPosition(cta, false, true);
TestThreadUtils.runOnUiThreadBlocking(
() -> {
ViewGroup.LayoutParams params = rootView.getLayoutParams();
params.height = rootViewHeight;
rootView.setLayoutParams(params);
});
checkPosition(cta, false, true);
checkPosition(cta, true, true);
}
@Test
@MediumTest
public void testTabGroupNaming() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and modify group title.
openDialogFromTabSwitcherAndVerify(
cta,
2,
cta.getResources()
.getQuantityString(R.plurals.bottom_tab_grid_title_placeholder, 2, 2));
editDialogTitle(cta, CUSTOMIZED_TITLE1);
// Verify the title is updated in both tab switcher and dialog.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
verifyFirstCardTitle(CUSTOMIZED_TITLE1);
openDialogFromTabSwitcherAndVerify(cta, 2, CUSTOMIZED_TITLE1);
if (isPhone()) {
// Modify title in dialog from tab strip.
clickFirstTabInDialog(cta);
openDialogFromStripAndVerify(cta, 2, CUSTOMIZED_TITLE1);
editDialogTitle(cta, CUSTOMIZED_TITLE2);
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
enterTabSwitcher(cta);
verifyFirstCardTitle(CUSTOMIZED_TITLE2);
}
}
@Test
@MediumTest
@EnableFeatures(TAB_GROUP_PARITY_ANDROID)
@RequiresRestart("Group creation modal dialog is sometimes persistent when dismissing")
public void testTabGroupNaming_KeyboardVisibility() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(
cta,
2,
cta.getResources()
.getQuantityString(R.plurals.bottom_tab_grid_title_placeholder, 2, 2));
// Test title text focus in dialog in tab switcher.
testTitleTextFocus(cta);
// Test title text focus in dialog from tab strip.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
clickFirstTabInDialog(cta);
waitForDialogHidingAnimation(cta);
if (isPhone()) {
openDialogFromStripAndVerify(cta, 2, null);
testTitleTextFocus(cta);
}
}
// Regression test for https://crbug.com/1419842
@Test
@MediumTest
public void testTabGroupNaming_afterFocusNoTitleSaved() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 3);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 3);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(
cta,
3,
cta.getResources()
.getQuantityString(R.plurals.bottom_tab_grid_title_placeholder, 3, 3));
// Click on the title this should not save the title.
onView(allOf(withParent(withId(R.id.main_content)), withId(R.id.title))).perform(click());
verifyTitleTextFocus(cta, true);
Espresso.pressBack();
verifyTitleTextFocus(cta, false);
verifyShowingDialog(cta, 3, null);
// Close a tab and exit dialog.
closeFirstTabInDialog();
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
// Verify the default title updated.
verifyTabSwitcherCardCount(cta, 1);
String twoTabsString =
cta.getResources()
.getQuantityString(R.plurals.bottom_tab_grid_title_placeholder, 2, 2);
verifyFirstCardTitle(twoTabsString);
openDialogFromTabSwitcherAndVerify(cta, 2, twoTabsString);
// Click on the title.
onView(allOf(withParent(withId(R.id.main_content)), withId(R.id.title))).perform(click());
verifyTitleTextFocus(cta, true);
Espresso.pressBack();
verifyTitleTextFocus(cta, false);
verifyShowingDialog(cta, 2, null);
// Confirm actually changing the title works.
editDialogTitle(cta, CUSTOMIZED_TITLE1);
// Verify the title is updated in both tab switcher and dialog.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
verifyFirstCardTitle(CUSTOMIZED_TITLE1);
openDialogFromTabSwitcherAndVerify(cta, 2, CUSTOMIZED_TITLE1);
}
// Regression test for https://crbug.com/1378226.
@Test
@MediumTest
public void testTabGroupNaming_afterMergeWithSelectionEditor() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 4);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 4);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and modify group title.
openDialogFromTabSwitcherAndVerify(
cta,
4,
cta.getResources()
.getQuantityString(R.plurals.bottom_tab_grid_title_placeholder, 4, 4));
editDialogTitle(cta, CUSTOMIZED_TITLE1);
// Verify the title is updated in both tab switcher and dialog.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
verifyFirstCardTitle(CUSTOMIZED_TITLE1);
openDialogFromTabSwitcherAndVerify(cta, 4, CUSTOMIZED_TITLE1);
openSelectionEditorAndVerify(cta, 4);
// Ungroup tab.
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(1)
.clickItemAtAdapterPosition(2)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Ungroup tabs");
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
// Verify the ungroup occurred.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
verifyFirstCardTitle(CUSTOMIZED_TITLE1);
verifyTabSwitcherCardCount(cta, 3);
enterTabListEditor(cta);
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsVisible();
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(0)
.clickItemAtAdapterPosition(1)
.clickItemAtAdapterPosition(2)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Group tabs");
mSelectionEditorRobot.resultRobot.verifyTabListEditorIsHidden();
// Verify the group worked and the title remained.
verifyFirstCardTitle(CUSTOMIZED_TITLE1);
openDialogFromTabSwitcherAndVerify(cta, 4, CUSTOMIZED_TITLE1);
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
}
@Test
@MediumTest
@DisableIf.Build(
sdk_is_greater_than = VERSION_CODES.N_MR1,
message = "https://crbug.com/1124336")
@DisableIf.Build(supported_abis_includes = "x86", message = "https://crbug.com/1124336")
@DisableIf.Device(type = UiDisableIf.TABLET)
public void testDialogInitialShowFromStrip() throws Exception {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
prepareTabsWithThumbnail(sActivityTestRule, 2, 0, "about:blank");
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Restart the activity and open the dialog from strip to check the initial setup of dialog.
finishActivity(cta);
sActivityTestRule.startMainActivityFromLauncher();
CriteriaHelper.pollUiThread(
sActivityTestRule.getActivity().getTabModelSelector()::isTabStateInitialized);
openDialogFromStripAndVerify(sActivityTestRule.getActivity(), 2, null);
closeNthTabInDialog(0);
verifyShowingDialog(cta, 1, null);
}
@Test
@MediumTest
@Feature({"RenderTest"})
@EnableFeatures({TAB_GROUP_PARITY_ANDROID})
@RequiresRestart("Group creation modal dialog is sometimes persistent when dismissing")
@ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
public void testRenderDialog_3Tabs_Portrait(boolean nightModeEnabled) throws Exception {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
prepareTabsWithThumbnail(sActivityTestRule, 3, 0, "about:blank");
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 3);
waitForThumbnailsToFetch(getRecyclerView(cta));
verifyAllTabsHaveThumbnail(cta.getCurrentTabModel());
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(cta, 3, null);
View dialogView = cta.findViewById(R.id.dialog_parent_view);
waitForThumbnailsToFetch(
(RecyclerView) dialogView.findViewById(R.id.tab_list_recycler_view));
mRenderTestRule.render(dialogView, "3_tabs_portrait");
}
@Test
@MediumTest
@Feature({"RenderTest"})
@EnableFeatures({TAB_GROUP_PARITY_ANDROID})
@RequiresRestart("Group creation modal dialog is sometimes persistent when dismissing")
@ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
public void testRenderDialog_3Tabs_Landscape_NewAspectRatio(boolean nightModeEnabled)
throws Exception {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
prepareTabsWithThumbnail(sActivityTestRule, 3, 0, "about:blank");
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 3);
waitForThumbnailsToFetch(getRecyclerView(cta));
verifyAllTabsHaveThumbnail(cta.getCurrentTabModel());
// Rotate to landscape mode and create a tab group.
ActivityTestUtils.rotateActivityToOrientation(cta, Configuration.ORIENTATION_LANDSCAPE);
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(cta, 3, null);
View dialogView = cta.findViewById(R.id.dialog_parent_view);
waitForThumbnailsToFetch(
(RecyclerView) dialogView.findViewById(R.id.tab_list_recycler_view));
mRenderTestRule.render(dialogView, "3_tabs_landscape_new_aspect_ratio");
}
@Test
@MediumTest
@Feature({"RenderTest"})
@EnableFeatures({TAB_GROUP_PARITY_ANDROID})
@RequiresRestart("Group creation modal dialog is sometimes persistent when dismissing")
@ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
public void testRenderDialog_5Tabs_InitialScroll(boolean nightModeEnabled) throws Exception {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
prepareTabsWithThumbnail(sActivityTestRule, 5, 0, "about:blank");
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 5);
waitForThumbnailsToFetch(getRecyclerView(cta));
verifyAllTabsHaveThumbnail(cta.getCurrentTabModel());
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(cta, 5, null);
// Select the last tab and reopen the dialog. Verify that the dialog has scrolled to the
// correct position.
clickNthTabInDialog(cta, 4);
enterTabSwitcher(cta);
openDialogFromTabSwitcherAndVerify(cta, 5, null);
View dialogView = cta.findViewById(R.id.dialog_parent_view);
waitForThumbnailsToFetch(
(RecyclerView) dialogView.findViewById(R.id.tab_list_recycler_view));
mRenderTestRule.render(dialogView, "5_tabs_select_last");
}
@Test
@MediumTest
@Feature({"RenderTest"})
@EnableFeatures(TAB_GROUP_PARITY_ANDROID)
@DisableFeatures({ChromeFeatureList.LOGO_POLISH})
@RequiresRestart("Group creation modal dialog is sometimes persistent when dismissing")
@ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
public void testRenderDialog_TabGroupColorChange(boolean nightModeEnabled) throws Exception {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
String blueColor =
cta.getString(R.string.accessibility_tab_group_color_picker_color_item_blue);
String notSelectedStringBlue =
cta.getString(
R.string
.accessibility_tab_group_color_picker_color_item_not_selected_description,
blueColor);
String redColor =
cta.getString(R.string.accessibility_tab_group_color_picker_color_item_red);
String notSelectedStringRed =
cta.getString(
R.string
.accessibility_tab_group_color_picker_color_item_not_selected_description,
redColor);
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyGroupCreationDialogOpenedAndDismiss();
verifyTabSwitcherCardCount(cta, 1);
// Open dialog and click the color icon to show the color picker.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
onView(withId(R.id.tab_group_color_icon)).perform(click());
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(matches(isDisplayed()));
// Select a non default color and assert the pop up closes.
onView(withContentDescription(notSelectedStringBlue)).perform(click());
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(doesNotExist());
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
View dialogView = cta.findViewById(R.id.dialog_parent_view);
waitForThumbnailsToFetch(getRecyclerView(cta));
// Take the GTS first snapshot, which should have the second color (blue) shown.
mRenderTestRule.render(getRecyclerView(cta), "GTS_tab_group_color_initial");
openDialogFromTabSwitcherAndVerify(cta, 2, null);
waitForThumbnailsToFetch(
(RecyclerView) dialogView.findViewById(R.id.tab_list_recycler_view));
// Take the dialog first snapshot, which should have the second color (blue) shown.
mRenderTestRule.render(dialogView, "dialog_tab_group_color_initial");
onView(withId(R.id.tab_group_color_icon)).perform(click());
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(matches(isDisplayed()));
// Select a non default color and assert the pop up closes.
onView(withContentDescription(notSelectedStringRed)).perform(click());
onView(
allOf(
instanceOf(TabGroupColorPickerContainer.class),
withId(R.id.color_picker_container)))
.check(doesNotExist());
waitForThumbnailsToFetch(
(RecyclerView) dialogView.findViewById(R.id.tab_list_recycler_view));
// Take the dialog second snapshot, which should have the third color (red) shown.
mRenderTestRule.render(dialogView, "dialog_tab_group_color_changed");
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
waitForThumbnailsToFetch(getRecyclerView(cta));
// Take the GTS second snapshot, which should have the third color (red) shown.
mRenderTestRule.render(getRecyclerView(cta), "GTS_tab_group_color_changed");
}
@Test
@MediumTest
public void testAdjustBackGroundViewAccessibilityImportance() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Verify accessibility importance adjustment when opening dialog from tab switcher.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
verifyBackgroundViewAccessibilityImportance(cta, true);
Espresso.pressBack();
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyBackgroundViewAccessibilityImportance(cta, false);
// Verify accessibility importance adjustment when opening dialog from tab strip.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
clickFirstTabInDialog(cta);
waitForDialogHidingAnimation(cta);
if (isPhone()) {
openDialogFromStripAndVerify(cta, 2, null);
verifyBackgroundViewAccessibilityImportance(cta, true);
Espresso.pressBack();
waitForDialogHidingAnimation(cta);
}
verifyBackgroundViewAccessibilityImportance(cta, false);
}
@Test
@MediumTest
@DisabledTest(message = "TODO(crbug.com/40148943): Fix flakiness.")
public void testAccessibilityString() throws ExecutionException {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 3);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 3);
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Verify the initial group card content description.
RecyclerView recyclerView = cta.findViewById(R.id.tab_list_recycler_view);
View firstItem = recyclerView.findViewHolderForAdapterPosition(0).itemView;
String expandTargetString = "Expand tab group with 3 tabs.";
assertEquals(expandTargetString, firstItem.getContentDescription());
// Back button content description should update with group title.
String collapseTargetString = "Collapse tab group with 3 tabs.";
openDialogFromTabSwitcherAndVerify(cta, 3, null);
verifyDialogBackButtonContentDescription(cta, collapseTargetString);
editDialogTitle(cta, CUSTOMIZED_TITLE1);
collapseTargetString = "Collapse " + CUSTOMIZED_TITLE1 + " tab group with 3 tabs.";
verifyDialogBackButtonContentDescription(cta, collapseTargetString);
// Group card content description should update with group title.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
verifyFirstCardTitle(CUSTOMIZED_TITLE1);
expandTargetString = "Expand " + CUSTOMIZED_TITLE1 + " tab group with 3 tabs.";
assertEquals(expandTargetString, firstItem.getContentDescription());
// Verify the TabSwitcher group card close button content description should update with
// group title.
View closeButton = firstItem.findViewById(R.id.action_button);
String closeButtonTargetString = "Close " + CUSTOMIZED_TITLE1 + " group with 3 tabs";
assertEquals(closeButtonTargetString, closeButton.getContentDescription());
// Back button content description should update with group count change.
openDialogFromTabSwitcherAndVerify(cta, 3, CUSTOMIZED_TITLE1);
closeFirstTabInDialog();
verifyShowingDialog(cta, 2, CUSTOMIZED_TITLE1);
collapseTargetString = "Collapse " + CUSTOMIZED_TITLE1 + " tab group with 2 tabs.";
verifyDialogBackButtonContentDescription(cta, collapseTargetString);
// Group card content description should update with group count change.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
expandTargetString = "Expand " + CUSTOMIZED_TITLE1 + " tab group with 2 tabs.";
assertEquals(expandTargetString, firstItem.getContentDescription());
// TabSwitcher group card Close button content description should update with group count
// change.
closeButtonTargetString = "Close " + CUSTOMIZED_TITLE1 + " group with 2 tabs";
assertEquals(closeButtonTargetString, closeButton.getContentDescription());
// Back button content description should restore when the group loses customized title.
openDialogFromTabSwitcherAndVerify(cta, 2, CUSTOMIZED_TITLE1);
editDialogTitle(cta, "");
verifyShowingDialog(cta, 2, null);
collapseTargetString = "Collapse tab group with 2 tabs.";
verifyDialogBackButtonContentDescription(cta, collapseTargetString);
// Back button content description should update when the group becomes a single tab.
closeFirstTabInDialog();
verifyShowingDialog(cta, 1, "1 tab");
collapseTargetString = "Collapse 1 tab.";
verifyDialogBackButtonContentDescription(cta, collapseTargetString);
// Group card content description should restore when the group becomes a single tab.
clickScrimToExitDialog(cta);
waitForDialogHidingAnimationInTabSwitcher(cta);
assertEquals(null, firstItem.getContentDescription());
// TabSwitcher Group card Close button content description should restore when the group
// becomes a single tab.
closeButtonTargetString = "Close New tab tab";
assertEquals(closeButtonTargetString, closeButton.getContentDescription());
}
@Test
@MediumTest
@DisableIf.Device(type = UiDisableIf.TABLET)
public void testStripDialog_TabListEditorCloseAll_NoCustomHomepage() throws Exception {
ChromeTabbedActivity cta = sActivityTestRule.getActivity();
// Create a tab group with 2 tabs.
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Enter tab switcher and select first tab.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
clickFirstTabInDialog(cta);
waitForDialogHidingAnimation(cta);
// Make sure tab strip is showing.
CriteriaHelper.pollUiThread(
() ->
sActivityTestRule
.getActivity()
.getBrowserControlsManager()
.getBottomControlOffset()
== 0);
ViewUtils.waitForVisibleView(
allOf(withId(R.id.toolbar_left_button), isCompletelyDisplayed()));
// Test opening dialog from strip and from tab switcher.
openDialogFromStripAndVerify(cta, 2, null);
openSelectionEditorAndVerify(cta, 2);
// Close two tabs.
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(0)
.clickItemAtAdapterPosition(1)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Close tabs");
// Rather than destroying the activity the GTS should be showing.
LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.TAB_SWITCHER);
verifyTabSwitcherCardCount(cta, 0);
}
@Test
@MediumTest
@DisableIf.Device(type = UiDisableIf.TABLET)
public void testStripDialog_TabListEditorCloseAll_CustomHomepage() throws Exception {
GURL url =
new GURL(
sActivityTestRule
.getEmbeddedTestServerRule()
.getServer()
.getURL("/chrome/test/data/android/google.html"));
when(mHomepagePolicyManager.isHomepageLocationPolicyEnabled()).thenReturn(true);
when(mHomepagePolicyManager.getHomepagePreference()).thenReturn(url);
HomepagePolicyManager.setInstanceForTests(mHomepagePolicyManager);
ChromeTabbedActivity cta = sActivityTestRule.getActivity();
// Create a tab group with 2 tabs.
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Enter tab switcher and select first tab.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
clickFirstTabInDialog(cta);
waitForDialogHidingAnimation(cta);
// Make sure tab strip is showing.
CriteriaHelper.pollUiThread(
() ->
sActivityTestRule
.getActivity()
.getBrowserControlsManager()
.getBottomControlOffset()
== 0);
ViewUtils.waitForVisibleView(
allOf(withId(R.id.toolbar_left_button), isCompletelyDisplayed()));
// Test opening dialog from strip and from tab switcher.
openDialogFromStripAndVerify(cta, 2, null);
openSelectionEditorAndVerify(cta, 2);
// Close two tabs.
mSelectionEditorRobot
.actionRobot
.clickItemAtAdapterPosition(0)
.clickItemAtAdapterPosition(1)
.clickToolbarMenuButton()
.clickToolbarMenuItem("Close tabs");
// With a custom homepage exit the app.
CriteriaHelper.pollUiThread(() -> cta.isDestroyed());
}
@Test
@MediumTest
@DisableIf.Device(type = UiDisableIf.TABLET)
public void testDialogSetup_WithStartSurface() throws Exception {
// Create a tab group with 2 tabs.
finishActivity(sActivityTestRule.getActivity());
createThumbnailBitmapAndWriteToFile(0, mBrowserControlsStateProvider);
createThumbnailBitmapAndWriteToFile(1, mBrowserControlsStateProvider);
TabAttributeCache.setRootIdForTesting(0, 0);
TabAttributeCache.setRootIdForTesting(1, 0);
createTabStatesAndMetadataFile(new int[] {0, 1});
// Restart Chrome and make sure tab strip is showing.
sActivityTestRule.startMainActivityFromLauncher();
ChromeTabbedActivity cta = sActivityTestRule.getActivity();
CriteriaHelper.pollUiThread(cta.getTabModelSelector()::isTabStateInitialized);
CriteriaHelper.pollUiThread(
() ->
sActivityTestRule
.getActivity()
.getBrowserControlsManager()
.getBottomControlOffset()
== 0);
ViewUtils.waitForVisibleView(
allOf(withId(R.id.toolbar_left_button), isCompletelyDisplayed()));
// Test opening dialog from strip and from tab switcher.
openDialogFromStripAndVerify(cta, 2, null);
Espresso.pressBack();
// Tab switcher is created, and a fake signal to hide dialog is sent. This line would
// crash if the fake signal is not properly handled. See crbug.com/1096358.
enterTabSwitcher(cta);
onView(
allOf(
isDescendantOfA(
withId(
getTabSwitcherAncestorId(
sActivityTestRule.getActivity()))),
withId(R.id.tab_list_recycler_view)))
.perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
CriteriaHelper.pollUiThread(() -> isDialogFullyVisible(sActivityTestRule.getActivity()));
verifyShowingDialog(cta, 2, null);
}
@Test
@MediumTest
public void testCreateTabInDialog() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
createTabs(cta, false, 2);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 2);
// Create a tab group.
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog from tab switcher and verify dialog is showing correct content.
openDialogFromTabSwitcherAndVerify(cta, 2, null);
// Create a tab by tapping "+" on the dialog.
onView(
allOf(
withId(R.id.toolbar_right_button),
isDescendantOfA(withId(R.id.dialog_container_view))))
.perform(click());
waitForDialogHidingAnimation(cta);
enterTabSwitcher(cta);
verifyTabSwitcherCardCount(cta, 1);
openDialogFromTabSwitcherAndVerify(cta, 3, null);
// Enter first tab page.
clickFirstTabInDialog(cta);
waitForDialogHidingAnimation(cta);
if (isPhone()) {
// Open dialog from tab strip and verify dialog is showing correct content.
openDialogFromStripAndVerify(cta, 3, null);
// Create a tab by tapping "+" on the dialog.
onView(
allOf(
withId(R.id.toolbar_right_button),
isDescendantOfA(withId(R.id.dialog_container_view))))
.perform(click());
waitForDialogHidingAnimation(cta);
LayoutTestUtils.waitForLayout(cta.getLayoutManager(), LayoutType.BROWSING);
openDialogFromStripAndVerify(cta, 4, null);
}
}
@Test
@MediumTest
@EnableFeatures({ChromeFeatureList.DATA_SHARING_ANDROID})
public void testDataSharing() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
// Create a tab group.
createTabs(cta, false, 2);
enterTabSwitcher(cta);
mergeAllNormalTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog from tab switcher and verify dialog is showing data sharing bar.
openDialogFromTabSwitcherAndVerifyDataSharing(cta, true);
}
@Test
@MediumTest
@EnableFeatures({ChromeFeatureList.DATA_SHARING_ANDROID})
public void testDataSharingIncognitoMode() {
final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
// Create an incognito tab group.
createTabs(cta, true, 2);
enterTabSwitcher(cta);
mergeAllIncognitoTabsToAGroup(cta);
verifyTabSwitcherCardCount(cta, 1);
// Open dialog from tab switcher and verify dialog is not showing data sharing bar.
openDialogFromTabSwitcherAndVerifyDataSharing(cta, false);
}
private void openDialogFromTabSwitcherAndVerifyDataSharing(
ChromeTabbedActivity cta, boolean shouldShow) {
clickFirstCardFromTabSwitcher(cta);
onView(
allOf(
withId(R.id.dialog_data_sharing_group_bar),
withParent(withId(R.id.dialog_container_view))))
.check(
(v, noMatchException) -> {
if (!shouldShow) {
assertNotNull(noMatchException);
} else {
assertNotNull(v);
assertEquals(v.getVisibility(), View.VISIBLE);
}
});
}
private void openDialogFromTabSwitcherAndVerify(
ChromeTabbedActivity cta, int tabCount, String customizedTitle) {
clickFirstCardFromTabSwitcher(cta);
CriteriaHelper.pollUiThread(() -> isDialogFullyVisible(cta));
verifyShowingDialog(cta, tabCount, customizedTitle);
}
private void openDialogFromStripAndVerify(
ChromeTabbedActivity cta, int tabCount, String customizedTitle) {
showDialogFromStrip(cta);
CriteriaHelper.pollUiThread(() -> isDialogFullyVisible(cta));
verifyShowingDialog(cta, tabCount, customizedTitle);
}
private void verifyShowingDialog(
ChromeTabbedActivity cta, int tabCount, String customizedTitle) {
onView(
allOf(
withId(R.id.tab_list_recycler_view),
withParent(withId(R.id.dialog_container_view))))
.check(matches(isDisplayed()))
.check(TabUiTestHelper.ChildrenCountAssertion.havingTabCount(tabCount));
// Check contents within dialog.
onView(allOf(withParent(withId(R.id.main_content)), withId(R.id.title)))
.check(
(v, noMatchException) -> {
if (noMatchException != null) throw noMatchException;
Assert.assertTrue(v instanceof EditText);
EditText titleText = (EditText) v;
String title =
customizedTitle == null
? cta.getResources()
.getQuantityString(
R.plurals
.bottom_tab_grid_title_placeholder,
tabCount,
tabCount)
: customizedTitle;
Assert.assertEquals(title, titleText.getText().toString());
assertFalse(v.isFocused());
});
// Check views used for animations are not visible.
onView(allOf(withParent(withId(R.id.dialog_parent_view)), withId(R.id.dialog_frame)))
.check((v, e) -> assertEquals(0f, v.getAlpha(), 0.0));
onView(
allOf(
withParent(withId(R.id.dialog_parent_view)),
withId(R.id.dialog_animation_card_view)))
.check((v, e) -> assertEquals(0f, v.getAlpha(), 0.0));
// For devices with version higher or equal to O_MR1 and use light color navigation bar,
// make sure that the color of navigation bar is changed by dialog scrim.
Resources resources = cta.getResources();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1
|| !resources.getBoolean(R.bool.window_light_navigation_bar)
|| isTablet(cta)) {
return;
}
@ColorInt int scrimDefaultColor = cta.getColor(R.color.default_scrim_color);
@ColorInt int navigationBarColor = SemanticColorUtils.getBottomSystemNavColor(cta);
@ColorInt
int navigationBarColorWithScrimOverlay =
ColorUtils.overlayColor(navigationBarColor, scrimDefaultColor);
assertEquals(cta.getWindow().getNavigationBarColor(), navigationBarColorWithScrimOverlay);
assertNotEquals(navigationBarColor, navigationBarColorWithScrimOverlay);
}
private boolean isPhone() {
return !isTablet(sActivityTestRule.getActivity());
}
private boolean isDialogFullyVisible(ChromeTabbedActivity cta) {
View dialogView = cta.findViewById(R.id.dialog_parent_view);
View dialogContainerView = cta.findViewById(R.id.dialog_container_view);
return dialogView.getVisibility() == View.VISIBLE && dialogContainerView.getAlpha() == 1f;
}
private boolean isDialogHidden(ChromeTabbedActivity cta) {
View dialogView = cta.findViewById(R.id.dialog_parent_view);
return dialogView.getVisibility() == View.GONE;
}
private void showDialogFromStrip(ChromeTabbedActivity cta) {
assertFalse(cta.getLayoutManager().isLayoutVisible(LayoutType.TAB_SWITCHER));
onView(
allOf(
withId(R.id.toolbar_left_button),
isDescendantOfA(withId(R.id.bottom_controls))))
.perform(click());
}
private void verifyTabGroupDialogUi(ChromeTabbedActivity cta) {
// Verify the menu button exists.
onView(withId(R.id.toolbar_menu_button)).check(matches(isDisplayed()));
// Verify the color icon exists.
onView(withId(R.id.tab_group_color_icon)).check(matches(isDisplayed()));
// Try to grab focus of the title text field by clicking on it.
onView(allOf(withParent(withId(R.id.main_content)), withId(R.id.title))).perform(click());
onView(allOf(withParent(withId(R.id.main_content)), withId(R.id.title)))
.check(
(v, noMatchException) -> {
if (noMatchException != null) throw noMatchException;
// Verify if we can grab focus on the editText or not.
assertTrue(v.isFocused());
});
// Verify if the keyboard shows or not.
CriteriaHelper.pollUiThread(
() ->
KeyboardVisibilityDelegate.getInstance()
.isKeyboardShowing(cta, cta.getCompositorViewHolderForTesting()));
}
private void openDialogToolbarMenuAndVerify(ChromeTabbedActivity cta) {
onView(withId(R.id.toolbar_menu_button)).perform(click());
onView(withId(R.id.tab_switcher_action_menu_list))
.inRoot(withDecorView(not(cta.getWindow().getDecorView())))
.check(
(v, noMatchException) -> {
if (noMatchException != null) throw noMatchException;
Assert.assertTrue(v instanceof ListView);
ListView listView = (ListView) v;
verifyTabGridDialogToolbarMenuItem(
listView, 0, cta.getString(R.string.menu_select_tabs));
verifyTabGridDialogToolbarMenuItem(
listView,
1,
cta.getString(
R.string.tab_grid_dialog_toolbar_edit_group_name));
verifyTabGridDialogToolbarMenuItem(
listView,
2,
cta.getString(
R.string.tab_grid_dialog_toolbar_edit_group_color));
assertEquals(3, listView.getCount());
});
}
private void verifyTabGridDialogToolbarMenuItem(ListView listView, int index, String text) {
View menuItemView = listView.getChildAt(index);
TextView menuItemText = menuItemView.findViewById(R.id.menu_item_text);
assertEquals(text, menuItemText.getText());
}
private void selectTabGridDialogToolbarMenuItem(ChromeTabbedActivity cta, String buttonText) {
onView(withText(buttonText))
.inRoot(withDecorView(not(cta.getWindow().getDecorView())))
.perform(click());
}
private void waitForDialogHidingAnimation(ChromeTabbedActivity cta) {
CriteriaHelper.pollUiThread(
() -> {
Criteria.checkThat(isDialogHidden(cta), Matchers.is(true));
});
}
private void waitForDialogHidingAnimationInTabSwitcher(ChromeTabbedActivity cta) {
waitForDialogHidingAnimation(cta);
// Animation source card becomes alpha = 0f when dialog is showing and animates back to 1f
// when dialog hides. Make sure the source card has restored its alpha change.
CriteriaHelper.pollUiThread(
() -> {
RecyclerView recyclerView = cta.findViewById(R.id.tab_list_recycler_view);
for (int i = 0; i < recyclerView.getAdapter().getItemCount(); i++) {
RecyclerView.ViewHolder viewHolder =
recyclerView.findViewHolderForAdapterPosition(i);
if (viewHolder == null) continue;
if (viewHolder.itemView.getAlpha() != 1f) return false;
}
return true;
});
}
private void openSelectionEditorAndVerify(ChromeTabbedActivity cta, int count) {
// Open tab selection editor by selecting the select tabs item in tab grid dialog menu.
onView(withId(R.id.toolbar_menu_button)).perform(click());
onView(withText(cta.getString(R.string.menu_select_tabs)))
.inRoot(withDecorView(not(cta.getWindow().getDecorView())))
.perform(click());
mSelectionEditorRobot
.resultRobot
.verifyTabListEditorIsVisible()
.verifyToolbarSelectionTextWithResourceId(
R.string.tab_selection_editor_toolbar_select_tabs)
.verifyAdapterHasItemCount(count);
}
private void verifyGroupCreationDialogOpenedAndDismiss() {
// Verify that the modal dialog is now showing.
CriteriaHelper.pollUiThread(
() -> {
Criteria.checkThat(mModalDialogManager.isShowing(), Matchers.is(true));
});
// Verify the visual data dialog exists.
onViewWaiting(withId(R.id.visual_data_dialog_layout), /* checkRootDialog= */ true)
.check(matches(isDisplayed()));
// Dismiss the tab group creation dialog.
dismissAllModalDialogs();
// Verify that the modal dialog is now hidden.
CriteriaHelper.pollUiThread(
() -> {
Criteria.checkThat(mModalDialogManager.isShowing(), Matchers.is(false));
});
}
private void dismissAllModalDialogs() {
TestThreadUtils.runOnUiThreadBlocking(
() -> {
mModalDialogManager.dismissAllDialogs(DialogDismissalCause.UNKNOWN);
});
}
private void checkPosition(ChromeTabbedActivity cta, boolean isDialog, boolean isPortrait) {
// If isDialog is true, we are checking the position of TabGridDialog; otherwise we are
// checking the position of TabListEditor.
int contentViewId = isDialog ? R.id.dialog_container_view : R.id.selectable_list;
int smallMargin =
(int) cta.getResources().getDimension(R.dimen.tab_grid_dialog_side_margin);
int largeMargin = (int) cta.getResources().getDimension(R.dimen.tab_grid_dialog_top_margin);
int topMargin = isPortrait ? largeMargin : smallMargin;
int sideMargin = isPortrait ? smallMargin : largeMargin;
View parentView = cta.getCompositorViewHolderForTesting();
Rect parentRect = new Rect();
parentView.getGlobalVisibleRect(parentRect);
int[] parentLoc = new int[2];
parentView.getLocationOnScreen(parentLoc);
onView(withId(contentViewId))
.check(
(v, e) -> {
int[] location = new int[2];
v.getLocationOnScreen(location);
int relLoc0 = location[0] - parentLoc[0];
int relLoc1 = location[1] - parentLoc[1];
// Check the position.
assertEquals(sideMargin, relLoc0);
assertEquals(topMargin, relLoc1);
// Check the size.
assertEquals(parentView.getHeight() - 2 * topMargin, v.getHeight());
assertEquals(parentView.getWidth() - 2 * sideMargin, v.getWidth());
});
}
private void editDialogTitle(ChromeTabbedActivity cta, String title) {
onView(allOf(withParent(withId(R.id.main_content)), withId(R.id.title)))
.perform(click())
.check(
(v, e) -> {
// Verify all texts in the field are selected.
EditText titleView = (EditText) v;
assertEquals(
titleView.getText().length(),
titleView.getSelectionEnd() - titleView.getSelectionStart());
})
.perform(replaceText(title))
.perform(pressImeActionButton());
// Wait until the keyboard is hidden to make sure the edit has taken effect.
KeyboardVisibilityDelegate delegate = KeyboardVisibilityDelegate.getInstance();
CriteriaHelper.pollUiThread(
() -> !delegate.isKeyboardShowing(cta, cta.getCompositorViewHolderForTesting()));
}
private void verifyFirstCardTitle(String title) {
onView(
allOf(
isDescendantOfA(
withId(
TabUiTestHelper.getTabSwitcherAncestorId(
sActivityTestRule.getActivity()))),
withId(R.id.tab_list_recycler_view)))
.check(
(v, noMatchException) -> {
if (noMatchException != null) throw noMatchException;
RecyclerView recyclerView = (RecyclerView) v;
TextView firstCardTitleTextView =
recyclerView
.findViewHolderForAdapterPosition(0)
.itemView
.findViewById(R.id.tab_title);
assertEquals(title, firstCardTitleTextView.getText().toString());
});
}
private void clickScrimToExitDialog(ChromeTabbedActivity cta) throws ExecutionException {
CriteriaHelper.pollUiThread(() -> isDialogFullyVisible(cta));
TestThreadUtils.runOnUiThreadBlocking(
() -> {
View scrimView;
if (isTablet(cta)) {
TabGridDialogView dialogView = cta.findViewById(R.id.dialog_parent_view);
scrimView = dialogView.getScrimCoordinatorForTesting().getViewForTesting();
} else {
scrimView =
cta.getRootUiCoordinatorForTesting()
.getScrimCoordinator()
.getViewForTesting();
}
scrimView.performClick();
});
}
private boolean isTablet(ChromeTabbedActivity cta) {
return DeviceFormFactor.isNonMultiDisplayContextOnTablet(cta);
}
private void verifyBackgroundViewAccessibilityImportance(
ChromeTabbedActivity cta, boolean isDialogFullyVisible) {
View controlContainer = cta.findViewById(R.id.control_container);
assertEquals(
isDialogFullyVisible,
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
== controlContainer.getImportantForAccessibility());
View compositorViewHolder = cta.getCompositorViewHolderForTesting();
assertEquals(
isDialogFullyVisible,
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
== compositorViewHolder.getImportantForAccessibility());
View bottomContainer = cta.findViewById(R.id.bottom_container);
assertEquals(
isDialogFullyVisible,
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
== bottomContainer.getImportantForAccessibility());
if (isPhone()) {
View bottomControls = cta.findViewById(R.id.bottom_controls);
assertEquals(
isDialogFullyVisible,
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
== bottomControls.getImportantForAccessibility());
}
if (isTablet(cta)) {
View tabSwitcherViewHolder = cta.findViewById(R.id.tab_switcher_view_holder);
assertEquals(
isDialogFullyVisible,
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
== tabSwitcherViewHolder.getImportantForAccessibility());
}
}
private void verifyDialogUndoBarAndClick() {
// Verify that the dialog undo bar is showing and the default undo bar is hidden.
onViewWaiting(
allOf(
withId(R.id.snackbar_button),
isDescendantOfA(withId(R.id.dialog_snack_bar_container_view)),
isDisplayed()));
onView(allOf(withId(R.id.snackbar), isDescendantOfA(withId(R.id.bottom_container))))
.check(doesNotExist());
onView(
allOf(
withId(R.id.snackbar_button),
isDescendantOfA(withId(R.id.dialog_snack_bar_container_view)),
isDisplayed()))
.perform(click());
}
private void verifyGlobalUndoBarAndClick() {
// Verify that the dialog undo bar is showing and the default undo bar is hidden.
Matcher<View> expectedAncestor = null;
if (HubFieldTrial.isHubEnabled()) {
expectedAncestor = instanceOf(HubContainerView.class);
} else {
expectedAncestor =
withId(
isTablet(sActivityTestRule.getActivity())
? R.id.tab_switcher_view_holder
: R.id.bottom_container);
}
onViewWaiting(
allOf(withId(R.id.snackbar), isDescendantOfA(expectedAncestor), isDisplayed()));
onView(
allOf(
withId(R.id.snackbar_button),
isDescendantOfA(withId(R.id.dialog_snack_bar_container_view))))
.check(doesNotExist());
onView(
allOf(
withId(R.id.snackbar_button),
isDescendantOfA(expectedAncestor),
isDisplayed()))
.perform(click());
}
private void verifyDialogBackButtonContentDescription(ChromeTabbedActivity cta, String s) {
assertTrue(isDialogFullyVisible(cta));
onView(
allOf(
withId(R.id.toolbar_left_button),
isDescendantOfA(withId(R.id.dialog_container_view))))
.check((v, e) -> assertEquals(s, v.getContentDescription()));
}
private void testTitleTextFocus(ChromeTabbedActivity cta) throws ExecutionException {
// Click the text field to grab focus and click back button to lose focus.
onView(allOf(withParent(withId(R.id.main_content)), withId(R.id.title))).perform(click());
verifyTitleTextFocus(cta, true);
Espresso.pressBack();
verifyTitleTextFocus(cta, false);
verifyShowingDialog(cta, 2, null);
// Use toolbar menu to grab focus and click back button to lose focus.
openDialogToolbarMenuAndVerify(cta);
selectTabGridDialogToolbarMenuItem(cta, "Edit group name");
verifyTitleTextFocus(cta, true);
Espresso.pressBack();
verifyTitleTextFocus(cta, false);
verifyShowingDialog(cta, 2, null);
// Click the text field to grab focus and click scrim to lose focus.
onView(allOf(withParent(withId(R.id.main_content)), withId(R.id.title))).perform(click());
verifyTitleTextFocus(cta, true);
clickScrimToExitDialog(cta);
waitForDialogHidingAnimation(cta);
verifyTitleTextFocus(cta, false);
}
private void verifyTitleTextFocus(ChromeTabbedActivity cta, boolean shouldFocus) {
CriteriaHelper.pollUiThread(
() -> {
View titleTextView =
cta.findViewById(R.id.tab_group_toolbar).findViewById(R.id.title);
KeyboardVisibilityDelegate delegate = KeyboardVisibilityDelegate.getInstance();
boolean keyboardVisible =
delegate.isKeyboardShowing(
cta, cta.getCompositorViewHolderForTesting());
boolean isFocused = titleTextView.isFocused();
return (!shouldFocus ^ isFocused) && (!shouldFocus ^ keyboardVisible);
});
}
private RecyclerView getRecyclerView(ChromeTabbedActivity cta) {
ViewGroup group = (ViewGroup) cta.findViewById(getTabSwitcherAncestorId(cta));
return (RecyclerView) group.findViewById(R.id.tab_list_recycler_view);
}
private void enterTabListEditor(ChromeTabbedActivity cta) {
MenuUtils.invokeCustomMenuActionSync(
InstrumentationRegistry.getInstrumentation(), cta, R.id.menu_select_tabs);
}
}