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

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

Add tests for toggling flags and the persistent notification. Also
remove test batching for flags fragment tests to so the flags service
is killed when the process is killed leaving a clean state.

Fixed: 1130599
Test: run_webview_instrumentation_test_apk -f "*FlagsFragmentTest*"
Change-Id: I0cb416471d3b79e98ed9ef8ad5e760b4bfaa1083
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2438534
Commit-Queue: Hazem Ashmawy <hazems@chromium.org>
Reviewed-by: default avatarChangwan Ryu <changwan@chromium.org>
Reviewed-by: default avatarNate Fischer <ntfschr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#814362}
parent 70f8f965
...@@ -57,7 +57,8 @@ import org.chromium.base.test.util.Feature; ...@@ -57,7 +57,8 @@ import org.chromium.base.test.util.Feature;
public class DeveloperUiTest { public class DeveloperUiTest {
// The package name of the test shell. This is acting both as the client app and the WebView // The package name of the test shell. This is acting both as the client app and the WebView
// provider. // 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 @Rule
public IntentsTestRule mRule = new IntentsTestRule<MainActivity>(MainActivity.class); public IntentsTestRule mRule = new IntentsTestRule<MainActivity>(MainActivity.class);
......
...@@ -4,15 +4,21 @@ ...@@ -4,15 +4,21 @@
package org.chromium.android_webview.test.devui; 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.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.replaceText; import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId; 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 androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf; 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.greaterThan;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
...@@ -22,6 +28,7 @@ import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.withC ...@@ -22,6 +28,7 @@ import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.withC
import android.content.Intent; import android.content.Intent;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule; import android.support.test.rule.ActivityTestRule;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
...@@ -30,41 +37,67 @@ import android.widget.ListView; ...@@ -30,41 +37,67 @@ import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.test.espresso.DataInteraction;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest; import androidx.test.filters.MediumTest;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.UiObject2;
import androidx.test.uiautomator.Until;
import org.hamcrest.Description; import org.hamcrest.Description;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher; import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.chromium.android_webview.common.AwSwitches; 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.FlagsFragment;
import org.chromium.android_webview.devui.MainActivity; import org.chromium.android_webview.devui.MainActivity;
import org.chromium.android_webview.devui.R; import org.chromium.android_webview.devui.R;
import org.chromium.android_webview.services.DeveloperUiService;
import org.chromium.android_webview.test.AwActivityTestRule;
import org.chromium.android_webview.test.AwJUnit4ClassRunner; 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.CallbackHelper;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
import org.chromium.content_public.browser.test.util.TestThreadUtils; import org.chromium.content_public.browser.test.util.TestThreadUtils;
/** /**
* UI tests for {@link FlagsFragment}. * 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) @RunWith(AwJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
public class FlagsFragmentTest { public class FlagsFragmentTest {
@Rule @Rule
public ActivityTestRule mRule = public ActivityTestRule mRule =
new ActivityTestRule<MainActivity>(MainActivity.class, false, false); new ActivityTestRule<MainActivity>(MainActivity.class, false, false);
private UiDevice mUiDevice;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
Intent intent = new Intent(); Intent intent = new Intent();
intent.putExtra(MainActivity.FRAGMENT_ID_INTENT_EXTRA, MainActivity.FRAGMENT_ID_FLAGS); intent.putExtra(MainActivity.FRAGMENT_ID_INTENT_EXTRA, MainActivity.FRAGMENT_ID_FLAGS);
mRule.launchActivity(intent); mRule.launchActivity(intent);
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}
@After
public void tearDown() {
// Make sure to clear shared preferences between tests to avoid any saved state.
DeveloperUiService.clearSharedPrefsForTesting(InstrumentationRegistry.getTargetContext());
// Always press home at the end of the each test to reset the device current view. So if a
// test fails leaving for example the notification drawer opened, it will not cause other
// following tests to fail for not getting focus.
mUiDevice.pressHome();
} }
private CallbackHelper getFlagUiSearchBarListener() { private CallbackHelper getFlagUiSearchBarListener() {
...@@ -318,4 +351,147 @@ public class FlagsFragmentTest { ...@@ -318,4 +351,147 @@ public class FlagsFragmentTest {
onView(withId(R.id.flag_search_bar)) onView(withId(R.id.flag_search_bar))
.check(matches(compoundDrawableVisible(CompoundDrawable.END))); .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 {
toggleFlag(onData(anything()).inAdapterView(withId(R.id.flags_list)).atPosition(1), true);
Assert.assertFalse(
DeveloperModeUtils.getFlagOverrides(DeveloperUiTest.TEST_WEBVIEW_PACKAGE_NAME)
.isEmpty());
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());
}
@Test
@LargeTest
@Feature({"AndroidWebView"})
public void testPersistantNotification() throws Throwable {
// Test that toggling a flag shows a notification.
toggleFlag(onData(anything()).inAdapterView(withId(R.id.flags_list)).atPosition(1), true);
// Navigate away to home fragment to test that tapping the notification brings us back to
// flags fragment.
onView(withId(R.id.navigation_home)).perform(click());
mUiDevice.openNotification();
mUiDevice.wait(
Until.hasObject(By.textStartsWith(DeveloperUiTest.TEST_WEBVIEW_APPLICATION_LABEL)),
AwActivityTestRule.WAIT_TIMEOUT_MS);
UiObject2 notification =
mUiDevice.findObject(By.textStartsWith(DeveloperUiService.NOTIFICATION_TITLE));
Assert.assertNotNull("notification shoud be found", notification);
notification.click();
onView(withId(R.id.fragment_flags)).check(matches(isDisplayed()));
// Test that resetting flags hides the notification
onView(withId(R.id.reset_flags_button)).perform(click());
mUiDevice.openNotification();
mUiDevice.wait(
Until.gone(By.textStartsWith(DeveloperUiTest.TEST_WEBVIEW_APPLICATION_LABEL)),
AwActivityTestRule.WAIT_TIMEOUT_MS);
notification =
mUiDevice.findObject(By.textStartsWith(DeveloperUiService.NOTIFICATION_TITLE));
Assert.assertNull("notification should be gone", notification);
}
} }
...@@ -54,6 +54,8 @@ ...@@ -54,6 +54,8 @@
</intent-filter> </intent-filter>
</activity> </activity>
<!-- Don't actually try to launch with this alias: it only exists so we can query its enabled state. --> <!-- 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" <activity-alias android:name="org.chromium.android_webview.devui.DeveloperModeState"
android:targetActivity="org.chromium.android_webview.devui.MainActivity" android:targetActivity="org.chromium.android_webview.devui.MainActivity"
android:visibleToInstantApps="true" android:visibleToInstantApps="true"
...@@ -78,6 +80,8 @@ ...@@ -78,6 +80,8 @@
android:grantUriPermissions="true" android:grantUriPermissions="true"
android:process=":webview_apk" /> {# Explicit process required for monochrome compatibility. #} android:process=":webview_apk" /> {# Explicit process required for monochrome compatibility. #}
<!-- Disabled by default, enabled at runtime by Developer UI. --> <!-- 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" <provider android:name="org.chromium.android_webview.services.DeveloperModeContentProvider"
android:visibleToInstantApps="true" android:visibleToInstantApps="true"
android:exported="true" android:exported="true"
......
...@@ -433,10 +433,11 @@ public class FlagsFragment extends DevUiBaseFragment { ...@@ -433,10 +433,11 @@ public class FlagsFragment extends DevUiBaseFragment {
TextView flagName = toggleableFlag.findViewById(R.id.flag_name); TextView flagName = toggleableFlag.findViewById(R.id.flag_name);
if (state == /* STATE_DEFAULT */ 0) { if (state == /* STATE_DEFAULT */ 0) {
// Unset the compound drawable. // Unset the compound drawable.
flagName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); flagName.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
} else { // STATE_ENABLED or STATE_DISABLED } else { // STATE_ENABLED or STATE_DISABLED
// Draws a blue circle to the left of the text. // 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; ...@@ -21,6 +21,8 @@ import android.os.IBinder;
import android.os.Process; import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import androidx.annotation.VisibleForTesting;
import org.chromium.android_webview.common.DeveloperModeUtils; import org.chromium.android_webview.common.DeveloperModeUtils;
import org.chromium.android_webview.common.Flag; import org.chromium.android_webview.common.Flag;
import org.chromium.android_webview.common.FlagOverrideHelper; import org.chromium.android_webview.common.FlagOverrideHelper;
...@@ -51,6 +53,11 @@ public final class DeveloperUiService extends Service { ...@@ -51,6 +53,11 @@ public final class DeveloperUiService extends Service {
private static final int FRAGMENT_ID_CRASHES = 1; private static final int FRAGMENT_ID_CRASHES = 1;
private static final int FRAGMENT_ID_FLAGS = 2; 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(); private static final Object sLock = new Object();
@GuardedBy("sLock") @GuardedBy("sLock")
private static Map<String, Boolean> sOverriddenFlags = new HashMap<>(); private static Map<String, Boolean> sOverriddenFlags = new HashMap<>();
...@@ -221,15 +228,14 @@ public final class DeveloperUiService extends Service { ...@@ -221,15 +228,14 @@ public final class DeveloperUiService extends Service {
notificationIntent.putExtra(FRAGMENT_ID_INTENT_EXTRA, FRAGMENT_ID_FLAGS); notificationIntent.putExtra(FRAGMENT_ID_INTENT_EXTRA, FRAGMENT_ID_FLAGS);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification.Builder builder = Notification.Builder builder = createNotificationBuilder()
createNotificationBuilder() .setContentTitle(NOTIFICATION_TITLE)
.setContentTitle("WARNING: experimental WebView features enabled") .setContentText(NOTIFICATION_CONTENT)
.setContentText("Tap to see experimental features.") .setSmallIcon(android.R.drawable.stat_notify_error)
.setSmallIcon(android.R.drawable.stat_notify_error) .setContentIntent(pendingIntent)
.setContentIntent(pendingIntent) .setOngoing(true)
.setOngoing(true) .setVisibility(Notification.VISIBILITY_PUBLIC)
.setVisibility(Notification.VISIBILITY_PUBLIC) .setTicker(NOTIFICATION_TICKER);
.setTicker("Experimental WebView features enabled");
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
builder = builder builder = builder
...@@ -310,4 +316,14 @@ public final class DeveloperUiService extends Service { ...@@ -310,4 +316,14 @@ public final class DeveloperUiService extends Service {
FlagOverrideHelper helper = new FlagOverrideHelper(ProductionSupportedFlagList.sFlagList); FlagOverrideHelper helper = new FlagOverrideHelper(ProductionSupportedFlagList.sFlagList);
helper.applyFlagOverrides(newFlags); helper.applyFlagOverrides(newFlags);
} }
@VisibleForTesting
public static void clearSharedPrefsForTesting(Context context) {
synchronized (sLock) {
context.getSharedPreferences(DeveloperUiService.SHARED_PREFS_FILE, Context.MODE_PRIVATE)
.edit()
.clear()
.apply();
}
}
} }
...@@ -203,6 +203,7 @@ instrumentation_test_apk("webview_instrumentation_test_apk") { ...@@ -203,6 +203,7 @@ instrumentation_test_apk("webview_instrumentation_test_apk") {
"//third_party/android_deps:androidx_core_core_java", "//third_party/android_deps:androidx_core_core_java",
"//third_party/android_deps:androidx_fragment_fragment_java", "//third_party/android_deps:androidx_fragment_fragment_java",
"//third_party/android_deps:androidx_test_runner_java", "//third_party/android_deps:androidx_test_runner_java",
"//third_party/android_deps:androidx_test_uiautomator_uiautomator_java",
"//third_party/android_deps:com_google_code_findbugs_jsr305_java", "//third_party/android_deps:com_google_code_findbugs_jsr305_java",
"//third_party/android_deps:com_google_guava_failureaccess_java", "//third_party/android_deps:com_google_guava_failureaccess_java",
"//third_party/android_deps:espresso_java", "//third_party/android_deps:espresso_java",
......
...@@ -84,5 +84,15 @@ ...@@ -84,5 +84,15 @@
android:exported="true" /> android:exported="true" />
<service android:name="org.chromium.android_webview.test.services.MockVariationsSeedServer" <service android:name="org.chromium.android_webview.test.services.MockVariationsSeedServer"
android:exported="true" /> android:exported="true" />
<!-- Components for DevTools Flags UI, make sure that any changes in these components reflect
the corresponding original components in nonembedded/java/AndroidManifest.xml -->
<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" />
</application> </application>
</manifest> </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