chromium/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "base/ios/ios_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "components/password_manager/core/browser/password_ui_utils.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/autofill/ui_bundled/autofill_app_interface.h"
#import "ios/chrome/browser/autofill/ui_bundled/form_input_accessory/form_input_accessory_app_interface.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_constants.h"
#import "ios/chrome/browser/autofill/ui_bundled/manual_fill/manual_fill_matchers.h"
#import "ios/chrome/browser/metrics/model/metrics_app_interface.h"
#import "ios/chrome/browser/passwords/ui_bundled/bottom_sheet/password_suggestion_bottom_sheet_app_interface.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/ui/authentication/signin_earl_grey.h"
#import "ios/chrome/browser/ui/authentication/signin_earl_grey_ui_test_util.h"
#import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
#import "ios/chrome/browser/ui/settings/password/password_details/password_details_table_view_constants.h"
#import "ios/chrome/browser/ui/settings/password/password_manager_egtest_utils.h"
#import "ios/chrome/browser/ui/settings/password/password_settings_app_interface.h"
#import "ios/chrome/browser/ui/settings/password/passwords_table_view_constants.h"
#import "ios/chrome/common/ui/elements/form_input_accessory_view.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/earl_grey/chrome_actions.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/testing/earl_grey/earl_grey_test.h"
#import "ios/web/public/test/element_selector.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "ui/base/l10n/l10n_util_mac.h"
#import "url/gurl.h"

using chrome_test_util::ButtonWithAccessibilityLabelId;
using chrome_test_util::CancelButton;
using chrome_test_util::NavigationBarCancelButton;
using chrome_test_util::NavigationBarDoneButton;
using chrome_test_util::SettingsAccountButton;
using chrome_test_util::SettingsDoneButton;
using chrome_test_util::SettingsPasswordMatcher;
using chrome_test_util::SettingsPasswordSearchMatcher;
using chrome_test_util::StaticTextWithAccessibilityLabelId;
using chrome_test_util::TapWebElementWithId;
using chrome_test_util::TapWebElementWithIdInFrame;
using chrome_test_util::UseSuggestedPasswordMatcher;

namespace {

const char kFormElementUsername[] = "username";
const char kFormElementPassword[] = "password";

NSString* const kPassphrase = @"hello";

const char kExampleUsername[] = "concrete username";

const char kFormHTMLFile[] = "/username_password_field_form.html";
const char kIFrameHTMLFile[] = "/iframe_form.html";

// Returns a matcher for the example username in the list.
id<GREYMatcher> UsernameButtonMatcher() {
  return grey_buttonTitle(base::SysUTF8ToNSString(kExampleUsername));
}

// Matcher for the not secure website alert.
id<GREYMatcher> NotSecureWebsiteAlert() {
  return StaticTextWithAccessibilityLabelId(
      IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_TITLE);
}

// Matcher for the confirmation dialog Continue button.
id<GREYMatcher> ConfirmUsingOtherPasswordButton() {
  return grey_allOf(ButtonWithAccessibilityLabelId(
                        IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_CONTINUE),
                    grey_interactable(), nullptr);
}

// Matcher for the confirmation dialog Cancel button.
id<GREYMatcher> CancelUsingOtherPasswordButton() {
  return grey_allOf(ButtonWithAccessibilityLabelId(IDS_CANCEL),
                    grey_interactable(), nullptr);
}

// Matcher for the expanded password manual fill view button.
id<GREYMatcher> PasswordManualFillViewButton() {
  return grey_allOf(grey_accessibilityLabel(l10n_util::GetNSString(
                        IDS_IOS_AUTOFILL_PASSWORD_AUTOFILL_DATA)),
                    grey_ancestor(grey_accessibilityID(
                        kFormInputAccessoryViewAccessibilityID)),
                    nil);
}

// Matcher for the overflow menu button shown in the password cells.
id<GREYMatcher> OverflowMenuButton() {
  return grey_allOf(
      grey_accessibilityID(manual_fill::kExpandedManualFillOverflowMenuID),
      grey_interactable(), nullptr);
}

// Matcher for the "Edit" action made available by the overflow menu button.
id<GREYMatcher> OverflowMenuEditAction() {
  return grey_allOf(ButtonWithAccessibilityLabelId(IDS_IOS_EDIT_ACTION_TITLE),
                    grey_interactable(), nullptr);
}

// Matcher for the "Autofill Form" button shown in the password cells.
id<GREYMatcher> AutofillFormButton() {
  return grey_allOf(grey_accessibilityID(
                        manual_fill::kExpandedManualFillAutofillFormButtonID),
                    grey_interactable(), nullptr);
}

// Matcher for the page showing the details of a password.
id<GREYMatcher> PasswordDetailsPage() {
  return grey_accessibilityID(kPasswordDetailsViewControllerID);
}

// Opens the password manual fill view and verifies that the password view
// controller is visible afterwards.
void OpenPasswordManualFillView(bool has_suggestions) {
  id<GREYMatcher> button_to_tap;
  if ([AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    button_to_tap = has_suggestions
                        ? grey_accessibilityLabel(l10n_util::GetNSString(
                              IDS_IOS_AUTOFILL_ACCNAME_AUTOFILL_DATA))
                        : PasswordManualFillViewButton();
  } else {
    button_to_tap = manual_fill::PasswordIconMatcher();
    [[EarlGrey
        selectElementWithMatcher:manual_fill::FormSuggestionViewMatcher()]
        performAction:grey_scrollToContentEdge(kGREYContentEdgeRight)];
  }

  // Tap the button that'll open the password manual fill view.
  [[EarlGrey selectElementWithMatcher:button_to_tap] performAction:grey_tap()];

  // Verify the password controller table view is visible.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordTableViewMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Verifies that the number of accepted suggestions recorded for the given
// `suggestion_index` is as expected. `from_all_password_context` indicates
// whether the suggestion was accpeted form the all password list.
void CheckAutofillSuggestionAcceptedIndexMetricsCount(
    NSInteger suggestion_index,
    bool from_all_password_context = false) {
  NSString* histogram =
      @"Autofill.UserAcceptedSuggestionAtIndex.Password.ManualFallback";
  NSString* error_message = @"Unexpected histogram count for manual fallback "
                            @"accepted password suggestion index.";

  if (from_all_password_context) {
    histogram = [NSString stringWithFormat:@"%@.AllPasswords", histogram];
    error_message =
        [NSString stringWithFormat:@"%@ In all password list.", error_message];
  }

  GREYAssertNil(
      [MetricsAppInterface expectUniqueSampleWithCount:1
                                             forBucket:suggestion_index
                                          forHistogram:histogram],
      error_message);
}

// Validates that the Password Manager UI opened from the manual fallback UI is
// dismissed when local authentication fails.
// - manual_fallback_action_matcher: Matcher for the action button opening the
// Password Manager UI (e.g. "Manage Passwords..." button).
void CheckPasswordManagerUIDismissesAfterFailedAuthentication(
    id<GREYMatcher> manual_fallback_action_matcher) {
  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Simulate failed authentication.
  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
                                    ReauthenticationResult::kFailure];
  [PasswordSettingsAppInterface
      mockReauthenticationModuleShouldReturnSynchronously:NO];

  // Tap the action in the manual fallback UI.
  [[EarlGrey selectElementWithMatcher:manual_fallback_action_matcher]
      performAction:grey_tap()];

  // Validate reauth UI is visible until auth result is delivered.
  [[EarlGrey selectElementWithMatcher:password_manager_test_utils::
                                          ReauthenticationController()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Deliver authentication result should dismiss the UI.
  [PasswordSettingsAppInterface mockReauthenticationModuleReturnMockedResult];

  // Verify that the whole navigation stack is gone.
  [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:
                      chrome_test_util::SettingsNavigationBar()];
}

// Checks that the password manual filling option is as expected and visible.
void CheckPasswordFillingOptionIsVisible(NSString* site) {
  [[EarlGrey selectElementWithMatcher:grey_text(site)]
      assertWithMatcher:grey_sufficientlyVisible()];

  [[EarlGrey selectElementWithMatcher:UsernameButtonMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];

  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordButtonMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Verifies that the keyboard is up and not covered by the password manual fill
// view.
void CheckKeyboardIsUpAndNotCovered() {
  // Verify the keyboard is not covered by the profiles view.
  // TODO(crbug.com/332956674): Remove version check once fixed.
  if (@available(iOS 17.4, *)) {
    // Skip verification.
  } else {
    [ChromeEarlGrey waitForKeyboardToAppear];
  }

  [ChromeEarlGrey waitForNotSufficientlyVisibleElementWithMatcher:
                      manual_fill::PasswordTableViewMatcher()];
}

}  // namespace

// Integration Tests for Mannual Fallback Passwords View Controller.
@interface PasswordViewControllerTestCase : ChromeTestCase

// URL of the current page.
@property(assign) GURL URL;

@end

@implementation PasswordViewControllerTestCase

- (BOOL)shouldEnableKeyboardAccessoryUpgradeFeature {
  return YES;
}

- (void)setUp {
  [super setUp];
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
  self.URL = self.testServer->GetURL(kFormHTMLFile);
  [self loadLoginPage];
  [AutofillAppInterface saveExamplePasswordFormToProfileStore];
  [ChromeEarlGrey loadURL:self.URL];
  [ChromeEarlGrey waitForWebStateContainingText:"hello!"];

  // Mock successful reauth for opening the Password Manager.
  [PasswordSettingsAppInterface setUpMockReauthenticationModule];
  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
                                    ReauthenticationResult::kSuccess];

  // Set up histogram tester.
  GREYAssertNil([MetricsAppInterface setupHistogramTester],
                @"Cannot setup histogram tester.");
  [MetricsAppInterface overrideMetricsAndCrashReportingForTesting];
}

- (void)tearDown {
  [AutofillAppInterface clearProfilePasswordStore];
  [PasswordSettingsAppInterface removeMockReauthenticationModule];

  // Clean up histogram tester.
  [MetricsAppInterface stopOverridingMetricsAndCrashReportingForTesting];
  GREYAssertNil([MetricsAppInterface releaseHistogramTester],
                @"Failed to release histogram tester.");
  [super tearDown];
}

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config;

  if ([self shouldEnableKeyboardAccessoryUpgradeFeature]) {
    config.features_enabled.push_back(kIOSKeyboardAccessoryUpgrade);
  } else {
    config.features_disabled.push_back(kIOSKeyboardAccessoryUpgrade);
  }

  return config;
}

- (void)loadLoginPage {
  [ChromeEarlGrey loadURL:self.URL];
  [ChromeEarlGrey waitForWebStateContainingText:"hello!"];
}

// Opens the "Other Passwords" screen.
- (void)openOtherPasswords {
  // Bring up the keyboard.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::WebViewMatcher()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Tap the "Select Password..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::OtherPasswordsMatcher()]
      performAction:grey_tap()];

  std::u16string origin = base::ASCIIToUTF16(
      password_manager::GetShownOrigin(url::Origin::Create(self.URL)));

  NSString* message = l10n_util::GetNSStringF(
      IDS_IOS_MANUAL_FALLBACK_SELECT_PASSWORD_DIALOG_MESSAGE, origin);

  [[EarlGrey selectElementWithMatcher:grey_text(message)]
      assertWithMatcher:grey_notNil()];

  // Acknowledge concerns using other passwords on a website.
  [[EarlGrey selectElementWithMatcher:ConfirmUsingOtherPasswordButton()]
      performAction:grey_tap()];
}

// Tests that the passwords view controller appears on screen.
- (void)testPasswordsViewControllerIsPresented {
  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  // Save password for site.
  NSString* URLString = base::SysUTF8ToNSString(self.URL.spec());
  [AutofillAppInterface savePasswordFormForURLSpec:URLString];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view and verify that the password controller
  // table view is visible.
  OpenPasswordManualFillView(/*has_suggestions=*/true);

  // Verify that the number of visible suggestions in the keyboard accessory was
  // correctly recorded.
  NSString* histogram =
      [AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]
          ? @"ManualFallback.VisibleSuggestions.ExpandIcon.OpenPasswords"
          : @"ManualFallback.VisibleSuggestions.OpenPasswords";
  GREYAssertNil(
      [MetricsAppInterface expectUniqueSampleWithCount:1
                                             forBucket:1
                                          forHistogram:histogram],
      @"Unexpected histogram error for number of visible suggestions.");
}

// Tests that the passwords view controller contains the "Manage Passwords..."
// and "Manage Settings..." actions.
- (void)testPasswordsViewControllerContainsManageActions {
  // TODO(crbug.com/40857537): Re-enable when flake fixed.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"Test flaky failing on iPad.")
  }

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Verify the password controller contains the "Manage Passwords..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::ManagePasswordsMatcher()]
      assertWithMatcher:grey_interactable()];

  // Verify the password controller contains the "Manage Settings..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::ManageSettingsMatcher()]
      assertWithMatcher:grey_interactable()];
}

// Tests that the "Manage Passwords..." action works.
- (void)testManagePasswordsActionOpensPasswordManager {
  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Tap the "Manage Passwords..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::ManagePasswordsMatcher()]
      performAction:grey_tap()];

  // Verify that the Password Manager opened.
  // Changed minimum visible percentage to 70% for Passwords table view in
  // settings because subviews cover > 25% in smaller screens(eg. iPhone 6s).
  [[EarlGrey
      selectElementWithMatcher:grey_accessibilityID(kPasswordsTableViewID)]
      assertWithMatcher:grey_minimumVisiblePercent(0.7)];
}

// Tests that the Password Manager is dismissed when local authentication fails
// after tapping "Manage Passwords...".
- (void)testManagePasswordsActionWithFailedAuthDismissesPasswordManager {
  // TODO(crbug.com/363172305): Re-enable when flake fixed.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"Test flaky failing on iPad.")
  }

  CheckPasswordManagerUIDismissesAfterFailedAuthentication(
      manual_fill::ManagePasswordsMatcher());

  // The keyboard should be visible.
  CheckKeyboardIsUpAndNotCovered();
}

// Tests that the "Manage Settings..." action works.
- (void)testManageSettingsActionOpensPasswordSettings {
  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Tap the "Manage Passwords..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::ManageSettingsMatcher()]
      performAction:grey_tap()];

  // Verify the password settings opened.
  // Changed minimum visible percentage to 70% for Passwords table view in
  // settings because subviews cover > 25% in smaller screens(eg. iPhone 6s).
  [[EarlGrey selectElementWithMatcher:SettingsPasswordMatcher()]
      assertWithMatcher:grey_minimumVisiblePercent(0.7)];
}

// Tests that Password Settings is dismissed when local authentication fails
// after tapping "Manage Settings...".
- (void)testManageSettingsActionWithFailedAuthDismissesPasswordSettings {
  CheckPasswordManagerUIDismissesAfterFailedAuthentication(
      manual_fill::ManageSettingsMatcher());

  // The keyboard should be visible.
  CheckKeyboardIsUpAndNotCovered();
}

// Tests that the "Manage Passwords..." action works in incognito mode.
- (void)testManagePasswordsActionOpensPasswordSettingsInIncognito {
  // Open a tab in incognito.
  [ChromeEarlGrey openNewIncognitoTab];
  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Tap the "Manage Passwords..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::ManagePasswordsMatcher()]
      performAction:grey_tap()];

  // Verify that the Password Manager opened.
  // Changed minimum visible percentage to 70% for Passwords table view in
  // settings because subviews cover > 25% in smaller screens(eg. iPhone 6s).
  [[EarlGrey
      selectElementWithMatcher:grey_accessibilityID(kPasswordsTableViewID)]
      assertWithMatcher:grey_minimumVisiblePercent(0.7)];
}

// Tests that the "Manage Settings..." action works in incognito mode.
- (void)testManageSettingsActionOpensPasswordSettingsInIncognito {
  // Open a tab in incognito.
  [ChromeEarlGrey openNewIncognitoTab];
  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Tap the "Manage Settings..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::ManageSettingsMatcher()]
      performAction:grey_tap()];

  // Verify the password settings opened.
  // Changed minimum visible percentage to 70% for Passwords table view in
  // settings because subviews cover > 25% in smaller screens(eg. iPhone 6s).
  [[EarlGrey selectElementWithMatcher:SettingsPasswordMatcher()]
      assertWithMatcher:grey_minimumVisiblePercent(0.7)];
}

// Tests that the "Select Password..." action works in incognito mode.
- (void)testSelectPasswordActionInIncognito {
  // Open a tab in incognito.
  [ChromeEarlGrey openNewIncognitoTab];
  [self loadLoginPage];

  [self openOtherPasswords];

  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that returning from "Manage Settings..." leaves the keyboard and the
// icons in the right state.
- (void)testPasswordsStateAfterPresentingManageSettings {
  // TODO(crbug.com/363172305): Re-enable when flake fixed.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"Test flaky failing on iPad.")
  }

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Icons are not present when the Keyboard Accessory Upgrade feature is
  // enabled.
  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    // Verify the status of the icon.
    [[EarlGrey selectElementWithMatcher:manual_fill::PasswordIconMatcher()]
        assertWithMatcher:grey_not(grey_userInteractionEnabled())];
  }

  // Tap the "Manage Passwords..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::ManageSettingsMatcher()]
      performAction:grey_tap()];

  // Verify the password settings opened.
  [[EarlGrey selectElementWithMatcher:SettingsPasswordMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap Done Button.
  [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
      performAction:grey_tap()];

  // TODO(crbug.com/332956674): Keyboard and keyboard accessory are not present
  // on iOS 17.4+, remove version check once fixed.
  if (@available(iOS 17.4, *)) {
    // Skip verifications.
  } else {
    // Icons are not present when the Keyboard Accessory Upgrade feature is
    // enabled.
    if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
      // Verify the status of the icons.
      [[EarlGrey selectElementWithMatcher:manual_fill::PasswordIconMatcher()]
          assertWithMatcher:grey_sufficientlyVisible()];
      [[EarlGrey selectElementWithMatcher:manual_fill::PasswordIconMatcher()]
          assertWithMatcher:grey_userInteractionEnabled()];
      [[EarlGrey selectElementWithMatcher:manual_fill::KeyboardIconMatcher()]
          assertWithMatcher:grey_not(grey_sufficientlyVisible())];
    }
  }

  // Verify that the keyboard is not covered by the password view.
  CheckKeyboardIsUpAndNotCovered();
}

// Tests that the "Select Password..." action works.
- (void)testSelectPasswordActionOpensOtherPasswordList {
  // TODO(crbug.com/363172305): Re-enable when flake fixed.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"Test flaky failing on iPad.")
  }

  [self openOtherPasswords];

  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that the "Select Password..." screen won't open if canceled.
- (void)testCancellingSelectPasswordAction {
  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Tap the "Select Password..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::OtherPasswordsMatcher()]
      performAction:grey_tap()];

  // Cancel using other passwords on a website.
  [[EarlGrey selectElementWithMatcher:CancelUsingOtherPasswordButton()]
      performAction:grey_tap()];

  // Verify that the other password list is not opened.
  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      assertWithMatcher:grey_nil()];
}

// Tests that the other password list can be dismissed with a swipe down.
- (void)testClosingOtherPasswordListViaSwipeDown {
  // TODO(crbug.com/363172305): Re-enable when flake fixed.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"Test flaky failing on iPad.")
  }

  [self openOtherPasswords];

  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Dismiss Other Passwords via swipe.
  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      performAction:grey_swipeFastInDirection(kGREYDirectionDown)];

  [ChromeEarlGrey waitForNotSufficientlyVisibleElementWithMatcher:
                      manual_fill::OtherPasswordsDismissMatcher()];

  // Open it again to make sure the old coordinator was properly cleaned up.
  [self openOtherPasswords];

  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that the "Select Password..." action is only availbale when there are
// saved passwords in the password stores.
- (void)testSelectPasswordActionAvailability {
  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_DISABLED(@"This test is not relevant when the Keyboard "
                            @"Accessory Upgrade feature is disabled.")
  }

  // Delete all saved passwords.
  [AutofillAppInterface clearProfilePasswordStore];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // The "Select Password..." action shouldn't be visible as there a no saved
  // passwords.
  [[EarlGrey selectElementWithMatcher:manual_fill::OtherPasswordsMatcher()]
      assertWithMatcher:grey_notVisible()];

  // Save a password.
  [AutofillAppInterface saveExamplePasswordFormToProfileStore];

  // The "Select Password..." action should now be visible as there's a saved
  // password available.
  [[EarlGrey selectElementWithMatcher:manual_fill::OtherPasswordsMatcher()]
      assertWithMatcher:grey_interactable()];
}

// Tests that the "Select Password..." UI is dismissed after failed local
// authentication.
- (void)testOtherPasswordListUIDismissedAfterFailedAuth {
  // TODO(crbug.com/363172305): Re-enable when flake fixed.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"Test flaky failing on iPad.")
  }

  // Setup failed authentication.
  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
                                    ReauthenticationResult::kFailure];
  [PasswordSettingsAppInterface
      mockReauthenticationModuleShouldReturnSynchronously:NO];

  [self openOtherPasswords];

  // Validate reauth UI is visible until auth result is delivered.
  [[EarlGrey selectElementWithMatcher:password_manager_test_utils::
                                          ReauthenticationController()]
      assertWithMatcher:grey_sufficientlyVisible()];
  // Passwords UI shouldn't be visible.
  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      assertWithMatcher:grey_notVisible()];

  // Deliver authentication result should dismiss the UI.
  [PasswordSettingsAppInterface mockReauthenticationModuleReturnMockedResult];

  // Verify that the whole navigation stack is gone.
  [ChromeEarlGrey
      waitForUIElementToDisappearWithMatcher:password_manager_test_utils::
                                                 ReauthenticationController()];
  [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:
                      manual_fill::OtherPasswordsDismissMatcher()];

  // The keyboard should be visible.
  CheckKeyboardIsUpAndNotCovered();
}

// Tests that returning from "Select Password..." leaves the view and icons
// in the right state.
- (void)testPasswordsStateAfterPresentingOtherPasswordList {
  [self openOtherPasswords];

  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap Done Button.
  [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
      performAction:grey_tap()];

  // TODO(crbug.com/332956674): Keyboard and keyboard accessory are not present
  // on iOS 17.4+, remove version check once fixed.
  if (@available(iOS 17.4, *)) {
    // Skip verifications.
  } else {
    // Icons are not present when the Keyboard Accessory Upgrade feature is
    // enabled.
    if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
      // Verify the status of the icons.
      [[EarlGrey selectElementWithMatcher:manual_fill::PasswordIconMatcher()]
          assertWithMatcher:grey_sufficientlyVisible()];
      [[EarlGrey selectElementWithMatcher:manual_fill::PasswordIconMatcher()]
          assertWithMatcher:grey_userInteractionEnabled()];
      [[EarlGrey selectElementWithMatcher:manual_fill::KeyboardIconMatcher()]
          assertWithMatcher:grey_not(grey_sufficientlyVisible())];
    }
  }

  // Verify that the keyboard is not covered by the password view.
  CheckKeyboardIsUpAndNotCovered();
}

// Tests that the Password View Controller is still present after tapping the
// search bar.
- (void)testPasswordControllerWhileSearching {
  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Tap the "Select Password..." action.
  [[EarlGrey selectElementWithMatcher:manual_fill::OtherPasswordsMatcher()]
      performAction:grey_tap()];

  // Acknowledge concerns using other passwords on a website.
  [[EarlGrey selectElementWithMatcher:ConfirmUsingOtherPasswordButton()]
      performAction:grey_tap()];

  // Verify that the all saved password list is visible.
  [[EarlGrey
      selectElementWithMatcher:manual_fill::OtherPasswordsDismissMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
  CheckPasswordFillingOptionIsVisible(/*site=*/@"example.com");

  // Tap the password search.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordSearchBarMatcher()]
      performAction:grey_tap()];

  // Verify keyboard is shown and that the password controller is still present
  // in the background.
  [ChromeEarlGrey waitForKeyboardToAppear];
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordTableViewMatcher()]
      assertWithMatcher:grey_minimumVisiblePercent(0.5)];
  CheckPasswordFillingOptionIsVisible(/*site=*/@"example.com");

  // Search for a term that shouldn't give any results.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordSearchBarMatcher()]
      performAction:grey_replaceText(@"example1")];
  [[EarlGrey selectElementWithMatcher:grey_text(@"example.com")]
      assertWithMatcher:grey_notVisible()];

  // Search for a term that matches with the saved credential.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordSearchBarMatcher()]
      performAction:grey_replaceText(@"AMPL")];
  CheckPasswordFillingOptionIsVisible(/*site=*/@"example.com");
}

// Tests that the Password View Controller is dismissed when tapping the
// keyboard icon.
- (void)testKeyboardIconDismissPasswordController {
  if ([ChromeEarlGrey isIPadIdiom] ||
      [AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_SKIPPED(
        @"The keyboard icon is never present on iPads or when the Keyboard "
        @"Accessory Upgrade feature is enabled.");
  }

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Tap on the keyboard icon.
  [[EarlGrey selectElementWithMatcher:manual_fill::KeyboardIconMatcher()]
      performAction:grey_tap()];

  // Verify the password controller table view and the password icon is NOT
  // visible.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordTableViewMatcher()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:manual_fill::KeyboardIconMatcher()]
      assertWithMatcher:grey_notVisible()];
}

// Tests that the Password View Controller is dismissed when tapping the outside
// the popover on iPad.
- (void)testIPadTappingOutsidePopOverDismissPasswordController {
  if (![ChromeEarlGrey isIPadIdiom]) {
    return;
  }
  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  [ChromeEarlGreyUI dismissByTappingOnTheWindowOfPopover:
                        manual_fill::PasswordTableViewMatcher()];

  // Verify the password controller table view is not visible and the password
  // icon is visible.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordTableViewMatcher()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordIconMatcher()]
      assertWithMatcher:grey_interactable()];
  // Verify the interaction status of the password icon.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordIconMatcher()]
      assertWithMatcher:grey_userInteractionEnabled()];
}

// Tests that the Password View Controller is dismissed when tapping the
// keyboard.
// TODO(crbug.com/41428686): started to be flaky and sometimes opens full list
// when typing text.
- (void)DISABLED_testTappingKeyboardDismissPasswordControllerPopOver {
  if (![ChromeEarlGrey isIPadIdiom]) {
    return;
  }
  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordTableViewMatcher()]
      performAction:grey_replaceText(@"text")];

  // Verify the password controller table view and the password icon is NOT
  // visible.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordTableViewMatcher()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:manual_fill::KeyboardIconMatcher()]
      assertWithMatcher:grey_notVisible()];
}

// Tests that the Password View Controller stays on rotation.
- (void)testPasswordControllerSupportsRotation {
  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
                                error:nil];

  // Verify the password controller table view is still visible.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordTableViewMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that content is injected in iframe messaging.
- (void)testPasswordControllerSupportsIFrameMessaging {
  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  const GURL URL = self.testServer->GetURL(kIFrameHTMLFile);
  NSString* URLString = base::SysUTF8ToNSString(URL.spec());
  [AutofillAppInterface savePasswordFormForURLSpec:URLString];
  [ChromeEarlGrey loadURL:URL];
  [ChromeEarlGrey waitForWebStateContainingText:"iFrame"];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithIdInFrame(kFormElementUsername, 0)];

  // Wait for the accessory icon to appear.
  [ChromeEarlGrey waitForKeyboardToAppear];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/true);

  CheckPasswordFillingOptionIsVisible(
      /*site=*/base::SysUTF8ToNSString(self.URL.host()));

  // Select a username.
  [[EarlGrey selectElementWithMatcher:UsernameButtonMatcher()]
      performAction:grey_tap()];

  // Verify Web Content.
  NSString* javaScriptCondition = [NSString
      stringWithFormat:
          @"window.frames[0].document.getElementById('%s').value === '%s'",
          kFormElementUsername, kExampleUsername];
  [ChromeEarlGrey waitForJavaScriptCondition:javaScriptCondition];
}

// Tests that an alert is shown when trying to fill a password in an unsecure
// field.
- (void)testPasswordControllerPresentsUnsecureAlert {
  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  // Only Objc objects can cross the EDO portal.
  NSString* URLString = base::SysUTF8ToNSString(self.URL.spec());
  [AutofillAppInterface savePasswordFormForURLSpec:URLString];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Wait for the accessory icon to appear.
  [ChromeEarlGrey waitForKeyboardToAppear];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/true);

  CheckPasswordFillingOptionIsVisible(
      /*site=*/base::SysUTF8ToNSString(self.URL.host()));

  // Select a password.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordButtonMatcher()]
      performAction:grey_tap()];

  // Look for the alert.
  [[EarlGrey selectElementWithMatcher:NotSecureWebsiteAlert()]
      assertWithMatcher:grey_not(grey_nil())];

  // Dismiss the alert.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::OKButton()]
      performAction:grey_tap()];

  [ChromeEarlGreyUI cleanupAfterShowingAlert];
}

// Tests that the password icon is not present when no passwords are available.
- (void)testPasswordIconIsNotVisibleWhenPasswordStoreEmpty {
  if ([AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_SKIPPED(@"This test is not relevant when the Keyboard "
                           @"Accessory Upgrade feature is enabled.");
  }

  [AutofillAppInterface clearProfilePasswordStore];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Assert the password icon is not enabled and not visible.
  [[EarlGrey selectElementWithMatcher:manual_fill::PasswordIconMatcher()]
      assertWithMatcher:grey_notVisible()];
}

// Tests that the "no passwords found" message is visible when no password
// suggestions are available for the current website.
- (void)testNoPasswordsFoundMessageIsVisibleWhenNoPasswordSuggestions {
  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_SKIPPED(@"This test is not relevant when the Keyboard "
                           @"Accessory Upgrade feature is disabled.");
  }

  [AutofillAppInterface clearProfilePasswordStore];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Assert that the "no passwords found" message is visible.
  id<GREYMatcher> noPasswordsFoundMessage = grey_accessibilityLabel(
      l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_NO_PASSWORDS_FOR_SITE));
  [[EarlGrey selectElementWithMatcher:noPasswordsFoundMessage]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Verify that the number of visible suggestions in the keyboard accessory was
  // correctly recorded.
  GREYAssertNil(
      [MetricsAppInterface
          expectUniqueSampleWithCount:1
                            forBucket:0
                         forHistogram:@"ManualFallback.VisibleSuggestions."
                                      @"OpenPasswords"],
      @"Unexpected histogram error for number of visible suggestions.");
}

// Tests password generation on manual fallback.
- (void)testPasswordGenerationOnManualFallback {
  [SigninEarlGreyUI signinWithFakeIdentity:[FakeSystemIdentity fakeIdentity1]];
  [ChromeEarlGrey waitForSyncEngineInitialized:YES
                                   syncTimeout:base::Seconds(10)];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementPassword)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Select a suggest password option.
  [[EarlGrey selectElementWithMatcher:manual_fill::SuggestPasswordMatcher()]
      performAction:grey_tap()];

  // Confirm by tapping on the 'Use Suggested Password' button.
  [[EarlGrey selectElementWithMatcher:UseSuggestedPasswordMatcher()]
      performAction:grey_tap()];

  // Verify Web Content.
  NSString* javaScriptCondition =
      [NSString stringWithFormat:@"document.getElementById('%s').value !== ''",
                                 kFormElementPassword];
  [ChromeEarlGrey waitForJavaScriptCondition:javaScriptCondition];
}

// Tests password generation on manual fallback for signed in not syncing users.
- (void)testPasswordGenerationOnManualFallbackSignedInNotSyncingAccount {
  [SigninEarlGreyUI signinWithFakeIdentity:[FakeSystemIdentity fakeIdentity1]];
  [ChromeEarlGrey waitForSyncEngineInitialized:YES
                                   syncTimeout:base::Seconds(10)];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementPassword)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Verify a suggest password option is showing.
  [[EarlGrey selectElementWithMatcher:manual_fill::SuggestPasswordMatcher()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Select a suggest password option.
  [[EarlGrey selectElementWithMatcher:manual_fill::SuggestPasswordMatcher()]
      performAction:grey_tap()];
}

// Tests password generation on manual fallback not showing for signed in not
// syncing users with Passwords toggle in account settings disbaled.
- (void)testPasswordGenerationFallbackSignedInNotSyncingPasswordsDisabled {
  [SigninEarlGreyUI signinWithFakeIdentity:[FakeSystemIdentity fakeIdentity1]];
  [ChromeEarlGrey waitForSyncEngineInitialized:YES
                                   syncTimeout:base::Seconds(10)];

  // Disable Passwords toggle in account settings.
  [ChromeEarlGreyUI openSettingsMenu];
  [ChromeEarlGreyUI tapSettingsMenuButton:SettingsAccountButton()];
  [[EarlGrey
      selectElementWithMatcher:grey_accessibilityID(kSyncPasswordsIdentifier)]
      performAction:chrome_test_util::TurnTableViewSwitchOn(/*on=*/NO)];
  [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
      performAction:grey_tap()];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementPassword)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Verify the suggest password option is not shown.
  [[EarlGrey selectElementWithMatcher:manual_fill::SuggestPasswordMatcher()]
      assertWithMatcher:grey_notVisible()];
}

// Tests password generation on manual fallback not showing for signed in not
// syncing users with encryption error.
- (void)testPasswordGenerationFallbackSignedInNotSyncingEncryptionError {
  // Encrypt synced data with a passphrase to enable passphrase encryption for
  // the signed in account.
  [ChromeEarlGrey addSyncPassphrase:kPassphrase];

  [SigninEarlGreyUI signinWithFakeIdentity:[FakeSystemIdentity fakeIdentity1]];
  [ChromeEarlGrey waitForSyncEngineInitialized:YES
                                   syncTimeout:base::Seconds(10)];

  // Verify encryption error is showing in in account settings.
  [ChromeEarlGreyUI openSettingsMenu];
  [ChromeEarlGreyUI tapSettingsMenuButton:SettingsAccountButton()];
  // Verify the error section is showing.
  [[EarlGrey selectElementWithMatcher:
                 grey_accessibilityLabel(l10n_util::GetNSString(
                     IDS_IOS_ACCOUNT_TABLE_ERROR_ENTER_PASSPHRASE_BUTTON))]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:SettingsDoneButton()]
      performAction:grey_tap()];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementPassword)];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/false);

  // Verify the suggest password option is not shown.
  [[EarlGrey selectElementWithMatcher:manual_fill::SuggestPasswordMatcher()]
      assertWithMatcher:grey_notVisible()];
}

// Tests that the overflow menu button is only visible when the Keyboard
// Accessory Upgrade feature is enabled.
- (void)testOverflowMenuVisibility {
  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  // Save password for site.
  NSString* URLString = base::SysUTF8ToNSString(self.URL.spec());
  [AutofillAppInterface savePasswordFormForURLSpec:URLString];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Wait for the accessory icon to appear.
  [ChromeEarlGrey waitForKeyboardToAppear];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/true);

  if ([AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    [[EarlGrey selectElementWithMatcher:OverflowMenuButton()]
        assertWithMatcher:grey_sufficientlyVisible()];
  } else {
    [[EarlGrey selectElementWithMatcher:OverflowMenuButton()]
        assertWithMatcher:grey_notVisible()];
  }
}

// Tests the "Edit" action of the overflow menu button displays the password's
// details in edit mode.
- (void)testEditPasswordFromOverflowMenu {
  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_DISABLED(@"This test is not relevant when the Keyboard "
                            @"Accessory Upgrade feature is disabled.")
  }

  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  // Save password for site.
  NSString* URLString = base::SysUTF8ToNSString(self.URL.spec());
  [AutofillAppInterface savePasswordFormForURLSpec:URLString];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Wait for the accessory icon to appear.
  [ChromeEarlGrey waitForKeyboardToAppear];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/true);

  // Tap the overflow menu button and select the "Edit" action.
  [[EarlGrey selectElementWithMatcher:OverflowMenuButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:OverflowMenuEditAction()]
      performAction:grey_tap()];

  // Edit the username.
  [[EarlGrey selectElementWithMatcher:grey_text(base::SysUTF8ToNSString(
                                          kExampleUsername))]
      performAction:grey_replaceText(@"new username")];

  // Tap Done Button.
  [[EarlGrey selectElementWithMatcher:NavigationBarDoneButton()]
      performAction:grey_tap()];

  // TODO(crbug.com/332956674): Check that the updated suggestion is visible.
}

// Tests the "Edit" action of the overflow menu button in the all password list
// displays the password's details in edit mode.
- (void)testEditPasswordFromAllPasswordListOverflowMenu {
  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_DISABLED(@"This test is not relevant when the Keyboard "
                            @"Accessory Upgrade feature is disabled.")
  }

  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  [self loadLoginPage];

  [self openOtherPasswords];

  // Tap the overflow menu button and select the "Edit" action.
  [[EarlGrey selectElementWithMatcher:OverflowMenuButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:OverflowMenuEditAction()]
      performAction:grey_tap()];

  // Check that the password details page opened.
  [[EarlGrey selectElementWithMatcher:PasswordDetailsPage()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap Cancel Button.
  [[EarlGrey selectElementWithMatcher:NavigationBarCancelButton()]
      performAction:grey_tap()];
}

// Tests that tapping the "Autofill Form" button fills the password form with
// the right data.
- (void)testAutofillFormButtonFillsForm {
  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_DISABLED(@"This test is not relevant when the Keyboard "
                            @"Accessory Upgrade feature is disabled.")
  }

  [FormInputAccessoryAppInterface setUpMockReauthenticationModule];
  [FormInputAccessoryAppInterface mockReauthenticationModuleExpectedResult:
                                      ReauthenticationResult::kSuccess];

  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  // Save password for site.
  NSString* URLString = base::SysUTF8ToNSString(self.URL.spec());
  [AutofillAppInterface savePasswordFormForURLSpec:URLString];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Wait for the accessory icon to appear.
  [ChromeEarlGrey waitForKeyboardToAppear];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/true);

  // Tap the "Autofill Form" button.
  [[EarlGrey selectElementWithMatcher:AutofillFormButton()]
      performAction:grey_tap()];

  // Verify that the page is filled properly.
  [self verifyPasswordInfoHasBeenFilled:base::SysUTF8ToNSString(
                                            kExampleUsername)];

  // Verify that the acceptance of the password suggestion at index 0 was
  // correctly recorded.
  CheckAutofillSuggestionAcceptedIndexMetricsCount(/*suggestion_index=*/0);

  [FormInputAccessoryAppInterface removeMockReauthenticationModule];
}

// Tests that tapping the "Autofill Form" button doesn't fill the password form
// if reauth failed.
- (void)testAutofillFormButtonWithFailedAuth {
  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_DISABLED(@"This test is not relevant when the Keyboard "
                            @"Accessory Upgrade feature is disabled.")
  }

  [FormInputAccessoryAppInterface setUpMockReauthenticationModule];
  [FormInputAccessoryAppInterface mockReauthenticationModuleExpectedResult:
                                      ReauthenticationResult::kFailure];

  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  // Save password for site.
  NSString* URLString = base::SysUTF8ToNSString(self.URL.spec());
  [AutofillAppInterface savePasswordFormForURLSpec:URLString];

  [self loadLoginPage];

  // Bring up the keyboard.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:TapWebElementWithId(kFormElementUsername)];

  // Wait for the accessory icon to appear.
  [ChromeEarlGrey waitForKeyboardToAppear];

  // Open the password manual fill view.
  OpenPasswordManualFillView(/*has_suggestions=*/true);

  // Tap the "Autofill Form" button.
  [[EarlGrey selectElementWithMatcher:AutofillFormButton()]
      performAction:grey_tap()];

  // Verify that the page is filled properly.
  [self verifyPasswordInfoHasntBeenFilled];

  [FormInputAccessoryAppInterface removeMockReauthenticationModule];
}

// Tests that tapping the "Autofill Form" button in the all password list fills
// the password form with the right data.
- (void)testAutofillFormButtonInAllPasswordListFillsForm {
  if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
    EARL_GREY_TEST_DISABLED(@"This test is not relevant when the Keyboard "
                            @"Accessory Upgrade feature is disabled.")
  }

  // Disable the password bottom sheet.
  [PasswordSuggestionBottomSheetAppInterface disableBottomSheet];

  [self loadLoginPage];

  [self openOtherPasswords];

  // Tap the "Autofill Form" button.
  [[EarlGrey selectElementWithMatcher:AutofillFormButton()]
      performAction:grey_tap()];

  // Verify that the page is filled properly.
  [self verifyPasswordInfoHasBeenFilled:base::SysUTF8ToNSString(
                                            kExampleUsername)];

  // Verify that the acceptance of the password suggestion at index 0 was
  // correctly recorded.
  CheckAutofillSuggestionAcceptedIndexMetricsCount(
      /*suggestion_index=*/0, /*from_all_password_context=*/true);
}

#pragma mark - Private

// Verify that the password info has been filled.
- (void)verifyPasswordInfoHasBeenFilled:(NSString*)username {
  // Username.
  NSString* usernameCondition = [NSString
      stringWithFormat:@"window.document.getElementById('%s').value === '%@'",
                       kFormElementUsername, username];

  // Password.
  NSString* passwordCondition =
      [NSString stringWithFormat:@"document.getElementById('%s').value !== ''",
                                 kFormElementPassword];

  NSString* condition = [NSString
      stringWithFormat:@"%@ && %@", usernameCondition, passwordCondition];
  [ChromeEarlGrey waitForJavaScriptCondition:condition];
}

// Verify that the password info has not been filled.
- (void)verifyPasswordInfoHasntBeenFilled {
  // Username.
  NSString* usernameCondition = [NSString
      stringWithFormat:@"window.document.getElementById('%s').value === ''",
                       kFormElementUsername];

  // Password.
  NSString* passwordCondition =
      [NSString stringWithFormat:@"document.getElementById('%s').value === ''",
                                 kFormElementPassword];

  NSString* condition = [NSString
      stringWithFormat:@"%@ && %@", usernameCondition, passwordCondition];
  [ChromeEarlGrey waitForJavaScriptCondition:condition];
}

@end

// Rerun all the tests in this file but with kIOSKeyboardAccessoryUpgrade
// disabled. This will be removed once that feature launches fully, but ensures
// regressions aren't introduced in the meantime.
@interface PasswordViewControllerKeyboardAccessoryUpgradeDisabledTestCase
    : PasswordViewControllerTestCase

@end

@implementation PasswordViewControllerKeyboardAccessoryUpgradeDisabledTestCase

- (BOOL)shouldEnableKeyboardAccessoryUpgradeFeature {
  return NO;
}

// This causes the test case to actually be detected as a test case. The actual
// tests are all inherited from the parent class.
- (void)testEmpty {
}

@end