Commit da37c4e6 authored by Arthur Wang's avatar Arthur Wang Committed by Commit Bot

Create a batch of feed card render test

R=aluo@chromium.org, bsheedy@chromium.org, carlosk@chromium.org, harringtond@chromium.org

Bug: 1021309
Change-Id: Icda90823cbb4e73f5735008d96f3d51e23c4a9e9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1898438
Commit-Queue: Arthur Wang <wuwang@google.com>
Reviewed-by: default avatarSam Maier <smaier@chromium.org>
Reviewed-by: default avatarAndrew Luo <aluo@chromium.org>
Reviewed-by: default avatarDan H <harringtond@chromium.org>
Cr-Commit-Position: refs/heads/master@{#719292}
parent d74a1ece
......@@ -51,6 +51,8 @@ public class FeedProcessScopeFactory {
private static FeedScheduler sFeedScheduler;
private static FeedOfflineIndicator sFeedOfflineIndicator;
private static NetworkClient sTestNetworkClient;
private static ContentStorageDirect sTestContentStorageDirect;
private static JournalStorageDirect sTestJournalStorageDirect;
private static FeedLoggingBridge sFeedLoggingBridge;
/** @return The shared {@link ProcessScope} instance. Null if the Feed is disabled. */
......@@ -151,12 +153,15 @@ public class FeedProcessScopeFactory {
FeedSchedulerBridge schedulerBridge = new FeedSchedulerBridge(profile);
sFeedScheduler = schedulerBridge;
ContentStorageDirect contentStorageDirect =
new FeedContentStorageDirect(new FeedContentStorage(profile));
JournalStorageDirect journalStorageDirect =
new FeedJournalStorageDirect(new FeedJournalStorage(profile));
NetworkClient networkClient = sTestNetworkClient == null ?
new FeedNetworkBridge(profile) : sTestNetworkClient;
ContentStorageDirect contentStorageDirect = sTestContentStorageDirect == null
? new FeedContentStorageDirect(new FeedContentStorage(profile))
: sTestContentStorageDirect;
JournalStorageDirect journalStorageDirect = sTestJournalStorageDirect == null
? new FeedJournalStorageDirect(new FeedJournalStorage(profile))
: sTestJournalStorageDirect;
NetworkClient networkClient =
sTestNetworkClient == null ? new FeedNetworkBridge(profile) : sTestNetworkClient;
sFeedLoggingBridge = new FeedLoggingBridge(profile);
SequencedTaskRunner sequencedTaskRunner =
......@@ -226,6 +231,32 @@ public class FeedProcessScopeFactory {
}
}
/** Use supplied ContentStorageDirect instead of real one, for tests. */
@VisibleForTesting
public static void setTestContentStorageDirect(ContentStorageDirect storage) {
if (storage == null) {
sTestContentStorageDirect = null;
} else if (sProcessScope == null) {
sTestContentStorageDirect = storage;
} else {
throw new IllegalStateException(
"TestContentStorageDirect can not be set after ProcessScope has initialized.");
}
}
/** Use supplied JournalStorageDirect instead of real one, for tests. */
@VisibleForTesting
public static void setTestJournalStorageDirect(JournalStorageDirect storage) {
if (storage == null) {
sTestJournalStorageDirect = null;
} else if (sProcessScope == null) {
sTestJournalStorageDirect = storage;
} else {
throw new IllegalStateException(
"TestJournalStorageDirect can not be set after ProcessScope has initialized.");
}
}
/** Resets the ProcessScope after testing is complete. */
@VisibleForTesting
static void clearFeedProcessScopeForTesting() {
......
......@@ -14,7 +14,6 @@ import com.google.android.libraries.feed.api.host.network.HttpRequest.HttpMethod
import com.google.android.libraries.feed.api.host.network.HttpResponse;
import com.google.android.libraries.feed.api.host.network.NetworkClient;
import com.google.android.libraries.feed.common.functional.Consumer;
import com.google.android.libraries.feed.common.logging.Logger;
import com.google.android.libraries.feed.feedrequestmanager.RequestHelper;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedOutputStream;
......@@ -96,7 +95,6 @@ public class TestNetworkClient implements NetworkClient {
if (requestToken != null) {
for (ConditionalResponse response : mMockServer.getConditionalResponsesList()) {
if (!response.hasContinuationToken()) {
Logger.w(TAG, "Conditional response without a token");
continue;
}
if (requestToken.equals(response.getContinuationToken())) {
......
......@@ -58,11 +58,14 @@ if (enable_feed_in_chrome) {
feed_test_java_sources = [
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/tooltip/FeedTooltipTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/ConsumerSyncWrapper.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/DataFilePath.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedAppLifecycleTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedConfigurationTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedContentStorageConformanceTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedDataInjectRule.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedJournalStorageConformanceTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedNetworkBridgeConformanceTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedNewTabPageCardRenderTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedNewTabPageTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedSchedulerBridgeConformanceTest.java",
"//chrome/android/javatests/src/org/chromium/chrome/browser/feed/FeedRefreshTaskTest.java",
......
......@@ -11,3 +11,7 @@
# Do not mark classes as final, as doing so may interfere with mocking.
-optimizations !class/marking/final
# For some unknown reason, JUnit does not like renamed annotations, and causes
# the tests annotated by them not to be run.
-keepnames @interface *
// Copyright 2019 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.browser.feed;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* We use this annotation, together with {@FeedDataInjectRule}, to tell
* which data file to inject for each test cases.
*
* For instance, if file_foo is used in test A, file_bar is used
* in test B.
*
* @Rule
* public FeedDataInjectRule mDataInjector = new FeedDataInjectRule();
*
* @DataFilePath("file_foo")
* public void test_A() {
* // Write the test case here.
* }
*
* @DataFilePath("file_bar")
* public void test_B() {
* // Write the test case here.
* }
*
* In test_A, the FeedDataInjectRule will then injects file_foo via testNetworkClient,
* invokes triggerRefresh at UI thread, responses the seeded mockserver response from
* file_foo, and then blocks waiting for the content change notification. It does the
* similar for test_B except it injects file_bar.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataFilePath {
/**
* @return one DataFilePath.
*/
public String value();
}
// Copyright 2019 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.browser.feed;
import static com.google.android.libraries.feed.basicstream.internal.viewholders.ViewHolderType.TYPE_CARD;
import android.support.v7.widget.RecyclerView;
import com.google.android.libraries.feed.api.client.stream.Stream;
import com.google.android.libraries.feed.hostimpl.storage.testing.InMemoryContentStorage;
import com.google.android.libraries.feed.hostimpl.storage.testing.InMemoryJournalStorage;
import org.junit.Assert;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.chromium.base.Log;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* A test rule to inject feed card data, base on the {@DataFilePath} annotation,
* into the FeedProcessScopeFactory via TestNetworkClient.
* If there is no {@DataFilePath} annotation, then no data injection happens
* for that test case.
*
* <pre>
* {@code
*
* @RunWith(ChromeJUnit4ClassRunner.class)
* @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
* public class MyTest{
* // Provide FeedDataInjectRule with the path from src/ to the test data directory.
* @Rule
* public FeedDataInjectRule mDataInjector = new FeedDataInjectRule();
*
* @Rule
* public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule =
* new ChromeActivityTestRule(ChromeTabbedActivity.class);
*
* @Test
* @Feature({"FeedNewTabPage"})
* @DataFilePath("foo")
* public void testViewCard() {
* mActivityTestRule.loadUrlInNewTab(UrlConstants.NTP_URL);
* Tab tab = mActivityTestRule.getActivity().getActivityTab();
* NewTabPageTestUtils.waitForNtpLoaded(tab);
*
* FeedNewTabPage ntp = (FeedNewTabPage) tab.getNativePage();
* mInjectDataRule.triggerFeedRefreshOnUiThreadBlocking(ntp.getStreamForTesting());
* }
* }
* }
* </pre>
*
* Note when using this test rule, do not also use ChromeTabbedActivityTestRule.
* ChromeTabbedActivityTestRule also try to set a default feed response file and
* it could results to strange behaviors in test, such as always see the same
* set of cards in each test case.
*/
final class FeedDataInjectRule extends TestWatcher {
private static final String TAG = "FeedDataInjectRule";
public static final int FIRST_CARD_POSITION = 3;
private TestNetworkClient mClient;
private boolean mTestCaseDataFileInjected;
@Override
protected void starting(Description desc) {
DataFilePath filePath = desc.getAnnotation(DataFilePath.class);
if (filePath != null) {
mClient = new TestNetworkClient();
try {
mClient.setNetworkResponseFile(UrlUtils.getIsolatedTestFilePath(filePath.value()));
FeedProcessScopeFactory.setTestNetworkClient(mClient);
FeedProcessScopeFactory.setTestContentStorageDirect(new InMemoryContentStorage());
FeedProcessScopeFactory.setTestJournalStorageDirect(new InMemoryJournalStorage());
mTestCaseDataFileInjected = true;
} catch (IOException e) {
Log.e(TAG, "fails to set response file %s, err %s", filePath.value(),
e.getMessage());
Assert.fail(
String.format("starting fails to set response file %s", filePath.value()));
}
}
}
@Override
protected void finished(Description description) {
FeedProcessScopeFactory.setTestNetworkClient(null);
FeedProcessScopeFactory.setTestContentStorageDirect(null);
FeedProcessScopeFactory.setTestJournalStorageDirect(null);
mTestCaseDataFileInjected = false;
}
public void triggerFeedRefreshOnUiThreadBlocking(Stream stream)
throws IllegalArgumentException, TimeoutException {
if (stream == null) {
throw new IllegalArgumentException("stream should not be null.");
}
if (mTestCaseDataFileInjected) {
TestObserver observer =
new TestObserver(((RecyclerView) stream.getView()).getAdapter());
stream.addOnContentChangedListener(observer);
int callCount = observer.firstCardShownCallback.getCallCount();
Log.i(TAG, "Waiting for %d callback calls", callCount);
TestThreadUtils.runOnUiThreadBlocking(() -> stream.triggerRefresh());
observer.firstCardShownCallback.waitForCallback(callCount);
}
}
private class TestObserver implements Stream.ContentChangedListener {
public final CallbackHelper firstCardShownCallback = new CallbackHelper();
private final RecyclerView.Adapter mRecyclerViewAdapter;
TestObserver(RecyclerView.Adapter adapter) {
mRecyclerViewAdapter = adapter;
}
@Override
public void onContentChanged() {
if (mRecyclerViewAdapter.getItemViewType(FIRST_CARD_POSITION) == TYPE_CARD) {
firstCardShownCallback.notifyCalled();
}
}
}
}
// Copyright 2019 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.browser.feed;
import static android.support.test.espresso.Espresso.onView;
import static org.hamcrest.Matchers.instanceOf;
import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.filters.MediumTest;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.ntp.snippets.SectionHeader;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.suggestions.SiteSuggestion;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.NewTabPageTestUtils;
import org.chromium.chrome.test.util.RenderTestRule;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.RecyclerViewTestUtils;
import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
import org.chromium.chrome.test.util.browser.suggestions.mostvisited.FakeMostVisitedSites;
import org.chromium.net.test.EmbeddedTestServerRule;
import java.util.List;
/**
* Tests for {@link FeedNewTabPage} specifically with card rendering. Other tests can be found in
* {@link org.chromium.chrome.browser.feed.FeedNewTabPageTest}.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
@Features.EnableFeatures(ChromeFeatureList.INTEREST_FEED_CONTENT_SUGGESTIONS)
public class FeedNewTabPageCardRenderTest {
private static final String TEST_FEED_DATA_BASE_PATH = "/chrome/test/data/android/feed/";
@Rule
public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule =
new ChromeActivityTestRule(ChromeTabbedActivity.class);
@Rule
public SuggestionsDependenciesRule mSuggestionsDeps = new SuggestionsDependenciesRule();
@Rule
public RenderTestRule mRenderTestRule =
new RenderTestRule("chrome/test/data/android/render_tests");
@Rule
public FeedDataInjectRule mFeedDataInjector = new FeedDataInjectRule();
@Rule
public EmbeddedTestServerRule mTestServer = new EmbeddedTestServerRule();
private Tab mTab;
private FeedNewTabPage mNtp;
private ViewGroup mTileGridLayout;
private FakeMostVisitedSites mMostVisitedSites;
private List<SiteSuggestion> mSiteSuggestions;
@Before
public void setUp() throws Exception {
mActivityTestRule.startMainActivityOnBlankPage();
mSiteSuggestions = NewTabPageTestUtils.createFakeSiteSuggestions(mTestServer.getServer());
mMostVisitedSites = new FakeMostVisitedSites();
mMostVisitedSites.setTileSuggestions(mSiteSuggestions);
mSuggestionsDeps.getFactory().mostVisitedSites = mMostVisitedSites;
mActivityTestRule.loadUrl(UrlConstants.NTP_URL);
mTab = mActivityTestRule.getActivity().getActivityTab();
NewTabPageTestUtils.waitForNtpLoaded(mTab);
Assert.assertTrue(mTab.getNativePage() instanceof FeedNewTabPage);
mNtp = (FeedNewTabPage) mTab.getNativePage();
mTileGridLayout = mNtp.getView().findViewById(R.id.tile_grid_layout);
Assert.assertEquals(mSiteSuggestions.size(), mTileGridLayout.getChildCount());
}
@Test
@MediumTest
@Feature({"FeedNewTabPage", "RenderTest"})
@DataFilePath(TEST_FEED_DATA_BASE_PATH + "feed_world.gcl.bin")
public void testFeedCardRenderingScenarioWorld() throws Exception {
renderFeedCards("world");
}
private void renderFeedCards(String scenarioName) throws Exception {
// Open a new tab.
SectionHeader firstHeader = mNtp.getMediatorForTesting().getSectionHeaderForTesting();
RecyclerView recycleView =
(RecyclerView) mNtp.getCoordinatorForTesting().getStream().getView();
RecyclerView.Adapter adapter1 =
((RecyclerView) mNtp.getCoordinatorForTesting().getStream().getView()).getAdapter();
// Check header is expanded.
Assert.assertTrue(firstHeader.isExpandable() && firstHeader.isExpanded());
Assert.assertTrue(getPreferenceForArticleSectionHeader());
// Trigger a refresh to get feed cards.
mFeedDataInjector.triggerFeedRefreshOnUiThreadBlocking(mNtp.getStreamForTesting());
// Scroll to the first feed card.
onView(instanceOf(RecyclerView.class))
.perform(RecyclerViewActions.scrollToPosition(
FeedDataInjectRule.FIRST_CARD_POSITION));
mRenderTestRule.render(
recycleView, String.format("render_feed_cards_top_%s", scenarioName));
// Scroll to the bottom.
RecyclerViewTestUtils.scrollToBottom(recycleView);
mRenderTestRule.render(
recycleView, String.format("render_feed_cards_bottom_%s", scenarioName));
// Scroll to the first feed card again.
onView(instanceOf(RecyclerView.class))
.perform(RecyclerViewActions.scrollToPosition(
FeedDataInjectRule.FIRST_CARD_POSITION));
mRenderTestRule.render(
recycleView, String.format("render_feed_cards_top_again_%s", scenarioName));
}
private boolean getPreferenceForArticleSectionHeader() throws Exception {
return ThreadUtils.runOnUiThreadBlocking(
() -> PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE));
}
}
This diff was suppressed by a .gitattributes entry.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment