Skip to content

Commit

Permalink
[Start] Add StartSurfaceTestUtils to make tests more readable.
Browse files Browse the repository at this point in the history
Bug: 1185009, 1199745
Change-Id: I316cde9c679c5371d0056bedf3b020cf36e61990
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2878454
Reviewed-by: Yaron Friedman <yfriedman@chromium.org>
Reviewed-by: Xi Han <hanxi@chromium.org>
Reviewed-by: Yue Zhang <yuezhanggg@chromium.org>
Commit-Queue: Hao Dong <spdonghao@chromium.org>
Cr-Commit-Position: refs/heads/master@{#881254}
  • Loading branch information
spdonghao authored and Chromium LUCI CQ committed May 10, 2021
1 parent 9809bcb commit cbb1104
Show file tree
Hide file tree
Showing 8 changed files with 846 additions and 1,095 deletions.
3 changes: 3 additions & 0 deletions chrome/android/DEPS
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ specific_include_rules = {
"+chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java",
"+chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java",
],
".*TestUtils\.java": [
"+chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java",
],

# Exceptions to the Chrome*Activity dependency restriction. These will all eventually be removed
# new code should rely on acceptable dependency aquisition patterns.
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.features.start_surface;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withParent;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.fail;

import static org.chromium.chrome.browser.tabmodel.TestTabModelDirectory.M26_GOOGLE_COM;
import static org.chromium.chrome.test.util.ViewUtils.onViewWaiting;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.support.test.InstrumentationRegistry;
import android.util.Base64;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.espresso.matcher.ViewMatchers;

import org.hamcrest.Matcher;
import org.junit.Assert;

import org.chromium.base.StreamUtil;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.tab.TabState;
import org.chromium.chrome.browser.tab.TabStateFileManager;
import org.chromium.chrome.browser.tabmodel.TabPersistentStore;
import org.chromium.chrome.browser.tabmodel.TabbedModeTabPersistencePolicy;
import org.chromium.chrome.browser.tabpersistence.TabStateDirectory;
import org.chromium.chrome.browser.tasks.pseudotab.PseudoTab;
import org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper;
import org.chromium.chrome.browser.toolbar.top.ToolbarPhone;
import org.chromium.chrome.start_surface.R;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.browser.test.util.TestTouchUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;

/**
* Utility methods and classes for testing Start Surface.
*/
public class StartSurfaceTestUtils {
private static final long MAX_TIMEOUT_MS = 30000L;

/**
* Only launch Chrome without waiting for a current tab.
* This method could not use {@link ChromeActivityTestRule#startMainActivityFromLauncher()}
* because of its {@link org.chromium.chrome.browser.tab.Tab} dependency.
*/
public static void startMainActivityFromLauncher(ChromeActivityTestRule activityTestRule) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
activityTestRule.prepareUrlIntent(intent, null);
activityTestRule.launchActivity(intent);
}

/**
* Wait for the start surface homepage or tab switcher page visible.
* @param cta The ChromeTabbedActivity under test.
*/
public static void waitForOverviewVisible(ChromeTabbedActivity cta) {
CriteriaHelper.pollUiThread(()
-> cta.getLayoutManager() != null
&& cta.getLayoutManager().overviewVisible(),
MAX_TIMEOUT_MS, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
}

/**
* Wait for the start surface homepage or tab switcher page visible.
* @param layoutChangedCallbackHelper The call back function to help check whether layout is
* changed.
* @param currentlyActiveLayout The current active layout.
*/
public static void waitForOverviewVisible(
CallbackHelper layoutChangedCallbackHelper, @LayoutType int currentlyActiveLayout) {
if (currentlyActiveLayout == LayoutType.TAB_SWITCHER) return;
try {
layoutChangedCallbackHelper.waitForNext();
} catch (TimeoutException ex) {
assert false : "Timeout waiting for browser to enter tab switcher / start surface.";
}
}

/**
* Wait for the tab state to be initialized.
* @param cta The ChromeTabbedActivity under test.
*/
public static void waitForTabModel(ChromeTabbedActivity cta) {
CriteriaHelper.pollUiThread(cta.getTabModelSelector()::isTabStateInitialized,
MAX_TIMEOUT_MS, CriteriaHelper.DEFAULT_POLLING_INTERVAL);
}

/**
* Create all the files so that tab models can be restored.
* @param tabIds all the Tab IDs in the normal tab model.
*/
public static void createTabStateFile(int[] tabIds) throws IOException {
createTabStateFile(tabIds, null, 0);
}

/**
* Create all the files so that tab models can be restored.
* @param tabIds all the Tab IDs in the normal tab model.
* @param urls all of the URLs in the normal tab model.
*/
public static void createTabStateFile(int[] tabIds, @Nullable String[] urls)
throws IOException {
createTabStateFile(tabIds, urls, 0);
}

/**
* Create all the files so that tab models can be restored.
* @param tabIds all the Tab IDs in the normal tab model.
* @param urls all of the URLs in the normal tab model.
* @param selectedIndex the selected index of normal tab model.
*/
public static void createTabStateFile(int[] tabIds, @Nullable String[] urls, int selectedIndex)
throws IOException {
TabPersistentStore.TabModelMetadata normalInfo =
new TabPersistentStore.TabModelMetadata(selectedIndex);
for (int i = 0; i < tabIds.length; i++) {
normalInfo.ids.add(tabIds[i]);
String url = urls != null ? urls[i] : "about:blank";
normalInfo.urls.add(url);

saveTabState(tabIds[i], false);
}
TabPersistentStore.TabModelMetadata incognitoInfo =
new TabPersistentStore.TabModelMetadata(0);

byte[] listData = TabPersistentStore.serializeMetadata(normalInfo, incognitoInfo);

File stateFile = new File(TabStateDirectory.getOrCreateTabbedModeStateDirectory(),
TabbedModeTabPersistencePolicy.getStateFileName(0));
FileOutputStream output = new FileOutputStream(stateFile);
output.write(listData);
output.close();
}

/**
* Create thumbnail bitmap of the tab based on the given id and write it to file.
* @param tabId The id of the target tab.
* @return The bitmap created.
*/
public static Bitmap createThumbnailBitmapAndWriteToFile(int tabId) {
final Bitmap thumbnailBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);

try {
File thumbnailFile = TabContentManager.getTabThumbnailFileJpeg(tabId);
if (thumbnailFile.exists()) {
thumbnailFile.delete();
}
Assert.assertFalse(thumbnailFile.exists());

FileOutputStream thumbnailFileOutputStream = new FileOutputStream(thumbnailFile);
thumbnailBitmap.compress(Bitmap.CompressFormat.JPEG, 100, thumbnailFileOutputStream);
thumbnailFileOutputStream.flush();
thumbnailFileOutputStream.close();

Assert.assertTrue(thumbnailFile.exists());
} catch (IOException e) {
e.printStackTrace();
}
return thumbnailBitmap;
}

/**
* Get the StartSurfaceCoordinator from UI thread.
* @param cta The ChromeTabbedActivity under test.
*/
public static StartSurfaceCoordinator getStartSurfaceFromUIThread(ChromeTabbedActivity cta) {
AtomicReference<StartSurface> startSurface = new AtomicReference<>();
TestThreadUtils.runOnUiThreadBlocking(() -> startSurface.set(cta.getStartSurface()));
return (StartSurfaceCoordinator) startSurface.get();
}

/**
* @param activityTestRule The test rule of activity under test.
* @return Whether the keyboard is shown.
*/
public static boolean isKeyboardShown(ChromeActivityTestRule activityTestRule) {
Activity activity = activityTestRule.getActivity();
if (activity.getCurrentFocus() == null) return false;
return activityTestRule.getKeyboardDelegate().isKeyboardShowing(
activity, activity.getCurrentFocus());
}

/**
* Scroll the start surface to make toolbar scrolled off.
* @param cta The ChromeTabbedActivity under test.
*/
public static void scrollToolbar(ChromeTabbedActivity cta) {
// The home button shouldn't show on homepage.
onView(withId(R.id.home_button))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));

// Drag the Feed header title to scroll the toolbar to the top.
int toY = -cta.getResources().getDimensionPixelOffset(R.dimen.toolbar_height_no_shadow);
TestTouchUtils.dragCompleteView(InstrumentationRegistry.getInstrumentation(),
cta.findViewById(R.id.header_title), 0, 0, 0, toY, 1);

// The start surface toolbar should be scrolled up and not be displayed.
CriteriaHelper.pollInstrumentationThread(
()
-> cta.findViewById(R.id.tab_switcher_toolbar).getTranslationY()
<= (float) -cta.getResources().getDimensionPixelOffset(
R.dimen.toolbar_height_no_shadow));

// Toolbar layout view should show.
onViewWaiting(withId(R.id.toolbar));
// The home button shouldn't show on homepage whether it's scrolled or not.
onView(withId(R.id.home_button))
.check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)));

// The start surface toolbar should be scrolled up and not be displayed.
onView(withId(R.id.tab_switcher_toolbar)).check(matches(not(isDisplayed())));

// Check the toolbar's background color.
ToolbarPhone toolbar = cta.findViewById(org.chromium.chrome.R.id.toolbar);
Assert.assertEquals(toolbar.getToolbarDataProvider().getPrimaryColor(),
toolbar.getBackgroundDrawable().getColor());
}

/**
* Navigate to homepage.
* @param cta The ChromeTabbedActivity under test.
*/
public static void pressHomePageButton(ChromeTabbedActivity cta) {
TestThreadUtils.runOnUiThreadBlocking(() -> {
cta.getToolbarManager().getToolbarTabControllerForTesting().openHomepage();
});
}

/**
* Click the first MV tile in mv_tiles_layout.
* @param cta The ChromeTabbedActivity under test.
* @param currentTabCount The correct number of normal tabs.
*/
public static void launchFirstMVTile(ChromeTabbedActivity cta, int currentTabCount) {
TabUiTestHelper.verifyTabModelTabCount(cta, currentTabCount, 0);
OverviewModeBehaviorWatcher hideWatcher = TabUiTestHelper.createOverviewHideWatcher(cta);
onViewWaiting(withId(org.chromium.chrome.tab_ui.R.id.mv_tiles_layout))
.perform(new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return isDisplayed();
}

@Override
public String getDescription() {
return "Click explore top sites view in MV tiles.";
}

@Override
public void perform(UiController uiController, View view) {
ViewGroup mvTilesContainer = (ViewGroup) view;
mvTilesContainer.getChildAt(0).performClick();
}
});
hideWatcher.waitForBehavior();
CriteriaHelper.pollUiThread(() -> !cta.getLayoutManager().overviewVisible());
// Verifies a new Tab is created.
TabUiTestHelper.verifyTabModelTabCount(cta, currentTabCount + 1, 0);
}

/**
* Click the first tab in carousel tab switcher.
*/
public static void clickFirstTabInCarousel() {
clickTabInCarousel(0);
}

/**
* Click the tab at specific position in carousel tab switcher.
* @param position The position of the tab which is clicked.
*/
public static void clickTabInCarousel(int position) {
onViewWaiting(
allOf(withParent(withId(
org.chromium.chrome.tab_ui.R.id.carousel_tab_switcher_container)),
withId(org.chromium.chrome.tab_ui.R.id.tab_list_view)))
.perform(RecyclerViewActions.actionOnItemAtPosition(position, click()));
}

/**
* Click "more_tabs" to navigate to tab switcher surface.
* @param cta The ChromeTabbedActivity under test.
*/
public static void clickMoreTabs(ChromeTabbedActivity cta) {
// Note that onView(R.id.more_tabs).perform(click()) can not be used since it requires 90
// percent of the view's area is displayed to the users. However, this view has negative
// margin which makes the percentage is less than 90.
// TODO(crbug.com/1186752): Investigate whether this would be a problem for real users.
try {
TestThreadUtils.runOnUiThreadBlocking(
()
-> cta.findViewById(org.chromium.chrome.tab_ui.R.id.more_tabs)
.performClick());
} catch (ExecutionException e) {
fail("Failed to tap 'more tabs' " + e.toString());
}
}

/**
* Create a file so that a TabState can be restored later.
* @param tabId the Tab ID
* @param encrypted for Incognito mode
*/
private static void saveTabState(int tabId, boolean encrypted) {
File file = TabStateFileManager.getTabStateFile(
TabStateDirectory.getOrCreateTabbedModeStateDirectory(), tabId, encrypted);
writeFile(file, M26_GOOGLE_COM.encodedTabState);

TabState tabState = TabStateFileManager.restoreTabState(file, false);
tabState.rootId = PseudoTab.fromTabId(tabId).getRootId();
TabStateFileManager.saveState(file, tabState, encrypted);
}

private static void writeFile(File file, String data) {
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
outputStream.write(Base64.decode(data, 0));
} catch (Exception e) {
assert false : "Failed to create " + file;
} finally {
StreamUtil.closeQuietly(outputStream);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ start_surface_test_java_sources = [
"//chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java",
"//chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceNoTabsTest.java",
"//chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTest.java",
"//chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceTestUtils.java",
"//chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/TasksSurfaceViewBinderTest.java",
]
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
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.features.start_surface.InstantStartTest.createTabStateFile;
import static org.chromium.chrome.features.start_surface.InstantStartTest.createThumbnailBitmapAndWriteToFile;
import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.createTabStateFile;
import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile;
import static org.chromium.chrome.test.util.ViewUtils.onViewWaiting;
import static org.chromium.chrome.test.util.ViewUtils.waitForView;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
import static org.chromium.chrome.browser.tasks.tab_management.TabUiTestHelper.mergeAllNormalTabsToAGroup;
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.features.start_surface.InstantStartTest.createTabStateFile;
import static org.chromium.chrome.features.start_surface.InstantStartTest.createThumbnailBitmapAndWriteToFile;
import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.createTabStateFile;
import static org.chromium.chrome.features.start_surface.StartSurfaceTestUtils.createThumbnailBitmapAndWriteToFile;
import static org.chromium.chrome.test.util.ViewUtils.waitForView;
import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;

Expand Down

0 comments on commit cbb1104

Please sign in to comment.