OLD | NEW |
(Empty) | |
| 1 // Copyright 2017 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 |
| 5 #include "base/callback.h" |
| 6 #include "base/mac/foundation_util.h" |
| 7 #include "base/memory/ref_counted.h" |
| 8 #include "base/strings/utf_string_conversions.h" |
| 9 #include "base/time/time.h" |
| 10 #include "components/autofill/core/common/password_form.h" |
| 11 #include "components/keyed_service/core/service_access_type.h" |
| 12 #include "components/password_manager/core/browser/password_store.h" |
| 13 #include "components/password_manager/core/common/password_manager_pref_names.h" |
| 14 #include "components/prefs/pref_service.h" |
| 15 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 16 #include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h" |
| 17 #import "ios/chrome/browser/ui/settings/password_details_collection_view_control
ler_for_testing.h" |
| 18 #import "ios/chrome/browser/ui/settings/reauthentication_module.h" |
| 19 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
| 20 #include "ios/chrome/browser/ui/tools_menu/tools_menu_constants.h" |
| 21 #import "ios/chrome/browser/ui/util/top_view_controller.h" |
| 22 #include "ios/chrome/grit/ios_strings.h" |
| 23 #import "ios/chrome/test/app/chrome_test_util.h" |
| 24 #include "ios/chrome/test/earl_grey/accessibility_util.h" |
| 25 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h" |
| 26 #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h" |
| 27 #import "ios/chrome/test/earl_grey/chrome_matchers.h" |
| 28 #import "ios/chrome/test/earl_grey/chrome_test_case.h" |
| 29 #include "ui/base/l10n/l10n_util.h" |
| 30 #include "url/gurl.h" |
| 31 |
| 32 #if !defined(__has_feature) || !__has_feature(objc_arc) |
| 33 #error "This file requires ARC support." |
| 34 #endif |
| 35 |
| 36 // This test complements |
| 37 // password_details_collection_view_controller_unittest.mm. Very simple |
| 38 // integration tests and features which are not currently unittestable should |
| 39 // go here, the rest into the unittest. |
| 40 // This test only uses the new UI which allows viewing passwords. |
| 41 // TODO(crbug.com/159166): Remove the above sentence once the new UI is the |
| 42 // default one. |
| 43 |
| 44 using autofill::PasswordForm; |
| 45 using chrome_test_util::ButtonWithAccessibilityLabel; |
| 46 using chrome_test_util::ButtonWithAccessibilityLabelId; |
| 47 using chrome_test_util::NavigationBarDoneButton; |
| 48 |
| 49 namespace { |
| 50 |
| 51 // Matcher for the Settings button in the tools menu. |
| 52 id<GREYMatcher> SettingsButton() { |
| 53 return grey_accessibilityID(kToolsMenuSettingsId); |
| 54 } |
| 55 |
| 56 // Matcher for the Save Passwords cell on the main Settings screen. |
| 57 id<GREYMatcher> PasswordsButton() { |
| 58 return ButtonWithAccessibilityLabelId(IDS_IOS_SAVE_PASSWORDS); |
| 59 } |
| 60 |
| 61 // Matcher for a password entry for |username|. |
| 62 id<GREYMatcher> Entry(NSString* username) { |
| 63 return ButtonWithAccessibilityLabel(username); |
| 64 } |
| 65 |
| 66 // Matcher for the Edit button in Save Passwords view. |
| 67 id<GREYMatcher> EditButton() { |
| 68 return ButtonWithAccessibilityLabelId(IDS_IOS_NAVIGATION_BAR_EDIT_BUTTON); |
| 69 } |
| 70 |
| 71 // Matcher for the Copy password button in Password Details view. |
| 72 id<GREYMatcher> CopyPasswordButton() { |
| 73 return ButtonWithAccessibilityLabelId(IDS_IOS_SETTINGS_PASSWORD_COPY_BUTTON); |
| 74 } |
| 75 |
| 76 } // namespace |
| 77 |
| 78 @interface MockReauthenticationModule : NSObject<ReauthenticationProtocol> |
| 79 |
| 80 @property(nonatomic, assign) BOOL shouldSucceed; |
| 81 |
| 82 @end |
| 83 |
| 84 @implementation MockReauthenticationModule |
| 85 |
| 86 @synthesize shouldSucceed = _shouldSucceed; |
| 87 |
| 88 - (BOOL)canAttemptReauth { |
| 89 return YES; |
| 90 } |
| 91 |
| 92 - (void)attemptReauthWithLocalizedReason:(NSString*)localizedReason |
| 93 handler:(void (^)(BOOL success)) |
| 94 showCopyPasswordsHandler { |
| 95 showCopyPasswordsHandler(_shouldSucceed); |
| 96 } |
| 97 |
| 98 @end |
| 99 |
| 100 // Various tests for the Save Passwords section of the settings. |
| 101 @interface PasswordsSettingsTestCase : ChromeTestCase |
| 102 @end |
| 103 |
| 104 @implementation PasswordsSettingsTestCase |
| 105 |
| 106 // Return pref for saving passwords back to the passed value and restores the |
| 107 // experimental flag for viewing passwords. |
| 108 - (void)passwordsTearDown:(BOOL)defaultPasswordManagementSetting |
| 109 :(NSString*)oldExperiment { |
| 110 ios::ChromeBrowserState* browserState = |
| 111 chrome_test_util::GetOriginalBrowserState(); |
| 112 PrefService* preferences = browserState->GetPrefs(); |
| 113 preferences->SetBoolean( |
| 114 password_manager::prefs::kPasswordManagerSavingEnabled, |
| 115 defaultPasswordManagementSetting); |
| 116 |
| 117 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| 118 [defaults setObject:oldExperiment forKey:@"EnableViewCopyPasswords"]; |
| 119 } |
| 120 |
| 121 // Sets the preference to allow saving passwords and activates the flag to use |
| 122 // the new UI for viewing passwords in settings. Also, ensures that original |
| 123 // state is restored after the test ends. |
| 124 - (void)scopedEnablePasswordManagementAndViewingUI { |
| 125 // Retrieve the original preference state. |
| 126 ios::ChromeBrowserState* browserState = |
| 127 chrome_test_util::GetOriginalBrowserState(); |
| 128 PrefService* preferences = browserState->GetPrefs(); |
| 129 bool defaultPasswordManagerSavingPref = preferences->GetBoolean( |
| 130 password_manager::prefs::kPasswordManagerSavingEnabled); |
| 131 |
| 132 // Retrieve the experiment setting. |
| 133 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| 134 NSString* oldSetting = [defaults stringForKey:@"EnableViewCopyPasswords"]; |
| 135 |
| 136 // Ensure restoring that on tear-down. |
| 137 __weak PasswordsSettingsTestCase* weakSelf = self; |
| 138 [self setTearDownHandler:^{ |
| 139 [weakSelf passwordsTearDown:defaultPasswordManagerSavingPref:oldSetting]; |
| 140 }]; |
| 141 |
| 142 // Enable saving. |
| 143 preferences->SetBoolean( |
| 144 password_manager::prefs::kPasswordManagerSavingEnabled, true); |
| 145 |
| 146 // Enable viewing passwords in settings. |
| 147 [defaults setObject:@"Enabled" forKey:@"EnableViewCopyPasswords"]; |
| 148 } |
| 149 |
| 150 - (scoped_refptr<password_manager::PasswordStore>)passwordStore { |
| 151 // ServiceAccessType governs behaviour in Incognito: only modifications with |
| 152 // EXPLICIT_ACCESS, which correspond to user's explicit gesture, succeed. |
| 153 // This test does not deal with Incognito, so the value of the argument is |
| 154 // irrelevant. |
| 155 return IOSChromePasswordStoreFactory::GetForBrowserState( |
| 156 chrome_test_util::GetOriginalBrowserState(), |
| 157 ServiceAccessType::EXPLICIT_ACCESS); |
| 158 } |
| 159 |
| 160 // Saves an example form in the store. |
| 161 - (void)saveExamplePasswordForm { |
| 162 PasswordForm example; |
| 163 example.username_value = base::ASCIIToUTF16("user"); |
| 164 example.password_value = base::ASCIIToUTF16("password"); |
| 165 example.origin = GURL("https://example.com"); |
| 166 example.signon_realm = example.origin.spec(); |
| 167 |
| 168 [self passwordStore]->AddLogin(example); |
| 169 // Allow the PasswordStore to process this on the DB thread. |
| 170 [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| 171 } |
| 172 |
| 173 // Removes all credentials stored. |
| 174 - (void)clearPasswordStore { |
| 175 [self passwordStore]->RemoveLoginsCreatedBetween(base::Time(), base::Time(), |
| 176 base::Closure()); |
| 177 // Allow the PasswordStore to process this on the DB thread. |
| 178 [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| 179 } |
| 180 |
| 181 // Opens the passwords page from the NTP. It requires no menus to be open. |
| 182 - (void)openPasswordSettings { |
| 183 // Open settings and verify data in the view controller. |
| 184 [ChromeEarlGreyUI openToolsMenu]; |
| 185 [[EarlGrey selectElementWithMatcher:SettingsButton()] |
| 186 performAction:grey_tap()]; |
| 187 [[EarlGrey selectElementWithMatcher:PasswordsButton()] |
| 188 performAction:grey_tap()]; |
| 189 |
| 190 // Wait for UI components to finish loading. |
| 191 [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| 192 } |
| 193 |
| 194 // Tap back arrow, to get one level higher in settings. |
| 195 - (void)tapBackArrow { |
| 196 [[EarlGrey |
| 197 selectElementWithMatcher:grey_allOf( |
| 198 grey_accessibilityID(@"ic_arrow_back"), |
| 199 grey_accessibilityTrait( |
| 200 UIAccessibilityTraitButton), |
| 201 nil)] performAction:grey_tap()]; |
| 202 |
| 203 // Wait for UI components to finish loading. |
| 204 [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| 205 } |
| 206 |
| 207 // Tap Edit in any settings view. |
| 208 - (void)tapEdit { |
| 209 [[EarlGrey selectElementWithMatcher:EditButton()] performAction:grey_tap()]; |
| 210 |
| 211 // Wait for UI components to finish loading. |
| 212 [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| 213 } |
| 214 |
| 215 // Tap Done in any settings view. |
| 216 - (void)tapDone { |
| 217 [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()] |
| 218 performAction:grey_tap()]; |
| 219 |
| 220 // Wait for UI components to finish loading. |
| 221 [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| 222 } |
| 223 |
| 224 // Verifies the UI elements are accessible on the Passwords page. |
| 225 // TODO(crbug.com/159166): This differs from testAccessibilityOnPasswords in |
| 226 // settings_egtest.mm in that here this tests the new UI (for viewing |
| 227 // passwords), where in settings_egtest.mm the default (old) UI is tested. |
| 228 // Once the new is the default, just remove the test in settings_egtest.mm. |
| 229 - (void)testAccessibilityOnPasswords { |
| 230 [self scopedEnablePasswordManagementAndViewingUI]; |
| 231 |
| 232 // Saving a form is needed for using the "password details" view. |
| 233 [self saveExamplePasswordForm]; |
| 234 |
| 235 [self openPasswordSettings]; |
| 236 chrome_test_util::VerifyAccessibilityForCurrentScreen(); |
| 237 |
| 238 [self tapEdit]; |
| 239 chrome_test_util::VerifyAccessibilityForCurrentScreen(); |
| 240 [self tapDone]; |
| 241 |
| 242 // Inspect "password details" view. |
| 243 [[EarlGrey selectElementWithMatcher:Entry(@"https://example.com, user")] |
| 244 performAction:grey_tap()]; |
| 245 chrome_test_util::VerifyAccessibilityForCurrentScreen(); |
| 246 [self tapBackArrow]; |
| 247 |
| 248 [self tapBackArrow]; |
| 249 [self tapDone]; |
| 250 [self clearPasswordStore]; |
| 251 } |
| 252 |
| 253 // Checks that attempts to copy a password provide appropriate feedback, |
| 254 // both when reauthentication succeeds and when it fails. |
| 255 - (void)testCopyPasswordToast { |
| 256 [self scopedEnablePasswordManagementAndViewingUI]; |
| 257 |
| 258 // Saving a form is needed for using the "password details" view. |
| 259 [self saveExamplePasswordForm]; |
| 260 |
| 261 [self openPasswordSettings]; |
| 262 |
| 263 [[EarlGrey selectElementWithMatcher:Entry(@"https://example.com, user")] |
| 264 performAction:grey_tap()]; |
| 265 |
| 266 // Get the PasswordDetailsCollectionViewController and replace the |
| 267 // reauthentication module with a fake one to avoid being blocked with a |
| 268 // reauth prompt. |
| 269 MockReauthenticationModule* mock_reauthentication_module = |
| 270 [[MockReauthenticationModule alloc] init]; |
| 271 SettingsNavigationController* settings_navigation_controller = |
| 272 base::mac::ObjCCastStrict<SettingsNavigationController>( |
| 273 top_view_controller::TopPresentedViewController()); |
| 274 PasswordDetailsCollectionViewController* |
| 275 password_details_collection_view_controller = |
| 276 base::mac::ObjCCastStrict<PasswordDetailsCollectionViewController>( |
| 277 settings_navigation_controller.topViewController); |
| 278 [password_details_collection_view_controller |
| 279 setReauthenticationModule:mock_reauthentication_module]; |
| 280 |
| 281 // Check the snackbar in case of successful reauthentication. |
| 282 mock_reauthentication_module.shouldSucceed = YES; |
| 283 [[EarlGrey selectElementWithMatcher:CopyPasswordButton()] |
| 284 performAction:grey_tap()]; |
| 285 [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| 286 NSString* snackbarLabel = |
| 287 l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORD_WAS_COPIED_MESSAGE); |
| 288 [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)] |
| 289 assertWithMatcher:grey_notNil()]; |
| 290 |
| 291 // Check the snackbar in case of failed reauthentication. |
| 292 mock_reauthentication_module.shouldSucceed = NO; |
| 293 [[EarlGrey selectElementWithMatcher:CopyPasswordButton()] |
| 294 performAction:grey_tap()]; |
| 295 [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| 296 snackbarLabel = |
| 297 l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORD_WAS_NOT_COPIED_MESSAGE); |
| 298 [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)] |
| 299 assertWithMatcher:grey_notNil()]; |
| 300 |
| 301 [self tapBackArrow]; |
| 302 [self tapBackArrow]; |
| 303 [self tapDone]; |
| 304 [self clearPasswordStore]; |
| 305 } |
| 306 |
| 307 @end |
OLD | NEW |