1 // Copyright 2020 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 package org.chromium.chrome.browser.password_manager.settings; 5 6 import static android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; 7 8 import static androidx.test.espresso.Espresso.onView; 9 import static androidx.test.espresso.action.ViewActions.click; 10 import static androidx.test.espresso.action.ViewActions.typeText; 11 import static androidx.test.espresso.assertion.ViewAssertions.matches; 12 import static androidx.test.espresso.matcher.ViewMatchers.withId; 13 import static androidx.test.espresso.matcher.ViewMatchers.withText; 14 15 import static org.mockito.Mockito.verify; 16 17 import static org.chromium.base.test.util.CriteriaHelper.pollUiThread; 18 import static org.chromium.chrome.browser.password_manager.settings.PasswordSettingsTest.withSaveMenuIdOrText; 19 20 import android.os.Bundle; 21 import android.view.View; 22 import android.widget.EditText; 23 24 import androidx.test.espresso.matcher.BoundedMatcher; 25 import androidx.test.filters.SmallTest; 26 27 import org.hamcrest.Description; 28 import org.hamcrest.Matcher; 29 import org.junit.Before; 30 import org.junit.Rule; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 import org.mockito.Mock; 34 import org.mockito.MockitoAnnotations; 35 36 import org.chromium.base.test.util.CommandLineFlags; 37 import org.chromium.chrome.R; 38 import org.chromium.chrome.browser.flags.ChromeSwitches; 39 import org.chromium.chrome.browser.settings.SettingsActivityTestRule; 40 import org.chromium.chrome.test.ChromeJUnit4ClassRunner; 41 42 /** 43 * View tests for the password entry editor screen. 44 */ 45 @RunWith(ChromeJUnit4ClassRunner.class) 46 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) 47 public class PasswordEntryEditorTest { 48 private static final String URL = "https://example.com"; 49 private static final String USERNAME = "test user"; 50 private static final String PASSWORD = "passw0rd"; 51 52 @Mock 53 private PasswordEditingDelegate mMockPasswordEditingDelegate; 54 55 private PasswordEntryEditor mPasswordEntryEditor; 56 57 @Rule 58 public SettingsActivityTestRule<PasswordEntryEditor> mTestRule = 59 new SettingsActivityTestRule<>(PasswordEntryEditor.class); 60 61 @Before setUp()62 public void setUp() throws InterruptedException { 63 MockitoAnnotations.initMocks(this); 64 PasswordEditingDelegateProvider.getInstance().setPasswordEditingDelegate( 65 mMockPasswordEditingDelegate); 66 67 launchEditor(); 68 pollUiThread(() -> mPasswordEntryEditor != null); 69 } 70 /** 71 * Check that the password editing activity displays the data received through arguments. 72 */ 73 @Test 74 @SmallTest testPasswordDataDisplayedInEditingActivity()75 public void testPasswordDataDisplayedInEditingActivity() { 76 PasswordEditingDelegateProvider.getInstance().setPasswordEditingDelegate( 77 mMockPasswordEditingDelegate); 78 79 onView(withId(R.id.site_edit)).check(matches(withText(URL))); 80 onView(withId(R.id.username_edit)).check(matches(withText(USERNAME))); 81 onView(withId(R.id.password_edit)).check(matches(withText(PASSWORD))); 82 } 83 84 /** 85 * Check that the password editing method from the PasswordEditingDelegate was called when the 86 * save button in the password editing activity was clicked. 87 */ 88 @Test 89 @SmallTest testPasswordEditingMethodWasCalled()90 public void testPasswordEditingMethodWasCalled() throws Exception { 91 PasswordEditingDelegateProvider.getInstance().setPasswordEditingDelegate( 92 mMockPasswordEditingDelegate); 93 onView(withId(R.id.username_edit)).perform(typeText(" new")); 94 95 onView(withSaveMenuIdOrText()).perform(click()); 96 97 verify(mMockPasswordEditingDelegate).editSavedPasswordEntry(USERNAME + " new", PASSWORD); 98 } 99 100 /** 101 * Check that the stored password is visible after clicking the unmasking icon and invisible 102 * after another click. 103 */ 104 @Test 105 @SmallTest testStoredPasswordCanBeUnmaskedAndMaskedAgain()106 public void testStoredPasswordCanBeUnmaskedAndMaskedAgain() { 107 ReauthenticationManager.setApiOverride(ReauthenticationManager.OverrideState.AVAILABLE); 108 ReauthenticationManager.setScreenLockSetUpOverride( 109 ReauthenticationManager.OverrideState.AVAILABLE); 110 111 ReauthenticationManager.recordLastReauth( 112 System.currentTimeMillis(), ReauthenticationManager.ReauthScope.ONE_AT_A_TIME); 113 114 // Masked by default 115 onView(withId(R.id.password_edit)).check(matches(isVisiblePasswordInput(false))); 116 117 // Clicking the unmask button shows the password. 118 onView(withId(R.id.password_entry_editor_view_password)).perform(click()); 119 onView(withId(R.id.password_edit)).check(matches(isVisiblePasswordInput(true))); 120 121 // Clicking the mask button hides the password again. 122 onView(withId(R.id.password_entry_editor_view_password)).perform(click()); 123 onView(withId(R.id.password_edit)).check(matches(isVisiblePasswordInput(false))); 124 } 125 launchEditor()126 private void launchEditor() { 127 Bundle fragmentArgs = new Bundle(); 128 fragmentArgs.putString(PasswordEntryEditor.CREDENTIAL_URL, URL); 129 fragmentArgs.putString(PasswordEntryEditor.CREDENTIAL_NAME, USERNAME); 130 fragmentArgs.putString(PasswordEntryEditor.CREDENTIAL_PASSWORD, PASSWORD); 131 mTestRule.startSettingsActivity(fragmentArgs); 132 mPasswordEntryEditor = mTestRule.getFragment(); 133 } 134 135 /** 136 * Matches any {@link EditText} which has the content visibility matching to |shouldBeVisible|. 137 * @return The matcher checking the input type. 138 */ isVisiblePasswordInput(final boolean shouldBeVisible)139 private static Matcher<View> isVisiblePasswordInput(final boolean shouldBeVisible) { 140 return new BoundedMatcher<View, EditText>(EditText.class) { 141 @Override 142 public boolean matchesSafely(EditText editText) { 143 return ((editText.getInputType() & TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) 144 == TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) 145 == shouldBeVisible; 146 } 147 148 @Override 149 public void describeTo(Description description) { 150 if (shouldBeVisible) { 151 description.appendText("The content should be visible."); 152 } else { 153 description.appendText("The content should not be visible."); 154 } 155 } 156 }; 157 } 158 } 159