Chromium Code Reviews| Index: ios/chrome/browser/ui/settings/passwords_settings_egtest.mm |
| diff --git a/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm b/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..0362e76c1a38bacd32209561e452ec555ebdc02f |
| --- /dev/null |
| +++ b/ios/chrome/browser/ui/settings/passwords_settings_egtest.mm |
| @@ -0,0 +1,305 @@ |
| +// Copyright 2017 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. |
| + |
| +#include "base/callback.h" |
| +#include "base/mac/foundation_util.h" |
| +#include "base/memory/ref_counted.h" |
| +#include "base/strings/utf_string_conversions.h" |
| +#include "base/time/time.h" |
| +#include "components/autofill/core/common/password_form.h" |
| +#include "components/keyed_service/core/service_access_type.h" |
| +#include "components/password_manager/core/browser/password_store.h" |
| +#include "components/password_manager/core/common/password_manager_pref_names.h" |
| +#include "components/prefs/pref_service.h" |
| +#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| +#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h" |
| +#import "ios/chrome/browser/ui/settings/password_details_collection_view_controller_for_testing.h" |
| +#import "ios/chrome/browser/ui/settings/reauthentication_module.h" |
| +#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h" |
| +#include "ios/chrome/browser/ui/tools_menu/tools_menu_constants.h" |
| +#import "ios/chrome/browser/ui/util/top_view_controller.h" |
| +#include "ios/chrome/grit/ios_strings.h" |
| +#import "ios/chrome/test/app/chrome_test_util.h" |
| +#include "ios/chrome/test/earl_grey/accessibility_util.h" |
| +#import "ios/chrome/test/earl_grey/chrome_earl_grey.h" |
| +#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h" |
| +#import "ios/chrome/test/earl_grey/chrome_matchers.h" |
| +#import "ios/chrome/test/earl_grey/chrome_test_case.h" |
| +#include "ui/base/l10n/l10n_util.h" |
| +#include "url/gurl.h" |
| + |
| +#if !defined(__has_feature) || !__has_feature(objc_arc) |
| +#error "This file requires ARC support." |
| +#endif |
| + |
| +// This test complements |
| +// password_details_collection_view_controller_unittest.mm. Very simple |
| +// integration tests and features which are not currently unittestable should |
| +// go here, the rest into the unittest. |
| +// This test only uses the new UI which allows viewing passwords. |
| +// TODO(crbug.com/159166): Remove the above sentence once the new UI is the |
| +// default one. |
| + |
| +using autofill::PasswordForm; |
| +using chrome_test_util::ButtonWithAccessibilityLabel; |
| +using chrome_test_util::ButtonWithAccessibilityLabelId; |
| +using chrome_test_util::NavigationBarDoneButton; |
| + |
| +namespace { |
| + |
| +// Matcher for the Settings button in the tools menu. |
| +id<GREYMatcher> SettingsButton() { |
|
sdefresne
2017/05/02 15:30:08
baxley: are those helper methods (used only once)
baxley
2017/05/02 16:20:41
I think some of these fall under the category of a
vabr (Chromium)
2017/05/03 05:41:37
Thanks for raising this and for the detailed answe
|
| + return grey_accessibilityID(kToolsMenuSettingsId); |
| +} |
| + |
| +// Matcher for the Save Passwords cell on the main Settings screen. |
| +id<GREYMatcher> PasswordsButton() { |
| + return ButtonWithAccessibilityLabelId(IDS_IOS_SAVE_PASSWORDS); |
| +} |
| + |
| +// Matcher for a password entry for |username|. |
| +id<GREYMatcher> Entry(NSString* username) { |
| + return ButtonWithAccessibilityLabel(username); |
| +} |
| + |
| +// Matcher for the Edit button in Save Passwords view. |
| +id<GREYMatcher> EditButton() { |
| + return ButtonWithAccessibilityLabelId(IDS_IOS_NAVIGATION_BAR_EDIT_BUTTON); |
| +} |
| + |
| +// Matcher for the Copy password button in Password Details view. |
| +id<GREYMatcher> CopyPasswordButton() { |
| + return ButtonWithAccessibilityLabelId(IDS_IOS_SETTINGS_PASSWORD_COPY_BUTTON); |
| +} |
| + |
| +} // namespace |
| + |
| +@interface MockReauthenticationModule : NSObject<ReauthenticationProtocol> |
| + |
| +@property(nonatomic, assign) BOOL shouldSucceed; |
| + |
| +@end |
| + |
| +@implementation MockReauthenticationModule |
| + |
| +@synthesize shouldSucceed = _shouldSucceed; |
| + |
| +- (BOOL)canAttemptReauth { |
| + return YES; |
| +} |
| + |
| +- (void)attemptReauthWithLocalizedReason:(NSString*)localizedReason |
| + handler:(void (^)(BOOL success)) |
| + showCopyPasswordsHandler { |
| + showCopyPasswordsHandler(_shouldSucceed); |
| +} |
| + |
| +@end |
| + |
| +// Various tests for the Save Passwords section of the settings. |
| +@interface PasswordsSettingsTestCase : ChromeTestCase |
| +@end |
| + |
| +@implementation PasswordsSettingsTestCase |
| + |
| +// Return pref for saving passwords back to the passed value and restores the |
| +// experimental flag for viewing passwords. |
| +- (void)passwordsTearDown:(BOOL)defaultPasswordManagementSetting |
| + :(NSString*)oldExperiment { |
| + ios::ChromeBrowserState* browserState = |
| + chrome_test_util::GetOriginalBrowserState(); |
| + PrefService* preferences = browserState->GetPrefs(); |
| + preferences->SetBoolean( |
| + password_manager::prefs::kPasswordManagerSavingEnabled, |
| + defaultPasswordManagementSetting); |
| + |
| + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| + [defaults setObject:oldExperiment forKey:@"EnableViewCopyPasswords"]; |
| +} |
| + |
| +// Sets the preference to allow saving passwords and activates the flag to use |
| +// the new UI for viewing passwords in settings. Also, ensures that original |
| +// state is restored after the test ends. |
| +- (void)scopedEnablePasswordManagementAndViewingUI { |
| + // Retrieve the original preference state. |
| + ios::ChromeBrowserState* browserState = |
| + chrome_test_util::GetOriginalBrowserState(); |
| + PrefService* preferences = browserState->GetPrefs(); |
| + bool defaultPasswordManagerSavingPref = preferences->GetBoolean( |
| + password_manager::prefs::kPasswordManagerSavingEnabled); |
| + |
| + // Retrieve the experiment setting. |
| + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| + NSString* oldSetting = [defaults stringForKey:@"EnableViewCopyPasswords"]; |
| + |
| + // Ensure restoring that on tear-down. |
| + __weak PasswordsSettingsTestCase* weakSelf = self; |
| + [self setTearDownHandler:^{ |
| + [weakSelf passwordsTearDown:defaultPasswordManagerSavingPref:oldSetting]; |
| + }]; |
| + |
| + // Enable saving. |
| + preferences->SetBoolean( |
| + password_manager::prefs::kPasswordManagerSavingEnabled, true); |
| + |
| + // Enable viewing passwords in settings. |
| + [defaults setObject:@"Enabled" forKey:@"EnableViewCopyPasswords"]; |
| +} |
| + |
| +- (scoped_refptr<password_manager::PasswordStore>)passwordStore { |
| + // ServiceAccessType governs behaviour in Incognito: only modifications with |
| + // EXPLICIT_ACCESS, which correspond to user's explicit gesture, succeed. |
| + // This test does not deal with Incognito, so the value of the argument is |
| + // irrelevant. |
| + return IOSChromePasswordStoreFactory::GetForBrowserState( |
| + chrome_test_util::GetOriginalBrowserState(), |
| + ServiceAccessType::EXPLICIT_ACCESS); |
| +} |
| + |
| +// Saves an example form in the store. |
| +- (void)saveExamplePasswordForm { |
| + PasswordForm example; |
| + example.username_value = base::ASCIIToUTF16("user"); |
| + example.password_value = base::ASCIIToUTF16("password"); |
| + example.origin = GURL("https://example.com"); |
| + example.signon_realm = example.origin.spec(); |
| + |
| + [self passwordStore]->AddLogin(example); |
| + // Allow the PasswordStore to process this on the DB thread. |
| + [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| +} |
| + |
| +// Removes all credentials stored. |
| +- (void)clearPasswordStore { |
| + [self passwordStore]->RemoveLoginsCreatedBetween(base::Time(), base::Time(), |
| + base::Closure()); |
| + // Allow the PasswordStore to process this on the DB thread. |
| + [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| +} |
| + |
| +// Opens the passwords page from the NTP. It requires no menus to be open. |
| +- (void)openPasswordSettings { |
| + // Open settings and verify data in the view controller. |
| + [ChromeEarlGreyUI openToolsMenu]; |
| + [[EarlGrey selectElementWithMatcher:SettingsButton()] |
| + performAction:grey_tap()]; |
| + [[EarlGrey selectElementWithMatcher:PasswordsButton()] |
| + performAction:grey_tap()]; |
| + |
| + // Wait for UI components to finish loading. |
| + [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| +} |
| + |
| +// Tap back arrow, to get one level higher in settings. |
| +- (void)tapBackArrow { |
| + [[EarlGrey |
| + selectElementWithMatcher:grey_allOf( |
| + grey_accessibilityID(@"ic_arrow_back"), |
| + grey_accessibilityTrait( |
| + UIAccessibilityTraitButton), |
| + nil)] performAction:grey_tap()]; |
| + |
| + // Wait for UI components to finish loading. |
| + [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| +} |
| + |
| +// Tap Edit in any settings view. |
| +- (void)tapEdit { |
| + [[EarlGrey selectElementWithMatcher:EditButton()] performAction:grey_tap()]; |
| + |
| + // Wait for UI components to finish loading. |
| + [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| +} |
| + |
| +// Tap Done in any settings view. |
| +- (void)tapDone { |
| + [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()] |
| + performAction:grey_tap()]; |
| + |
| + // Wait for UI components to finish loading. |
| + [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| +} |
| + |
| +// Verifies the UI elements are accessible on the Passwords page. |
| +// TODO(crbug.com/159166): This differs from testAccessibilityOnPasswords in |
| +// settings_egtest.mm in that here this tests the new UI (for viewing |
| +// passwords), where in settings_egtest.mm the default (old) UI is tested. |
| +// Once the new is the default, just remove the test in settings_egtest.mm. |
| +- (void)testAccessibilityOnPasswords { |
| + [self scopedEnablePasswordManagementAndViewingUI]; |
| + |
| + // Saving a form is needed for using the "password details" view. |
| + [self saveExamplePasswordForm]; |
| + |
| + [self openPasswordSettings]; |
| + chrome_test_util::VerifyAccessibilityForCurrentScreen(); |
| + |
| + [self tapEdit]; |
| + chrome_test_util::VerifyAccessibilityForCurrentScreen(); |
| + [self tapDone]; |
| + |
| + // Inspect "password details" view. |
| + [[EarlGrey selectElementWithMatcher:Entry(@"https://example.com, user")] |
| + performAction:grey_tap()]; |
| + chrome_test_util::VerifyAccessibilityForCurrentScreen(); |
| + [self tapBackArrow]; |
| + |
| + [self tapBackArrow]; |
| + [self tapDone]; |
| + [self clearPasswordStore]; |
| +} |
| + |
| +// Checks that attempts to copy a password provide appropriate feedback, |
| +// both when reauthentication succeeds and when it fails. |
| +- (void)testCopyPasswordToast { |
| + [self scopedEnablePasswordManagementAndViewingUI]; |
| + |
| + // Saving a form is needed for using the "password details" view. |
| + [self saveExamplePasswordForm]; |
| + |
| + [self openPasswordSettings]; |
| + |
| + [[EarlGrey selectElementWithMatcher:Entry(@"https://example.com, user")] |
| + performAction:grey_tap()]; |
| + |
| + // Get the PasswordDetailsCollectionViewController and replace the |
| + // reauthentication module with a fake one to avoid being blocked with a |
| + // reauth prompt. |
| + MockReauthenticationModule* reauth = |
|
sdefresne
2017/05/02 15:30:08
I think that the styleguide discourage uses of abb
vabr (Chromium)
2017/05/03 05:41:37
Good point, done.
|
| + [[MockReauthenticationModule alloc] init]; |
| + SettingsNavigationController* snc = |
| + base::mac::ObjCCastStrict<SettingsNavigationController>( |
| + top_view_controller::TopPresentedViewController()); |
| + PasswordDetailsCollectionViewController* pdcvc = |
| + base::mac::ObjCCastStrict<PasswordDetailsCollectionViewController>( |
| + snc.topViewController); |
| + [pdcvc setReauthenticationModule:reauth]; |
| + |
| + // Check the snackbar in case of successful reauthentication. |
| + reauth.shouldSucceed = YES; |
| + [[EarlGrey selectElementWithMatcher:CopyPasswordButton()] |
| + performAction:grey_tap()]; |
| + [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| + NSString* snackbarLabel = |
| + l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORD_WAS_COPIED_MESSAGE); |
| + [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)] |
| + assertWithMatcher:grey_notNil()]; |
| + |
| + // Check the snackbar in case of failed reauthentication. |
| + reauth.shouldSucceed = NO; |
| + [[EarlGrey selectElementWithMatcher:CopyPasswordButton()] |
| + performAction:grey_tap()]; |
| + [[GREYUIThreadExecutor sharedInstance] drainUntilIdle]; |
| + snackbarLabel = |
| + l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORD_WAS_NOT_COPIED_MESSAGE); |
| + [[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(snackbarLabel)] |
| + assertWithMatcher:grey_notNil()]; |
| + |
| + [self tapBackArrow]; |
| + [self tapBackArrow]; |
| + [self tapDone]; |
| + [self clearPasswordStore]; |
| +} |
| + |
| +@end |