Commit e62cb7e3 authored by Hazem Ashmawy's avatar Hazem Ashmawy Committed by Commit Bot

Reland "[AW][Dev-UI] Reland "Add some espresso tests for flag UI""

This is a reland of db9be6c9

The recent flakes are most probably caused by using of UIAutomator APIs
to test the developer UI service notification and pressing home button
in the tearDown of tests. This reland the CL after removing all
UIAutomator code including the test for persistent notification. A
separate reland of that test would be done later.

Original change's description:
> [AW][Dev-UI] Reland "Add some espresso tests for flag UI"
>
> This relands https://crbug.com/c/2438534 with a couple small changes:
>
> - android:windowSoftInputMode="adjustPan" attribute was missing in the
>   test manifest but is used in prod manifest. Adding it to the tests
>   manifest fixes the problem of auto-showing the keyboard on M devices
>   which was causing the test flakiness. When the keyboard shows up it
>   hides the view causing espresso to fail.
>
> - Merge the small change from https://crrev.com/c/2455687 to
>   test whether the overrideFlagsMap has the correct entries.
>
> Fixed: 1135839, 1130599
> Test: run_webview_instrumentation_test_apk -f "*FlagsFragmentTest*" on android M emulator
> Change-Id: I973e8cd2b7a9c12651d7a6e14f11a368f6e06bad
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2453564
> Reviewed-by: Changwan Ryu <changwan@chromium.org>
> Reviewed-by: Nate Fischer <ntfschr@chromium.org>
> Commit-Queue: Hazem Ashmawy <hazems@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#817184}

Change-Id: I9089d7e8d9b61b46b412061715abbf981da03706
Bug: 1130599, 1135839
Test: run_webview_instrumentation_test_apk -f "*FlagsFragmentTest*" --num_retries=0 --repeat=1000 --break-on-failure
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2482142Reviewed-by: default avatarNate Fischer <ntfschr@chromium.org>
Reviewed-by: default avatarChangwan Ryu <changwan@chromium.org>
Commit-Queue: Hazem Ashmawy <hazems@chromium.org>
Cr-Commit-Position: refs/heads/master@{#818858}
parent 4269a21a
......@@ -57,7 +57,8 @@ import org.chromium.base.test.util.Feature;
public class DeveloperUiTest {
// The package name of the test shell. This is acting both as the client app and the WebView
// provider.
private static final String TEST_WEBVIEW_PACKAGE_NAME = "org.chromium.android_webview.shell";
public static final String TEST_WEBVIEW_PACKAGE_NAME = "org.chromium.android_webview.shell";
public static final String TEST_WEBVIEW_APPLICATION_LABEL = "AwShellApplication";
@Rule
public IntentsTestRule mRule = new IntentsTestRule<MainActivity>(MainActivity.class);
......
......@@ -4,24 +4,33 @@
package org.chromium.android_webview.test.devui;
import static androidx.test.espresso.Espresso.onData;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withSpinnerText;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.collection.IsMapContaining.hasEntry;
import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.withCount;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.view.MotionEvent;
import android.view.View;
......@@ -30,31 +39,40 @@ import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.test.espresso.DataInteraction;
import androidx.test.filters.MediumTest;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.android_webview.common.AwSwitches;
import org.chromium.android_webview.common.DeveloperModeUtils;
import org.chromium.android_webview.common.Flag;
import org.chromium.android_webview.devui.FlagsFragment;
import org.chromium.android_webview.devui.MainActivity;
import org.chromium.android_webview.devui.R;
import org.chromium.android_webview.services.DeveloperUiService;
import org.chromium.android_webview.test.AwJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.util.Map;
/**
* UI tests for {@link FlagsFragment}.
* <p>
* These tests should not be batched to make sure that the DeveloperUiService is killed
* after each test, leaving a clean state.
*/
@RunWith(AwJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
public class FlagsFragmentTest {
@Rule
public ActivityTestRule mRule =
......@@ -67,6 +85,12 @@ public class FlagsFragmentTest {
mRule.launchActivity(intent);
}
@After
public void tearDown() {
// Make sure to clear shared preferences between tests to avoid any saved state.
DeveloperUiService.clearSharedPrefsForTesting(InstrumentationRegistry.getTargetContext());
}
private CallbackHelper getFlagUiSearchBarListener() {
final CallbackHelper helper = new CallbackHelper();
FlagsFragment.setFilterListener(() -> { helper.notifyCalled(); });
......@@ -318,4 +342,121 @@ public class FlagsFragmentTest {
onView(withId(R.id.flag_search_bar))
.check(matches(compoundDrawableVisible(CompoundDrawable.END)));
}
/**
* Toggle a flag's spinner with the given state value, and check that text changes to the
* correct value.
*
* @param flagInteraction the {@link DataInteraction} object representing a flag View item via
* {@code onData()}.
* @param state {@code true} for "enabled", {@code false} for "disabled", {@code null} for
* Default.
* @return the same {@code flagInteraction} passed param for the ease of chaining.
*/
private DataInteraction toggleFlag(DataInteraction flagInteraction, Boolean state) {
String stateText = state == null ? "Default" : state ? "Enabled" : "Disabled";
flagInteraction.onChildView(withId(R.id.flag_toggle)).perform(click());
onData(allOf(is(instanceOf(String.class)), is(stateText))).perform(click());
flagInteraction.onChildView(withId(R.id.flag_toggle))
.check(matches(withSpinnerText(containsString(stateText))));
return flagInteraction;
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testTogglingFlagShowsBlueDot() throws Throwable {
DataInteraction flagInteraction =
onData(anything()).inAdapterView(withId(R.id.flags_list)).atPosition(1);
// blue dot should be hidden by default
flagInteraction.onChildView(withId(R.id.flag_name))
.check(matches(not(compoundDrawableVisible(CompoundDrawable.START))));
// Test enabling flags shows a bluedot next to flag name
toggleFlag(flagInteraction, true);
flagInteraction.onChildView(withId(R.id.flag_name))
.check(matches(compoundDrawableVisible(CompoundDrawable.START)));
// Test setting to default hide the blue dot
toggleFlag(flagInteraction, null);
flagInteraction.onChildView(withId(R.id.flag_name))
.check(matches(not(compoundDrawableVisible(CompoundDrawable.START))));
// Test disabling flags shows a bluedot next to flag name
toggleFlag(flagInteraction, false);
flagInteraction.onChildView(withId(R.id.flag_name))
.check(matches(compoundDrawableVisible(CompoundDrawable.START)));
// Test setting to default again hide the blue dot
toggleFlag(flagInteraction, null);
flagInteraction.onChildView(withId(R.id.flag_name))
.check(matches(not(compoundDrawableVisible(CompoundDrawable.START))));
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testToggledFlagsFloatToTop() throws Throwable {
ListView flagsList = mRule.getActivity().findViewById(R.id.flags_list);
int totalNumFlags = flagsList.getCount();
String lastFlagName = ((Flag) flagsList.getAdapter().getItem(totalNumFlags - 1)).getName();
// Toggle the last flag in the list.
toggleFlag(onData(anything())
.inAdapterView(withId(R.id.flags_list))
.atPosition(totalNumFlags - 1),
true);
// Navigate from the flags UI then back to it to trigger list sorting.
onView(withId(R.id.navigation_home)).perform(click());
onView(withId(R.id.navigation_flags_ui)).perform(click());
// Check that the toggled flag is now at the top of the list. This assumes that the flags
// list has > 2 items.
onData(anything())
.inAdapterView(withId(R.id.flags_list))
.atPosition(1)
.onChildView(withId(R.id.flag_name))
.check(matches(withText(lastFlagName)));
// Reset to default.
toggleFlag(onData(anything()).inAdapterView(withId(R.id.flags_list)).atPosition(1), null);
// Navigate from the flags UI then back to it to trigger list sorting.
onView(withId(R.id.navigation_home)).perform(click());
onView(withId(R.id.navigation_flags_ui)).perform(click());
// Check that flags goes back to the end of the list when untoggled.
onData(anything())
.inAdapterView(withId(R.id.flags_list))
.atPosition(totalNumFlags - 1)
.onChildView(withId(R.id.flag_name))
.check(matches(withText(lastFlagName)));
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testResetFlags() throws Throwable {
ListView flagsList = mRule.getActivity().findViewById(R.id.flags_list);
String firstFlagName = ((Flag) flagsList.getAdapter().getItem(1)).getName();
toggleFlag(onData(anything()).inAdapterView(withId(R.id.flags_list)).atPosition(1), true);
Map<String, Boolean> flagOverrides =
DeveloperModeUtils.getFlagOverrides(DeveloperUiTest.TEST_WEBVIEW_PACKAGE_NAME);
assertThat(
"flagOverrides map should contain exactly one entry", flagOverrides.size(), is(1));
assertThat(flagOverrides, hasEntry(firstFlagName, true));
onView(withId(R.id.reset_flags_button)).perform(click());
DataInteraction flagInteraction =
onData(anything()).inAdapterView(withId(R.id.flags_list)).atPosition(1);
flagInteraction.onChildView(withId(R.id.flag_name))
.check(matches(not(compoundDrawableVisible(CompoundDrawable.START))));
flagInteraction.onChildView(withId(R.id.flag_toggle))
.check(matches(withSpinnerText(containsString("Default"))));
Assert.assertTrue(
DeveloperModeUtils.getFlagOverrides(DeveloperUiTest.TEST_WEBVIEW_PACKAGE_NAME)
.isEmpty());
}
}
......@@ -54,6 +54,8 @@
</intent-filter>
</activity>
<!-- Don't actually try to launch with this alias: it only exists so we can query its enabled state. -->
<!-- If you change this component make sure to update the corresponding copy in
test/shell/AndroidManifest.xml -->
<activity-alias android:name="org.chromium.android_webview.devui.DeveloperModeState"
android:targetActivity="org.chromium.android_webview.devui.MainActivity"
android:visibleToInstantApps="true"
......@@ -79,6 +81,8 @@
android:process=":webview_apk" {# Explicit process required for monochrome compatibility. #}
tools:ignore="ExportedContentProvider"/>
<!-- Disabled by default, enabled at runtime by Developer UI. -->
<!-- If you change this component make sure to update the corresponding copy in
test/shell/AndroidManifest.xml-->
<provider android:name="org.chromium.android_webview.services.DeveloperModeContentProvider"
android:visibleToInstantApps="true"
android:exported="true"
......
......@@ -433,10 +433,11 @@ public class FlagsFragment extends DevUiBaseFragment {
TextView flagName = toggleableFlag.findViewById(R.id.flag_name);
if (state == /* STATE_DEFAULT */ 0) {
// Unset the compound drawable.
flagName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
flagName.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
} else { // STATE_ENABLED or STATE_DISABLED
// Draws a blue circle to the left of the text.
flagName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.blue_circle, 0, 0, 0);
flagName.setCompoundDrawablesRelativeWithIntrinsicBounds(
R.drawable.blue_circle, 0, 0, 0);
}
}
......
......@@ -21,6 +21,8 @@ import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import androidx.annotation.VisibleForTesting;
import org.chromium.android_webview.common.DeveloperModeUtils;
import org.chromium.android_webview.common.Flag;
import org.chromium.android_webview.common.FlagOverrideHelper;
......@@ -51,6 +53,11 @@ public final class DeveloperUiService extends Service {
private static final int FRAGMENT_ID_CRASHES = 1;
private static final int FRAGMENT_ID_FLAGS = 2;
public static final String NOTIFICATION_TITLE =
"WARNING: experimental WebView features enabled";
public static final String NOTIFICATION_CONTENT = "Tap to see experimental features.";
public static final String NOTIFICATION_TICKER = "Experimental WebView features enabled";
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static Map<String, Boolean> sOverriddenFlags = new HashMap<>();
......@@ -221,15 +228,14 @@ public final class DeveloperUiService extends Service {
notificationIntent.putExtra(FRAGMENT_ID_INTENT_EXTRA, FRAGMENT_ID_FLAGS);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification.Builder builder =
createNotificationBuilder()
.setContentTitle("WARNING: experimental WebView features enabled")
.setContentText("Tap to see experimental features.")
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setTicker("Experimental WebView features enabled");
Notification.Builder builder = createNotificationBuilder()
.setContentTitle(NOTIFICATION_TITLE)
.setContentText(NOTIFICATION_CONTENT)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setContentIntent(pendingIntent)
.setOngoing(true)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setTicker(NOTIFICATION_TICKER);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
builder = builder
......@@ -310,4 +316,14 @@ public final class DeveloperUiService extends Service {
FlagOverrideHelper helper = new FlagOverrideHelper(ProductionSupportedFlagList.sFlagList);
helper.applyFlagOverrides(newFlags);
}
@VisibleForTesting
public static void clearSharedPrefsForTesting(Context context) {
synchronized (sLock) {
context.getSharedPreferences(DeveloperUiService.SHARED_PREFS_FILE, Context.MODE_PRIVATE)
.edit()
.clear()
.apply();
}
}
}
......@@ -41,13 +41,6 @@
</intent-filter>
</activity>
<!-- WebView Developer UI Activities. If you change these Activities, please update
android_webview/nonembedded/java/AndroidManifest.xml as well. -->
<activity android:name="org.chromium.android_webview.devui.MainActivity"
android:theme="@style/Theme.DevUi.DayNight"
android:launchMode="singleTask">
</activity>
<provider android:name="org.chromium.android_webview.test.TestContentProvider"
android:authorities="org.chromium.android_webview.test.TestContentProvider" />
<!-- Some tests require 3 sandboxed services -->
......@@ -84,5 +77,21 @@
android:exported="true" />
<service android:name="org.chromium.android_webview.test.services.MockVariationsSeedServer"
android:exported="true" />
<!-- Components for Developer UI, make sure that any changes in these components reflect
the corresponding original components in nonembedded/java/AndroidManifest.xml -->
<activity android:name="org.chromium.android_webview.devui.MainActivity"
android:theme="@style/Theme.DevUi.DayNight"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan">
</activity>
<provider android:name="org.chromium.android_webview.services.DeveloperModeContentProvider"
android:exported="true"
android:authorities="org.chromium.android_webview.shell.DeveloperModeContentProvider"/>
<service android:name="org.chromium.android_webview.services.DeveloperUiService"
android:exported="false" />
<activity-alias android:name="org.chromium.android_webview.devui.DeveloperModeState"
android:targetActivity="org.chromium.android_webview.shell.AwShellActivity"
android:enabled="false" />
<!-- End of Developer UI related components -->
</application>
</manifest>
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