// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/ui/settings/password/password_manager_egtest_utils.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/password_settings_constants.h"
#import "ios/chrome/browser/ui/settings/password/password_settings_app_interface.h"
#import "ios/chrome/browser/ui/settings/password/passwords_in_other_apps/constants.h"
#import "ios/chrome/browser/ui/settings/password/passwords_in_other_apps/passwords_in_other_apps_app_interface.h"
#import "ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h"
#import "ios/chrome/browser/ui/settings/settings_root_table_constants.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.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"
#import "ios/chrome/test/earl_grey/earl_grey_scoped_block_swizzler.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util.h"
using chrome_test_util::SettingsDoneButton;
using chrome_test_util::SettingsMenuBackButton;
namespace {
// Checks if the current device is running iOS 16 and above. This may seem
// overly verbose, but the @available guard needs to be wrapped in an if() or
// else the compiler complains.
bool isIOS16AndAbove() {
if (@available(iOS 16, *)) {
return true;
}
return false;
}
// Matcher for view
id<GREYMatcher> PasswordsInOtherAppsViewMatcher() {
return grey_accessibilityID(kPasswordsInOtherAppsViewAccessibilityIdentifier);
}
// Matcher for title.
id<GREYMatcher> PasswordsInOtherAppsTitleMatcher() {
return grey_accessibilityID(
kPasswordsInOtherAppsTitleAccessibilityIdentifier);
}
// Matcher for subtitle.
id<GREYMatcher> PasswordsInOtherAppsSubtitleMatcher() {
return grey_accessibilityID(
kPasswordsInOtherAppsSubtitleAccessibilityIdentifier);
}
// Matcher for banner image.
id<GREYMatcher> PasswordsInOtherAppsImageMatcher() {
return grey_accessibilityID(
kPasswordsInOtherAppsImageAccessibilityIdentifier);
}
// Matcher for the cell item in Password Settings page.
id<GREYMatcher> PasswordsInOtherAppsListItemMatcher() {
return grey_accessibilityID(kPasswordSettingsPasswordsInOtherAppsRowId);
}
// Matcher for turn off instructions.
id<GREYMatcher> PasswordsInOtherAppsTurnOffInstruction() {
return grey_text(
isIOS16AndAbove()
? @"To turn off, open Settings and go to Password Options."
: @"To turn off, open Settings and go to AutoFill Passwords.");
}
// Matcher for the Show password button in Password Details view.
id<GREYMatcher> OpenSettingsButton() {
return grey_accessibilityID(
kPasswordsInOtherAppsActionAccessibilityIdentifier);
}
// Action to open the Passwords in Other Apps modal from Chrome root view.
void OpensPasswordsInOtherApps() {
[ChromeEarlGreyUI openSettingsMenu];
[ChromeEarlGreyUI
tapSettingsMenuButton:chrome_test_util::SettingsMenuPasswordsButton()];
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(
kSettingsToolbarSettingsButtonId)]
performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsListItemMatcher()]
performAction:grey_tap()];
}
} // namespace
// This test tests overall behaviors and interactions of Passwords In Other Apps
// view controller.
@interface PasswordsInOtherAppsTestCase : ChromeTestCase
@end
@implementation PasswordsInOtherAppsTestCase {
// A swizzler to observe fake auto-fill status instead of real one.
std::unique_ptr<EarlGreyScopedBlockSwizzler> _passwordAutoFillStatusSwizzler;
}
- (void)setUp {
[super setUp];
_passwordAutoFillStatusSwizzler =
std::make_unique<EarlGreyScopedBlockSwizzler>(
@"PasswordAutoFillStatusManager", @"sharedManager",
[PasswordsInOtherAppsAppInterface
swizzlePasswordAutoFillStatusManagerWithFake]);
// Mock successful reauth when opening the Password Manager.
[PasswordSettingsAppInterface setUpMockReauthenticationModule];
[PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
ReauthenticationResult::kSuccess];
}
- (void)tearDown {
[super tearDown];
[PasswordsInOtherAppsAppInterface resetManager];
_passwordAutoFillStatusSwizzler.reset();
// Remove mock to keep the app in the same state as before running the test.
[PasswordSettingsAppInterface removeMockReauthenticationModule];
}
#pragma mark - helper functions
// Tests that the banner image, title and subtitle are visible.
- (void)checkThatCommonElementsAreVisible {
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsTitleMatcher()]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsSubtitleMatcher()]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsImageMatcher()]
assertWithMatcher:grey_minimumVisiblePercent(0.2)];
}
// Tests that instructions to turn on Chrome auto-fill is visible.
- (void)checkThatTurnOnInstructionsAreVisible {
NSArray<NSString*>* steps = @[
ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET
? l10n_util::GetNSString(
IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_1_IPAD)
: l10n_util::GetNSString(
IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_1_IPHONE),
l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_2),
l10n_util::GetNSString(
isIOS16AndAbove()
? IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_3_IOS16
: IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_3),
l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_4)
];
for (NSString* step in steps) {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(step)]
assertWithMatcher:grey_sufficientlyVisible()];
}
[[EarlGrey selectElementWithMatcher:OpenSettingsButton()]
assertWithMatcher:grey_notVisible()];
}
// Tests that instructions to turn on Chrome auto-fill is invisible.
- (void)checkThatTurnOnInstructionsAreNotVisible {
NSArray<NSString*>* steps = @[
ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET
? l10n_util::GetNSString(
IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_1_IPAD)
: l10n_util::GetNSString(
IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_1_IPHONE),
l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_2),
l10n_util::GetNSString(
isIOS16AndAbove()
? IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_3_IOS16
: IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_3),
l10n_util::GetNSString(IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_STEP_4)
];
for (NSString* step in steps) {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(step)]
assertWithMatcher:grey_notVisible()];
}
}
// Tests that instructions to turn off Chrome auto-fill is visible.
- (void)checkThatTurnOffInstructionsAreVisible {
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsTurnOffInstruction()]
assertWithMatcher:grey_sufficientlyVisible()];
}
// Tests that instructions to turn off Chrome auto-fill is invisible.
- (void)checkThatTurnOffInstructionsAreNotVisible {
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsTurnOffInstruction()]
assertWithMatcher:grey_notVisible()];
}
#pragma mark - Test cases
// Tests Passwords In Other Apps first shows instructions when auto-fill is off,
// then shows the caption label after auto-fill is turned on.
- (void)testTurnOnPasswordsInOtherApps {
// Rewrites passwordInAppsViewController.useShortInstruction property.
EarlGreyScopedBlockSwizzler longInstruction(
@"PasswordsInOtherAppsViewController", @"useShortInstruction", ^{
return NO;
});
[PasswordsInOtherAppsAppInterface startFakeManagerWithAutoFillStatus:NO];
OpensPasswordsInOtherApps();
[self checkThatCommonElementsAreVisible];
[self checkThatTurnOnInstructionsAreVisible];
[self checkThatTurnOffInstructionsAreNotVisible];
[PasswordsInOtherAppsAppInterface setAutoFillStatus:YES];
[self checkThatTurnOnInstructionsAreNotVisible];
[self checkThatTurnOffInstructionsAreVisible];
}
// Tests Passwords In Other Apps first shows instructions when auto-fill is on,
// then shows the caption label after auto-fill is turned off.
- (void)testTurnOffPasswordsInOtherApps {
// Rewrites passwordInAppsViewController.useShortInstruction property.
EarlGreyScopedBlockSwizzler longInstruction(
@"PasswordsInOtherAppsViewController", @"useShortInstruction", ^{
return NO;
});
[PasswordsInOtherAppsAppInterface startFakeManagerWithAutoFillStatus:YES];
OpensPasswordsInOtherApps();
[self checkThatCommonElementsAreVisible];
[self checkThatTurnOffInstructionsAreVisible];
[self checkThatTurnOnInstructionsAreNotVisible];
[PasswordsInOtherAppsAppInterface setAutoFillStatus:NO];
[self checkThatTurnOffInstructionsAreNotVisible];
[self checkThatTurnOnInstructionsAreVisible];
}
// Tests Passwords In Other Apps shows instructions when auto-fill is off with
// short instruction.
- (void)testShowPasswordsInOtherAppsWithShortInstruction {
// Rewrites passwordInAppsViewController.useShortInstruction property.
EarlGreyScopedBlockSwizzler shortInstruction(
@"PasswordsInOtherAppsViewController", @"useShortInstruction", ^{
return YES;
});
[PasswordsInOtherAppsAppInterface startFakeManagerWithAutoFillStatus:NO];
OpensPasswordsInOtherApps();
// Check both turn off instructions and default turn on instructions aren't
// visible.
[self checkThatCommonElementsAreVisible];
[self checkThatTurnOnInstructionsAreNotVisible];
[self checkThatTurnOffInstructionsAreNotVisible];
// Check backup instructions are visible.
NSArray<NSString*>* steps = @[
l10n_util::GetNSString(
isIOS16AndAbove()
? IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_SHORTENED_STEP_1_IOS16
: IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_SHORTENED_STEP_1),
l10n_util::GetNSString(
IDS_IOS_SETTINGS_PASSWORDS_IN_OTHER_APPS_SHORTENED_STEP_2)
];
for (NSString* step in steps) {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(step)]
assertWithMatcher:grey_sufficientlyVisible()];
}
[[EarlGrey selectElementWithMatcher:OpenSettingsButton()]
assertWithMatcher:grey_interactable()];
}
// Tests Passwords In Other Apps shows instructions when auto-fill state is
// unknown.
- (void)testOpenPasswordsInOtherAppsWithAutoFillUnknown {
OpensPasswordsInOtherApps();
[self checkThatCommonElementsAreVisible];
[self checkThatTurnOffInstructionsAreNotVisible];
[[EarlGrey
selectElementWithMatcher:grey_allOf(grey_kindOfClassName(
@"UIActivityIndicatorView"),
grey_sufficientlyVisible(), nil)]
assertWithMatcher:grey_notNil()];
// Simulate status retrieved.
[PasswordsInOtherAppsAppInterface startFakeManagerWithAutoFillStatus:NO];
[[EarlGrey
selectElementWithMatcher:grey_allOf(grey_kindOfClassName(
@"UIActivityIndicatorView"),
grey_sufficientlyVisible(), nil)]
assertWithMatcher:grey_nil()];
}
// Tests Passwords In Other Apps dismisses itself when top right "done" button
// is tapped.
- (void)testTapPasswordsInOtherAppsDoneButtonToDismiss {
OpensPasswordsInOtherApps();
[self checkThatCommonElementsAreVisible];
// Taps done button and check settings dismissed.
[[EarlGrey
selectElementWithMatcher:grey_allOf(SettingsDoneButton(),
grey_sufficientlyVisible(), nil)]
performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsViewMatcher()]
assertWithMatcher:grey_notVisible()];
}
// Tests Passwords In Other Apps dismisses itself when the user swipes down.
- (void)testSwipeDownPasswordsInOtherAppsToDismiss {
OpensPasswordsInOtherApps();
[self checkThatCommonElementsAreVisible];
// Swipes down and check settings dismissed.
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsViewMatcher()]
performAction:grey_swipeFastInDirection(kGREYDirectionDown)];
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsViewMatcher()]
assertWithMatcher:grey_notVisible()];
}
// Tests Passwords In Other Apps doesn't show the image on iPhone landscape
// mode, while showing it for iPad.
- (void)testImageVisibilityForLandscapeMode {
OpensPasswordsInOtherApps();
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsImageMatcher()]
assertWithMatcher:grey_minimumVisiblePercent(0.2)];
[EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
error:nil];
if (ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_PHONE) {
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsImageMatcher()]
assertWithMatcher:grey_notVisible()];
} else {
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsImageMatcher()]
assertWithMatcher:grey_minimumVisiblePercent(0.2)];
}
[EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsImageMatcher()]
assertWithMatcher:grey_minimumVisiblePercent(0.2)];
}
// Tests that the Password Manager UI is dismissed after failed local
// authentication while in Passwords In Other Apps.
- (void)testTapPasswordsInOtherAppsWithFailedAuth {
OpensPasswordsInOtherApps();
[PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
ReauthenticationResult::kFailure];
[PasswordSettingsAppInterface
mockReauthenticationModuleShouldReturnSynchronously:NO];
[[AppLaunchManager sharedManager] backgroundAndForegroundApp];
// Passwords in Other Apps should be covered by Reauthentication UI.
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsViewMatcher()]
assertWithMatcher:grey_notVisible()];
[[EarlGrey selectElementWithMatcher:password_manager_test_utils::
ReauthenticationController()]
assertWithMatcher:grey_sufficientlyVisible()];
[PasswordSettingsAppInterface mockReauthenticationModuleReturnMockedResult];
// The Password Manager UI should have been dismissed leaving Settings
// visible.
[[EarlGrey
selectElementWithMatcher:chrome_test_util::SettingsCollectionView()]
assertWithMatcher:grey_sufficientlyVisible()];
[[EarlGrey selectElementWithMatcher:PasswordsInOtherAppsViewMatcher()]
assertWithMatcher:grey_notVisible()];
[[EarlGrey selectElementWithMatcher:password_manager_test_utils::
ReauthenticationController()]
assertWithMatcher:grey_notVisible()];
}
@end