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