| // Copyright 2014 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.preferences.password; |
| |
| import static android.support.test.espresso.action.ViewActions.click; |
| import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist; |
| import static android.support.test.espresso.assertion.ViewAssertions.matches; |
| import static android.support.test.espresso.matcher.RootMatchers.withDecorView; |
| import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; |
| import static android.support.test.espresso.matcher.ViewMatchers.isEnabled; |
| import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription; |
| import static android.support.test.espresso.matcher.ViewMatchers.withParent; |
| import static android.support.test.espresso.matcher.ViewMatchers.withText; |
| |
| import static org.hamcrest.Matchers.allOf; |
| import static org.hamcrest.Matchers.containsString; |
| import static org.hamcrest.Matchers.not; |
| |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.espresso.Espresso; |
| import android.support.test.filters.SmallTest; |
| |
| import org.junit.Assert; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.RunWith; |
| |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.test.BaseJUnit4ClassRunner; |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.PasswordManagerHandler; |
| import org.chromium.chrome.browser.SavedPasswordEntry; |
| import org.chromium.chrome.browser.preferences.ChromeBaseCheckBoxPreference; |
| import org.chromium.chrome.browser.preferences.ChromeSwitchPreference; |
| import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| import org.chromium.chrome.browser.preferences.Preferences; |
| import org.chromium.chrome.browser.preferences.PreferencesTest; |
| import org.chromium.chrome.browser.test.ChromeBrowserTestRule; |
| import org.chromium.chrome.test.util.browser.Features; |
| import org.chromium.chrome.test.util.browser.Features.EnableFeatures; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Tests for the "Save Passwords" settings screen. |
| */ |
| @RunWith(BaseJUnit4ClassRunner.class) |
| public class SavePasswordsPreferencesTest { |
| @Rule |
| public final ChromeBrowserTestRule mBrowserTestRule = new ChromeBrowserTestRule(); |
| |
| @Rule |
| public TestRule mProcessor = new Features.InstrumentationProcessor(); |
| |
| private static final class FakePasswordManagerHandler implements PasswordManagerHandler { |
| // This class has exactly one observer, set on construction and expected to last at least as |
| // long as this object (a good candidate is the owner of this object). |
| private final PasswordListObserver mObserver; |
| |
| // The faked contents of the password store to be displayed. |
| private ArrayList<SavedPasswordEntry> mSavedPasswords; |
| |
| public void setSavedPasswords(ArrayList<SavedPasswordEntry> savedPasswords) { |
| mSavedPasswords = savedPasswords; |
| } |
| |
| /** |
| * Constructor. |
| * @param PasswordListObserver The only observer. |
| */ |
| public FakePasswordManagerHandler(PasswordListObserver observer) { |
| mObserver = observer; |
| } |
| |
| // Pretends that the updated lists are |mSavedPasswords| for the saved passwords and an |
| // empty list for exceptions and immediately calls the observer. |
| @Override |
| public void updatePasswordLists() { |
| mObserver.passwordListAvailable(mSavedPasswords.size()); |
| } |
| |
| @Override |
| public SavedPasswordEntry getSavedPasswordEntry(int index) { |
| return mSavedPasswords.get(index); |
| } |
| |
| @Override |
| public String getSavedPasswordException(int index) { |
| // Define this method before starting to use it in tests. |
| assert false; |
| return null; |
| } |
| |
| @Override |
| public void removeSavedPasswordEntry(int index) { |
| // Define this method before starting to use it in tests. |
| assert false; |
| return; |
| } |
| |
| @Override |
| public void removeSavedPasswordException(int index) { |
| // Define this method before starting to use it in tests. |
| assert false; |
| return; |
| } |
| } |
| |
| // Used to provide fake lists of stored passwords. Tests which need it can use setPasswordSource |
| // to instantiate it. |
| FakePasswordManagerHandler mHandler; |
| |
| /** |
| * Helper to set up a fake source of displayed passwords. |
| * @param entry An entry to be added to saved passwords. Can be null. |
| */ |
| private void setPasswordSource(SavedPasswordEntry entry) throws Exception { |
| if (mHandler == null) { |
| mHandler = new FakePasswordManagerHandler(PasswordManagerHandlerProvider.getInstance()); |
| } |
| ArrayList<SavedPasswordEntry> entries = new ArrayList<SavedPasswordEntry>(); |
| if (entry != null) entries.add(entry); |
| mHandler.setSavedPasswords(entries); |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| PasswordManagerHandlerProvider.getInstance().setPasswordManagerHandlerForTest( |
| mHandler); |
| } |
| }); |
| } |
| |
| /** |
| * Ensure that resetting of empty passwords list works. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| public void testResetListEmpty() throws Exception { |
| // Load the preferences, they should show the empty list. |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| SavePasswordsPreferences savePasswordPreferences = |
| (SavePasswordsPreferences) preferences.getFragmentForTest(); |
| // Emulate an update from PasswordStore. This should not crash. |
| savePasswordPreferences.passwordListAvailable(0); |
| } |
| }); |
| } |
| |
| /** |
| * Ensure that the on/off switch in "Save Passwords" settings actually enables and disables |
| * password saving. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| public void testSavePasswordsSwitch() throws Exception { |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| PrefServiceBridge.getInstance().setRememberPasswordsEnabled(true); |
| } |
| }); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| SavePasswordsPreferences savedPasswordPrefs = |
| (SavePasswordsPreferences) preferences.getFragmentForTest(); |
| ChromeSwitchPreference onOffSwitch = (ChromeSwitchPreference) |
| savedPasswordPrefs.findPreference( |
| SavePasswordsPreferences.PREF_SAVE_PASSWORDS_SWITCH); |
| Assert.assertTrue(onOffSwitch.isChecked()); |
| |
| PreferencesTest.clickPreference(savedPasswordPrefs, onOffSwitch); |
| Assert.assertFalse(PrefServiceBridge.getInstance().isRememberPasswordsEnabled()); |
| PreferencesTest.clickPreference(savedPasswordPrefs, onOffSwitch); |
| Assert.assertTrue(PrefServiceBridge.getInstance().isRememberPasswordsEnabled()); |
| |
| preferences.finish(); |
| |
| PrefServiceBridge.getInstance().setRememberPasswordsEnabled(false); |
| } |
| }); |
| |
| final Preferences preferences2 = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| SavePasswordsPreferences savedPasswordPrefs = |
| (SavePasswordsPreferences) preferences2.getFragmentForTest(); |
| ChromeSwitchPreference onOffSwitch = (ChromeSwitchPreference) |
| savedPasswordPrefs.findPreference( |
| SavePasswordsPreferences.PREF_SAVE_PASSWORDS_SWITCH); |
| Assert.assertFalse(onOffSwitch.isChecked()); |
| } |
| }); |
| } |
| |
| /** |
| * Ensure that the "Auto Sign-in" switch in "Save Passwords" settings actually enables and |
| * disables auto sign-in. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| public void testAutoSignInCheckbox() throws Exception { |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| PrefServiceBridge.getInstance().setPasswordManagerAutoSigninEnabled(true); |
| } |
| }); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| SavePasswordsPreferences passwordPrefs = |
| (SavePasswordsPreferences) preferences.getFragmentForTest(); |
| ChromeBaseCheckBoxPreference onOffSwitch = |
| (ChromeBaseCheckBoxPreference) passwordPrefs.findPreference( |
| SavePasswordsPreferences.PREF_AUTOSIGNIN_SWITCH); |
| Assert.assertTrue(onOffSwitch.isChecked()); |
| |
| PreferencesTest.clickPreference(passwordPrefs, onOffSwitch); |
| Assert.assertFalse( |
| PrefServiceBridge.getInstance().isPasswordManagerAutoSigninEnabled()); |
| PreferencesTest.clickPreference(passwordPrefs, onOffSwitch); |
| Assert.assertTrue( |
| PrefServiceBridge.getInstance().isPasswordManagerAutoSigninEnabled()); |
| |
| preferences.finish(); |
| |
| PrefServiceBridge.getInstance().setPasswordManagerAutoSigninEnabled(false); |
| } |
| }); |
| |
| final Preferences preferences2 = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| SavePasswordsPreferences passwordPrefs = |
| (SavePasswordsPreferences) preferences2.getFragmentForTest(); |
| ChromeBaseCheckBoxPreference onOffSwitch = |
| (ChromeBaseCheckBoxPreference) passwordPrefs.findPreference( |
| SavePasswordsPreferences.PREF_AUTOSIGNIN_SWITCH); |
| Assert.assertFalse(onOffSwitch.isChecked()); |
| } |
| }); |
| } |
| |
| /** |
| * Check that if there are no saved passwords, the export menu item is disabled. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| @EnableFeatures("password-export") |
| public void testExportMenuDisabled() throws Exception { |
| // Ensure there are no saved passwords reported to settings. |
| setPasswordSource(null); |
| |
| ReauthenticationManager.setApiOverride(ReauthenticationManager.OverrideState.AVAILABLE); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| Espresso.openActionBarOverflowOrOptionsMenu( |
| InstrumentationRegistry.getInstrumentation().getTargetContext()); |
| // The text matches a text view, but the disabled entity is some wrapper two levels up in |
| // the view hierarchy, hence the two withParent matchers. |
| Espresso.onView(allOf(withText(R.string.save_password_preferences_export_action_title), |
| withParent(withParent(not(isEnabled()))))) |
| .check(matches(isDisplayed())); |
| } |
| |
| /** |
| * Check that if there are saved passwords, the export menu item is enabled. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| @EnableFeatures("password-export") |
| public void testExportMenuEnabled() throws Exception { |
| setPasswordSource(new SavedPasswordEntry("https://example.com", "test user", "password")); |
| |
| ReauthenticationManager.setApiOverride(ReauthenticationManager.OverrideState.AVAILABLE); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| Espresso.openActionBarOverflowOrOptionsMenu( |
| InstrumentationRegistry.getInstrumentation().getTargetContext()); |
| // The text matches a text view, but the potentially disabled entity is some wrapper two |
| // levels up in the view hierarchy, hence the two withParent matchers. |
| Espresso.onView(allOf(withText(R.string.save_password_preferences_export_action_title), |
| withParent(withParent(isEnabled())))) |
| .check(matches(isDisplayed())); |
| } |
| |
| /** |
| * Check that if "password-export" feature is not explicitly enabled, there is no menu item to |
| * export passwords. |
| * TODO(crbug.com/788701): Add the @DisableFeatures annotation once exporting gets enabled by |
| * default, and remove completely once the feature is gone. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| public void testExportMenuMissing() throws Exception { |
| ReauthenticationManager.setApiOverride(ReauthenticationManager.OverrideState.AVAILABLE); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| // Ideally this would need the same matcher (Espresso.OVERFLOW_BUTTON_MATCHER) as used |
| // inside Espresso.openActionBarOverflowOrOptionsMenu(), but that is private to Espresso. |
| // Matching the overflow menu with the class name "OverflowMenuButton" won't work on |
| // obfuscated release builds, so matching the description remains. The |
| // OVERFLOW_BUTTON_MATCHER specifies the string directly, not via string resource, so this |
| // is also done below. |
| Espresso.onView(withContentDescription("More options")).check(doesNotExist()); |
| } |
| |
| /** |
| * Check that the export menu item is included and hidden behind the overflow menu. Check that |
| * the menu displays the warning before letting the user export passwords. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| @EnableFeatures("password-export") |
| public void testExportMenuItem() throws Exception { |
| setPasswordSource(new SavedPasswordEntry("https://example.com", "test user", "password")); |
| |
| ReauthenticationManager.setApiOverride(ReauthenticationManager.OverrideState.AVAILABLE); |
| ReauthenticationManager.setScreenLockSetUpOverride( |
| ReauthenticationManager.OverrideState.AVAILABLE); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| Espresso.openActionBarOverflowOrOptionsMenu( |
| InstrumentationRegistry.getInstrumentation().getTargetContext()); |
| // Before tapping the menu item for export, pretend that the last successful |
| // reauthentication just happened. This will allow the export flow to continue. |
| ReauthenticationManager.setLastReauthTimeMillis(System.currentTimeMillis()); |
| Espresso.onView(withText(R.string.save_password_preferences_export_action_title)) |
| .perform(click()); |
| |
| Espresso.onView(withText(R.string.settings_passwords_export_description)) |
| .check(matches(isDisplayed())); |
| } |
| |
| /** |
| * Check whether the user is asked to set up a screen lock if attempting to export passwords. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| @EnableFeatures("password-export") |
| public void testExportMenuItemNoLock() throws Exception { |
| setPasswordSource(new SavedPasswordEntry("https://example.com", "test user", "password")); |
| |
| ReauthenticationManager.setApiOverride(ReauthenticationManager.OverrideState.AVAILABLE); |
| ReauthenticationManager.setScreenLockSetUpOverride( |
| ReauthenticationManager.OverrideState.UNAVAILABLE); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| Espresso.openActionBarOverflowOrOptionsMenu( |
| InstrumentationRegistry.getInstrumentation().getTargetContext()); |
| Espresso.onView(withText(R.string.save_password_preferences_export_action_title)) |
| .perform(click()); |
| Espresso.onView(withText(R.string.password_export_set_lock_screen)) |
| .inRoot(withDecorView(isEnabled())) |
| .check(matches(isDisplayed())); |
| } |
| |
| /** |
| * Check whether the user is asked to set up a screen lock if attempting to view passwords. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| public void testViewPasswordNoLock() throws Exception { |
| setPasswordSource(new SavedPasswordEntry("https://example.com", "test user", "password")); |
| |
| ReauthenticationManager.setApiOverride(ReauthenticationManager.OverrideState.AVAILABLE); |
| ReauthenticationManager.setScreenLockSetUpOverride( |
| ReauthenticationManager.OverrideState.UNAVAILABLE); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| Espresso.onView(withText(containsString("test user"))).perform(click()); |
| |
| Espresso.onView(withContentDescription(R.string.password_entry_editor_copy_stored_password)) |
| .perform(click()); |
| Espresso.onView(withText(R.string.password_entry_editor_set_lock_screen)) |
| .inRoot(withDecorView(isEnabled())) |
| .check(matches(isDisplayed())); |
| } |
| |
| /** |
| * Check whether the user can view a saved password. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"Preferences"}) |
| public void testViewPassword() throws Exception { |
| setPasswordSource( |
| new SavedPasswordEntry("https://example.com", "test user", "test password")); |
| |
| ReauthenticationManager.setApiOverride(ReauthenticationManager.OverrideState.AVAILABLE); |
| ReauthenticationManager.setScreenLockSetUpOverride( |
| ReauthenticationManager.OverrideState.AVAILABLE); |
| |
| final Preferences preferences = |
| PreferencesTest.startPreferences(InstrumentationRegistry.getInstrumentation(), |
| SavePasswordsPreferences.class.getName()); |
| |
| Espresso.onView(withText(containsString("test user"))).perform(click()); |
| |
| // Before tapping the view button, pretend that the last successful reauthentication just |
| // happened. This will allow the export flow to continue. |
| ReauthenticationManager.setLastReauthTimeMillis(System.currentTimeMillis()); |
| Espresso.onView(withContentDescription(R.string.password_entry_editor_view_stored_password)) |
| .perform(click()); |
| Espresso.onView(withText("test password")).check(matches(isDisplayed())); |
| } |
| } |