chromium/ios/chrome/browser/ui/settings/password/password_checkup/password_checkup_egtest.mm

// Copyright 2023 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/strings/sys_string_conversions.h"
#import "components/strings/grit/components_strings.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/password/password_checkup/password_checkup_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/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/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ui/base/l10n/l10n_util.h"

using chrome_test_util::ButtonWithAccessibilityLabel;
using chrome_test_util::SettingsCollectionView;
using password_manager_test_utils::DeleteCredential;
using password_manager_test_utils::GetInteractionForPasswordIssueEntry;
using password_manager_test_utils::kDefaultPassword;
using password_manager_test_utils::kDefaultSite;
using password_manager_test_utils::kDefaultUsername;
using password_manager_test_utils::PasswordCheckupCellForState;
using password_manager_test_utils::PasswordIssuesTableView;
using password_manager_test_utils::ReauthenticationController;
using password_manager_test_utils::SaveCompromisedPasswordFormToProfileStore;
using password_manager_test_utils::
    SaveMutedCompromisedPasswordFormToProfileStore;
using password_manager_test_utils::SavePasswordFormToProfileStore;

namespace {

constexpr NSString* kDefaultDomain = @"example.com";
constexpr NSString* kDomain1 = @"example1.com";

constexpr NSString* kSite1 = @"https://example1.com/";
constexpr NSString* kSite2 = @"https://example2.com/";
constexpr NSString* kSite3 = @"https://example3.com/";

constexpr NSString* kReusedPassword = @"reused password";
constexpr NSString* kSafePassword = @"s@fe pa55word!";
constexpr NSString* kWeakPassword = @"1";

#pragma mark - Password Checkup Homepage matchers

// Matcher for the TableView in the Password Checkup Homepage.
id<GREYMatcher> PasswordCheckupTableView() {
  return grey_accessibilityID(password_manager::kPasswordCheckupTableViewId);
}

// Matcher for the header image view shown in the Password Checkup Homepage.
id<GREYMatcher> PasswordCheckupHompageHeaderImageView() {
  return grey_accessibilityID(
      password_manager::kPasswordCheckupHeaderImageViewId);
}

// Matcher for the compromised passwords table view item shown in the Password
// Checkup Homepage.
id<GREYMatcher> PasswordCheckupHomepageCompromisedPasswordsItem() {
  return grey_accessibilityID(
      password_manager::kPasswordCheckupCompromisedPasswordsItemId);
}

// Matcher for the reused passwords table view item shown in the Password
// Checkup Homepage.
id<GREYMatcher> PasswordCheckupHomepageReusedPasswordsItem() {
  return grey_accessibilityID(
      password_manager::kPasswordCheckupReusedPasswordsItemId);
}

// Matcher for the weak passwords table view item shown in the Password Checkup
// Homepage.
id<GREYMatcher> PasswordCheckupHomepageWeakPasswordsItem() {
  return grey_accessibilityID(
      password_manager::kPasswordCheckupWeakPasswordsItemId);
}

// Matcher for the "Check Again" button shown in the Password Checkup Homepage.
id<GREYMatcher> CheckAgainButton() {
  return ButtonWithAccessibilityLabel(l10n_util::GetNSString(
      IDS_IOS_PASSWORD_CHECKUP_HOMEPAGE_CHECK_AGAIN_BUTTON));
}

// Matcher for the error dialog that pops up in the Password Checkup Homepage
// when an error occurred while running a new password check.
id<GREYMatcher> PasswordCheckupHomepageErrorDialog() {
  return grey_accessibilityLabel(
      l10n_util::GetNSString(IDS_IOS_PASSWORD_CHECKUP_ERROR_OFFLINE));
}

// Matcher for the "OK" button of the error dialog that pops up in the Password
// Checkup Homepage when an error occurred while running a new password check.
id<GREYMatcher> PasswordCheckupHomepageErrorDialogOKButton() {
  return chrome_test_util::AlertAction(l10n_util::GetNSString(IDS_OK));
}

#pragma mark - Password Issues matchers

// Matcher for the navigation title of the compromised issues page.
id<GREYMatcher> CompromisedPasswordIssuesPageTitle(int issue_count) {
  return grey_accessibilityLabel(
      base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
          IDS_IOS_COMPROMISED_PASSWORD_ISSUES_TITLE, issue_count)));
}

// Matcher for dismissed warnings table view item shown in the compromised
// issues page.
id<GREYMatcher> CompromisedPasswordIssuesDismissedWarnings() {
  return grey_accessibilityID(kDismissedWarningsCellID);
}

// Matcher for the navigation title of the dismissed warnings page.
id<GREYMatcher> DismissedWarningsPageTitle() {
  return grey_accessibilityLabel(
      l10n_util::GetNSString(IDS_IOS_DISMISSED_WARNINGS_PASSWORD_ISSUES_TITLE));
}

// Matcher for the navigation title of the reused issues page.
id<GREYMatcher> ReusedPasswordIssuesPageTitle(int issue_count) {
  return grey_accessibilityLabel(
      l10n_util::GetNSStringF(IDS_IOS_REUSED_PASSWORD_ISSUES_TITLE,
                              base::NumberToString16(issue_count)));
}

// Matcher for the navigation title of the weak issues page.
id<GREYMatcher> WeakPasswordIssuesPageTitle(int issue_count) {
  return grey_accessibilityLabel(
      base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
          IDS_IOS_WEAK_PASSWORD_ISSUES_TITLE, issue_count)));
}

#pragma mark - Password Details matchers

// Matcher for the compromised warning found in a compromised password's details
// page.
id<GREYMatcher> CompromisedWarning() {
  return grey_accessibilityID(kCompromisedWarningID);
}

// Matcher for the "Dismiss Warning" button found in a compromised password's
// details page.
id<GREYMatcher> DismissWarningButton() {
  return ButtonWithAccessibilityLabel(
      l10n_util::GetNSString(IDS_IOS_DISMISS_WARNING));
}

// Matcher for the "Dismiss" button of the confirmation dialog found in a
// compromised password's details page when trying to dismiss the warning.
id<GREYMatcher> DismissWarningConfirmationDialogButton() {
  return ButtonWithAccessibilityLabel(
      l10n_util::GetNSString(IDS_IOS_DISMISS_WARNING_DIALOG_DISMISS_BUTTON));
}

// Matcher for the "Restore Warning" button found in a muted compromised
// password's details page.
id<GREYMatcher> RestoreWarningButton() {
  return ButtonWithAccessibilityLabel(
      l10n_util::GetNSString(IDS_IOS_RESTORE_WARNING));
}

#pragma mark - Helpers

// Saves two reused passwords.
void SaveReusedPasswordFormsToProfileStore() {
  SavePasswordFormToProfileStore(kReusedPassword, kDefaultUsername, kSite1);
  SavePasswordFormToProfileStore(kReusedPassword, kDefaultUsername, kSite2);
}

// Saves a weak password.
void SaveWeakPasswordFormToProfileStore() {
  SavePasswordFormToProfileStore(kWeakPassword, kDefaultUsername, kSite3);
}

// Opens the Password Checkup Homepage.
void OpenPasswordCheckupHomepage(PasswordCheckUIState result_state,
                                 int result_password_count) {
  password_manager_test_utils::OpenPasswordManager();

  id<GREYMatcher> password_checkup_result_cell =
      PasswordCheckupCellForState(result_state, result_password_count);

  // Wait for Password Checkup result.
  [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                      password_checkup_result_cell];

  // Open the Password Checkup Homepage and make sure that it is visible.
  [[EarlGrey selectElementWithMatcher:password_checkup_result_cell]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:PasswordCheckupTableView()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Verify that the compromised issues page is correctly presented.
void VerifyCompromisedPasswordIssuesPageIsVisible(int issue_count) {
  [[EarlGrey
      selectElementWithMatcher:CompromisedPasswordIssuesPageTitle(issue_count)]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:PasswordIssuesTableView()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Verify that the dismissed warnings page is correctly presented.
void VerifyDismissedWarningsPageIsVisible() {
  [[EarlGrey selectElementWithMatcher:DismissedWarningsPageTitle()]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:PasswordIssuesTableView()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Verify that the reused issues page is correctly presented.
void VerifyReusedPasswordIssuesPageIsVisible(int issue_count) {
  [[EarlGrey
      selectElementWithMatcher:ReusedPasswordIssuesPageTitle(issue_count)]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:PasswordIssuesTableView()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Verify that the weak issues page is correctly presented.
void VerifyWeakPasswordIssuesPageIsVisible(int issue_count) {
  [[EarlGrey selectElementWithMatcher:WeakPasswordIssuesPageTitle(issue_count)]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:PasswordIssuesTableView()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Taps the "Back" button of the navigation bar.
void GoBackToPreviousPage() {
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::SettingsMenuBackButton()]
      performAction:grey_tap()];
}

// Edits the credential's password.
void EditPassword(NSString* new_password) {
  [[EarlGrey selectElementWithMatcher:password_manager_test_utils::
                                          PasswordDetailPassword()]
      performAction:grey_replaceText(new_password)];

  [[EarlGrey
      selectElementWithMatcher:password_manager_test_utils::EditDoneButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:password_manager_test_utils::
                                          EditPasswordConfirmationButton()]
      performAction:grey_tap()];

  // Wait until the confirmation dialog is dimsissed.
  [ChromeEarlGreyUI waitForAppToIdle];
}

NSString* LeakedPasswordDescription() {
  return l10n_util::GetNSString(
      IDS_IOS_COMPROMISED_PASSWORD_ISSUES_LEAKED_DESCRIPTION);
}

}  // namespace

// Test case for Password Checkup.
@interface PasswordCheckupTestCase : ChromeTestCase
@end

@implementation PasswordCheckupTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config;
  config.relaunch_policy = ForceRelaunchByCleanShutdown;
  return config;
}

- (void)setUp {
  [super setUp];

  // Set the FakeBulkLeakCheckService to return the idle state.
  [PasswordSettingsAppInterface
      setFakeBulkLeakCheckBufferedState:
          password_manager::BulkLeakCheckServiceInterface::State::kIdle];

  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey signinWithFakeIdentity:fakeIdentity];

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

- (void)tearDown {
  [PasswordSettingsAppInterface removeMockReauthenticationModule];
  [super tearDown];
}

#pragma mark - Tests

// Tests the safe state of the Password Checkup Homepage.
- (void)testPasswordCheckupHomepageSafeState {
  SavePasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(/*result_state=*/PasswordCheckStateSafe,
                              /*result_password_count=*/0);

  // Verify that tapping the items of the insecure types section doesn't open
  // another page.
  [[[EarlGrey selectElementWithMatcher:
                  PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()] assertWithMatcher:grey_sufficientlyVisible()];
  [[[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageReusedPasswordsItem()]
      performAction:grey_tap()] assertWithMatcher:grey_sufficientlyVisible()];
  [[[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageWeakPasswordsItem()]
      performAction:grey_tap()] assertWithMatcher:grey_sufficientlyVisible()];
}

// Validates that the Password Manager UI is dismissed when local authentication
// fails while in the Password Checkup UI.
- (void)testPasswordCheckupHomepageWithFailedAuth {
  SavePasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(/*result_state=*/PasswordCheckStateSafe,
                              /*result_password_count=*/0);

  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
                                    ReauthenticationResult::kFailure];
  [PasswordSettingsAppInterface
      mockReauthenticationModuleShouldReturnSynchronously:NO];

  [[AppLaunchManager sharedManager] backgroundAndForegroundApp];

  // Password Checkup should be covered by reauthentication UI until successful
  // local authentication.
  [[EarlGrey selectElementWithMatcher:PasswordCheckupTableView()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:ReauthenticationController()]
      assertWithMatcher:grey_sufficientlyVisible()];

  [PasswordSettingsAppInterface mockReauthenticationModuleReturnMockedResult];

  // Password Manager UI should be dismissed leaving the Settings UI Visible.
  [[EarlGrey selectElementWithMatcher:PasswordCheckupTableView()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:ReauthenticationController()]
      assertWithMatcher:grey_notVisible()];

  [[EarlGrey selectElementWithMatcher:SettingsCollectionView()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests the warning state of the Password Checkup Homepage.
- (void)testPasswordCheckupHomepageWarningState {
  SaveMutedCompromisedPasswordFormToProfileStore();
  SaveReusedPasswordFormsToProfileStore();
  SaveWeakPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateReusedPasswords,
      /*result_password_count=*/2);

  // Verify that tapping the compromised passwords item opens the compromised
  // password issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];
  VerifyCompromisedPasswordIssuesPageIsVisible(/*issue_count=*/0);

  GoBackToPreviousPage();

  // Verify that tapping the reused passwords item opens the reused password
  // issues page.
  [[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageReusedPasswordsItem()]
      performAction:grey_tap()];
  VerifyReusedPasswordIssuesPageIsVisible(/*issue_count=*/2);

  GoBackToPreviousPage();

  // Verify that tapping the weak passwords item opens the weak password issues
  // page.
  [[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageWeakPasswordsItem()]
      performAction:grey_tap()];
  VerifyWeakPasswordIssuesPageIsVisible(/*issue_count=*/1);
}

// Tests the severe warning state of the Password Checkup Homepage.
- (void)testPasswordCheckupHomepageCompromisedState {
  SaveCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Verify that tapping the reused and weak passwords items doesn't open
  // another page.
  [[[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageReusedPasswordsItem()]
      performAction:grey_tap()] assertWithMatcher:grey_sufficientlyVisible()];
  [[[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageWeakPasswordsItem()]
      performAction:grey_tap()] assertWithMatcher:grey_sufficientlyVisible()];

  // Verify that tapping the compromised passwords item opens the compromised
  // password issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];
  VerifyCompromisedPasswordIssuesPageIsVisible(/*issue_count=*/1);
}

// Tests the loading state of the Password Checkup Homepage.
// TODO(crbug.com/40921746): Fix and re enable the test.
- (void)DISABLED_testPasswordCheckupHomepageLoadingState {
  // TODO(crbug.com/41484731): Test fails on iPad.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"Fails on iPad.");
  }
  SaveCompromisedPasswordFormToProfileStore();

  NSInteger numberOfAffiliatedGroups = 1;

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Trigger a new check by tapping the "Check Again" button.
  [[EarlGrey selectElementWithMatcher:CheckAgainButton()]
      performAction:grey_tap()];

  // Verify that tapping the items of the insecure types section doesn't open
  // another page.
  [[[EarlGrey selectElementWithMatcher:
                  PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()] assertWithMatcher:grey_sufficientlyVisible()];
  [[[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageReusedPasswordsItem()]
      performAction:grey_tap()] assertWithMatcher:grey_sufficientlyVisible()];
  [[[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageWeakPasswordsItem()]
      performAction:grey_tap()] assertWithMatcher:grey_sufficientlyVisible()];

  // Verify that the "Check Again" button is disabled.
  [[EarlGrey selectElementWithMatcher:CheckAgainButton()]
      assertWithMatcher:grey_not(grey_userInteractionEnabled())];

  // Wait for Password Checkup to finish loading.
  [ChromeEarlGrey waitForNotSufficientlyVisibleElementWithMatcher:
                      PasswordCheckupCellForState(PasswordCheckStateRunning,
                                                  numberOfAffiliatedGroups)];

  // Verify that the "Check Again" button is enabled again.
  [[EarlGrey selectElementWithMatcher:CheckAgainButton()]
      assertWithMatcher:grey_userInteractionEnabled()];
}

// Tests the error state of the Password Checkup Homepage.
- (void)testPasswordCheckupHomepageErrorState {
  SaveCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Set the FakeBulkLeakCheckService to return the offline error state.
  [PasswordSettingsAppInterface
      setFakeBulkLeakCheckBufferedState:
          password_manager::BulkLeakCheckServiceInterface::State::
              kNetworkError];

  // Trigger a new check by tapping the "Check Again" button.
  [[EarlGrey selectElementWithMatcher:CheckAgainButton()]
      performAction:grey_tap()];

  // Wait for the error dialog to pop up.
  [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                      PasswordCheckupHomepageErrorDialog()];

  // Tap the OK button.
  [[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageErrorDialogOKButton()]
      performAction:grey_tap()];

  // Verify that the Password Checkup Homepage is still visible.
  [[EarlGrey
      selectElementWithMatcher:
          grey_accessibilityID(password_manager::kPasswordCheckupTableViewId)]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that the Password Checkup Homepage header image view is correctly
// shown/hidden depending on the device's orientation.
- (void)testPasswordCheckupHomepageDeviceOrientation {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Landscape orientation doesn't change the look of "
                           @"the Password Checkup Homepage.");
  }

  SavePasswordFormToProfileStore();

  // Rotate device to left landscape orientation before opening the Password
  // Checkup Homepage.
  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
                                error:nil];

  OpenPasswordCheckupHomepage(/*result_state=*/PasswordCheckStateSafe,
                              /*result_password_count=*/0);

  // The header image view should not be visible after being rotated to left
  // landscape orientation.
  [[EarlGrey selectElementWithMatcher:PasswordCheckupHompageHeaderImageView()]
      assertWithMatcher:grey_notVisible()];

  // The header image view should be visible after being rotated to portrait
  // orientation.
  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait error:nil];
  [[EarlGrey selectElementWithMatcher:PasswordCheckupHompageHeaderImageView()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // The header image view should not be visible after being rotated to right
  // landscape orientation.
  [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeRight
                                error:nil];
  [[EarlGrey selectElementWithMatcher:PasswordCheckupHompageHeaderImageView()]
      assertWithMatcher:grey_notVisible()];
}

// Tests opening the Password Issues page.
- (void)testOpeningPasswordIssues {
  SaveCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Auth should not be required to open Password Issues. The user is
  // authenticated when opening the Password Manager page. Catch any unexpected
  // authentication requests.
  [PasswordSettingsAppInterface mockReauthenticationModuleCanAttempt:NO];

  // Open the compromised issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];

  VerifyCompromisedPasswordIssuesPageIsVisible(/*issue_count=*/1);
}

// Tests dismissing a compromised password warning.
- (void)testPasswordCheckupDismissCompromisedPasswordWarning {
  SaveCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Open the compromised issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];

  VerifyCompromisedPasswordIssuesPageIsVisible(/*issue_count=*/1);

  // Validate that the compromised password is present in the list and that the
  // "Dismissed Warning" cell is not present.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      assertWithMatcher:grey_sufficientlyVisible()];
  [password_manager_test_utils::GetInteractionForIssuesListItem(
      CompromisedPasswordIssuesDismissedWarnings(), kGREYDirectionDown)
      assertWithMatcher:grey_notVisible()];

  // Open the password's details.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap the "Dismiss Warning" button and confirm the warning dismissal.
  [[EarlGrey selectElementWithMatcher:DismissWarningButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:DismissWarningConfirmationDialogButton()]
      performAction:grey_tap()];

  // Wait until the alert and the details view are dismissed.
  [ChromeEarlGreyUI waitForAppToIdle];

  // Verify that the compromised warning is gone.
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_notVisible()];

  GoBackToPreviousPage();

  // Check that the current view is now the compromised password issues view.
  VerifyCompromisedPasswordIssuesPageIsVisible(/*issue_count=*/0);

  // Verify that the password is not in the list anymore and that the "Dismissed
  // Warning" cell is now present.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      assertWithMatcher:grey_notVisible()];
  [password_manager_test_utils::GetInteractionForIssuesListItem(
      CompromisedPasswordIssuesDismissedWarnings(), kGREYDirectionDown)
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests restoring a muted compromised password warning.
- (void)testPasswordCheckupRestoreCompromisedPasswordWarning {
  SaveMutedCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateDismissedWarnings,
      /*result_password_count=*/1);

  // Open the compromised issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];

  // Verify that the compromised password issues page is displayed and that the
  // "Dismissed Warning" cell is present.
  VerifyCompromisedPasswordIssuesPageIsVisible(/*issue_count=*/0);
  [password_manager_test_utils::GetInteractionForIssuesListItem(
      CompromisedPasswordIssuesDismissedWarnings(), kGREYDirectionDown)
      assertWithMatcher:grey_sufficientlyVisible()];

  // Open the dismissed warnings page.
  [[EarlGrey
      selectElementWithMatcher:CompromisedPasswordIssuesDismissedWarnings()]
      performAction:grey_tap()];

  // Verify that the dismissed warnings is displayed and that the muted password
  // is in the list.
  VerifyDismissedWarningsPageIsVisible();
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      assertWithMatcher:grey_sufficientlyVisible()];

  // Open the password's details.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap the "Restore Warning" button.
  [[EarlGrey selectElementWithMatcher:RestoreWarningButton()]
      performAction:grey_tap()];

  // Wait until the details view is dismissed.
  [ChromeEarlGreyUI waitForAppToIdle];

  // Check that the current view is now the compromised password issues view.
  VerifyCompromisedPasswordIssuesPageIsVisible(/*issue_count=*/1);

  // Verify that the compromised password is present in the list and that the
  // "Dismissed Warning" cell is not present anymore.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      assertWithMatcher:grey_sufficientlyVisible()];
  [password_manager_test_utils::GetInteractionForIssuesListItem(
      CompromisedPasswordIssuesDismissedWarnings(), kGREYDirectionDown)
      assertWithMatcher:grey_notVisible()];
}

// Tests deleting the last saved password through Password Checkup.
- (void)testDeleteLastPassword {
  SaveCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Open the compromised issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];

  // Open the password's details.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      performAction:grey_tap()];

  // Enter edit mode and delete the password.
  password_manager_test_utils::TapNavigationBarEditButton();
  DeleteCredential(kDefaultUsername, kDefaultSite);

  // Wait until the details view is dismissed.
  [ChromeEarlGreyUI waitForAppToIdle];

  // Verify that the empty view of Password Manager is now displayed.
  [[EarlGrey selectElementWithMatcher:password_manager_test_utils::
                                          PasswordManagerEmptyView()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests resolving the last reused passwords issue by editing a password through
// Password Checkup.
- (void)testResolveLastIssueByEditingPassword {
  SaveReusedPasswordFormsToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateReusedPasswords,
      /*result_password_count=*/2);

  // Open the reused issues page.
  [[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageReusedPasswordsItem()]
      performAction:grey_tap()];

  // Open one of the password's details.
  [GetInteractionForPasswordIssueEntry(kDomain1, kDefaultUsername)
      performAction:grey_tap()];

  // Enter edit mode and change the password to something that's not weak.
  password_manager_test_utils::TapNavigationBarEditButton();
  EditPassword(kSafePassword);

  GoBackToPreviousPage();

  // Wait until the details view is dismissed.
  [ChromeEarlGreyUI waitForAppToIdle];

  // Verify that the Password Checkup Homepage is now displayed and that tapping
  // the reused passwords item doesn't open the issues page since there are no
  // reused issues left.
  [[[[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageReusedPasswordsItem()]
      assertWithMatcher:grey_sufficientlyVisible()] performAction:grey_tap()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests resolving the last compromised passwords issue by deleting a password
// through Password Checkup.
- (void)testResolveLastIssueByDeletingPassword {
  SavePasswordFormToProfileStore(kSafePassword, kDefaultUsername, kSite1);
  SaveCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Open the compromised issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];

  // Open the password's details.

  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      performAction:grey_tap()];

  // Enter edit mode and change the password to something that's not weak.
  password_manager_test_utils::TapNavigationBarEditButton();
  DeleteCredential(kDefaultUsername, kDefaultSite);

  // Wait until the details view is dismissed.
  [ChromeEarlGreyUI waitForAppToIdle];

  // Verify that the Password Checkup Homepage is now displayed and that tapping
  // the compromised passwords item doesn't open the issues page since there are
  // no compromised issues left.
  [[[[EarlGrey selectElementWithMatcher:
                   PasswordCheckupHomepageCompromisedPasswordsItem()]
      assertWithMatcher:grey_sufficientlyVisible()] performAction:grey_tap()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests resolving the last compromised passwords issue by deleting a password
// through Password Checkup.
- (void)testChangeCompromisedPasswordToSafePassword {
  SaveCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Open the compromised issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];

  // Open the password's details.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:DismissWarningButton()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Enter edit mode and change the password to something that's not
  // compromised.
  password_manager_test_utils::TapNavigationBarEditButton();
  EditPassword(kSafePassword);

  // Verify that the compromised warning and the "Dismiss Warning" button are
  // now gone.
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:DismissWarningButton()]
      assertWithMatcher:grey_notVisible()];
}

// Tests changing the password of a muted compromised password to a weak
// password.
- (void)testChangeMutedPasswordToWeakPassword {
  SaveMutedCompromisedPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateDismissedWarnings,
      /*result_password_count=*/1);

  // Open the compromised issues page.
  [[EarlGrey selectElementWithMatcher:
                 PasswordCheckupHomepageCompromisedPasswordsItem()]
      performAction:grey_tap()];

  // Open the dismissed warnings page.
  [[EarlGrey
      selectElementWithMatcher:CompromisedPasswordIssuesDismissedWarnings()]
      performAction:grey_tap()];

  // Open the password's details.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername,
                                       LeakedPasswordDescription())
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:RestoreWarningButton()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Enter edit mode and change the password to something that's not
  // compromised.
  password_manager_test_utils::TapNavigationBarEditButton();
  EditPassword(kWeakPassword);

  // Verify that the compromised warning and the "Restore Warning" button are
  // now gone.
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:RestoreWarningButton()]
      assertWithMatcher:grey_notVisible()];

  GoBackToPreviousPage();

  // Wait until the details view is dismissed.
  [ChromeEarlGreyUI waitForAppToIdle];

  // Verify that the Password Checkup Homepage is now displayed and that tapping
  // the compromised passwords item doesn't open the issues page since there are
  // no muted compromised issues left.
  [[[[EarlGrey selectElementWithMatcher:
                   PasswordCheckupHomepageCompromisedPasswordsItem()]
      assertWithMatcher:grey_sufficientlyVisible()] performAction:grey_tap()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Verify that tapping the weak passwords item opens the weak password issues
  // page since there is now a weak password issue.
  [[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageWeakPasswordsItem()]
      performAction:grey_tap()];
  VerifyWeakPasswordIssuesPageIsVisible(/*issue_count=*/1);
}

// Tests the details page of a credential that is both weak and compromised when
// opened from the weak issues page.
- (void)testCompromisedAndWeakPasswordOpenedInWeakContext {
  SaveCompromisedPasswordFormToProfileStore(kWeakPassword);

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Open the weak issues page.
  [[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageWeakPasswordsItem()]
      performAction:grey_tap()];

  // Open the password's details.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername)
      performAction:grey_tap()];

  // Verify that the compromised warning and the "Dismiss Warning" button are
  // not present.
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:DismissWarningButton()]
      assertWithMatcher:grey_notVisible()];
}

// Tests the details page of a credential that is both reused and compromised
// when opened from the reused issues page.
- (void)testCompromisedAndReusedPasswordOpenedInReusedContext {
  SaveCompromisedPasswordFormToProfileStore(kReusedPassword);
  SaveReusedPasswordFormsToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateUnmutedCompromisedPasswords,
      /*result_password_count=*/1);

  // Open the reused issues page.
  [[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageReusedPasswordsItem()]
      performAction:grey_tap()];

  // Open the password's details.
  [GetInteractionForPasswordIssueEntry(kDefaultDomain, kDefaultUsername)
      performAction:grey_tap()];

  // Verify that the compromised warning and the "Dismiss Warning" button are
  // not present.
  [[EarlGrey selectElementWithMatcher:CompromisedWarning()]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:DismissWarningButton()]
      assertWithMatcher:grey_notVisible()];
}

// Tests that Password Checkup Homepage is dismissed when there are no saved
// passwords.
- (void)testPasswordCheckupDismissedAfterAllPasswordsGone {
  SavePasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateSafe,
      /*result_password_count=*/0);

  [PasswordSettingsAppInterface clearProfilePasswordStore];

  // Verify that the Password Checkup Homepage is dismissed.
  [[EarlGrey
      selectElementWithMatcher:
          grey_accessibilityID(password_manager::kPasswordCheckupTableViewId)]
      assertWithMatcher:grey_nil()];
}

// Validates that the Password Manager UI is dismissed when local authentication
// fails while in the Password Issues UI.
- (void)testPasswordIssuesWithFailedAuth {
  SaveWeakPasswordFormToProfileStore();

  OpenPasswordCheckupHomepage(
      /*result_state=*/PasswordCheckStateWeakPasswords,
      /*result_password_count=*/1);

  // Verify that tapping the reused passwords item opens the reused password
  // issues page.
  [[EarlGrey
      selectElementWithMatcher:PasswordCheckupHomepageWeakPasswordsItem()]
      performAction:grey_tap()];
  VerifyWeakPasswordIssuesPageIsVisible(/*issue_count=*/1);

  [PasswordSettingsAppInterface mockReauthenticationModuleExpectedResult:
                                    ReauthenticationResult::kFailure];
  [PasswordSettingsAppInterface
      mockReauthenticationModuleShouldReturnSynchronously:NO];

  [[AppLaunchManager sharedManager] backgroundAndForegroundApp];

  // Reauthentication UI should be covering Password Issues.
  [[EarlGrey
      selectElementWithMatcher:WeakPasswordIssuesPageTitle(/*issue_count=*/1)]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:ReauthenticationController()]
      assertWithMatcher:grey_sufficientlyVisible()];

  [PasswordSettingsAppInterface mockReauthenticationModuleReturnMockedResult];

  // Password Manager UI should have been dismissed leaving Settings visible.
  [[EarlGrey
      selectElementWithMatcher:WeakPasswordIssuesPageTitle(/*issue_count=*/1)]
      assertWithMatcher:grey_notVisible()];
  [[EarlGrey selectElementWithMatcher:ReauthenticationController()]
      assertWithMatcher:grey_notVisible()];

  [[EarlGrey selectElementWithMatcher:SettingsCollectionView()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

@end