chromium/ios/chrome/browser/ui/reading_list/reading_list_account_storage_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 "base/test/ios/wait_util.h"
#import "components/reading_list/features/reading_list_switches.h"
#import "components/signin/public/base/consent_level.h"
#import "components/signin/public/base/signin_pref_names.h"
#import "ios/chrome/browser/reading_list/model/reading_list_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/elements/activity_overlay_egtest_util.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_navigation_controller_constants.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/ui/authentication/authentication_constants.h"
#import "ios/chrome/browser/ui/authentication/signin/signin_constants.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/authentication/signin_matchers.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_app_interface.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_constants.h"
#import "ios/chrome/browser/ui/reading_list/reading_list_egtest_utils.h"
#import "ios/chrome/browser/ui/settings/settings_table_view_controller_constants.h"
#import "ios/chrome/common/ui/table_view/table_view_cells_constants.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/web_http_server_chrome_test_case.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "ui/base/l10n/l10n_util.h"

using chrome_test_util::DeleteButton;
using chrome_test_util::IdentityCellMatcherForEmail;
using chrome_test_util::PrimarySignInButton;
using chrome_test_util::ReadingListMarkAsReadButton;
using chrome_test_util::SecondarySignInButton;
using chrome_test_util::SettingsDoneButton;
using reading_list_test_utils::AddedToLocalReadingListSnackbar;
using reading_list_test_utils::AddURLToReadingListWithoutSnackbarDismiss;
using reading_list_test_utils::AddURLToReadingListWithSnackbarDismiss;
using reading_list_test_utils::OpenReadingList;
using reading_list_test_utils::ReadingListItem;
using reading_list_test_utils::VisibleLocalItemIcon;
using reading_list_test_utils::VisibleReadingListItem;

namespace {

NSString* const kReadTitle = @"foobar";
NSString* const kReadURL = @"http://readfoobar.com";
NSString* kPage1Title = @"Page 1 Title";
const char kPage1URL[] = "/page1";
NSString* kPage2Title = @"Page 2 Title";
const char kPage2URL[] = "/page2";
constexpr base::TimeDelta kLongPressDuration = base::Seconds(1);
constexpr base::TimeDelta kSyncInitializedTimeout = base::Seconds(5);

id<GREYMatcher> SignedInSnackbar(NSString* email) {
  NSString* snackbarMessage = l10n_util::GetNSStringF(
      IDS_IOS_SIGNIN_SNACKBAR_SIGNED_IN_AS, base::SysNSStringToUTF16(email));
  return grey_text(snackbarMessage);
}

id<GREYMatcher> SignedInSnackbarUndoButton() {
  return grey_accessibilityID(kSigninSnackbarUndo);
}

id<GREYMatcher> AddedToAccountReadingListSnackbarUndoButton() {
  return grey_accessibilityID(kReadingListAddedToAccountSnackbarUndoID);
}

// Provides responses containing a custom title for fake URLs.
std::unique_ptr<net::test_server::HttpResponse> StandardResponse(
    const net::test_server::HttpRequest& request) {
  std::unique_ptr<net::test_server::BasicHttpResponse> response =
      std::make_unique<net::test_server::BasicHttpResponse>();
  response->set_code(net::HTTP_OK);

  if (request.relative_url == kPage1URL) {
    response->set_content("<html><head><title>" +
                          base::SysNSStringToUTF8(kPage1Title) +
                          "</title></head></html>");
    return std::move(response);
  }
  if (request.relative_url == kPage2URL) {
    response->set_content("<html><head><title>" +
                          base::SysNSStringToUTF8(kPage2Title) +
                          "</title></head></html>");
    return std::move(response);
  }

  return nil;
}

}  // namespace

// Reading List integration tests for Chrome with account storage and UI
// enabled.
@interface ReadingListAccountStorageTestCase : ChromeTestCase
@end

@implementation ReadingListAccountStorageTestCase

- (void)setUp {
  [super setUp];
  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&StandardResponse));
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");
}

- (void)tearDown {
  GREYAssertNil([ReadingListAppInterface clearEntries],
                @"Unable to clear Reading List entries");

  // Close the Reading List if it is open.
  NSError* error = nil;
  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(kReadingListViewID)]
      assertWithMatcher:grey_notNil()
                  error:&error];
  if (!error) {
    [[EarlGrey
        selectElementWithMatcher:grey_accessibilityID(
                                     kTableViewNavigationDismissButtonId)]
        performAction:grey_tap()];
  }

  // Close tabs before clearing browsing history to prevent unneeded tabs from
  // reloading.
  [ChromeEarlGrey closeAllNormalTabs];

  [ChromeEarlGrey clearBrowsingHistory];
  // Prevent failure due to clear browsing data spinner. Should be called
  // before [super tearDown] which calls sign-out.
  // TODO(crbug.com/40065405): Remove this when ChromeTestCase will always wait
  // for sign-out completion.
  [ChromeEarlGrey signOutAndClearIdentities];
  [ChromeEarlGrey waitForSyncEngineInitialized:NO
                                   syncTimeout:kSyncInitializedTimeout];
  // Shutdown network process after tests run to avoid hanging from
  // clearing browsing history.
  [ChromeEarlGrey killWebKitNetworkProcess];
  [super tearDown];
}

#pragma mark - Sign-in promo and snackbar

// Test that the Reading List sign-in promo is in the "no accounts" mode when
// there is no identity on the device.
- (void)testPromoWithNoMainAccount {
  OpenReadingList();
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeNoAccounts];
}

// Test that the Reading List sign-in promo show the existing identity when an
// identity exists on the device.
- (void)testPromoWithMainAccount {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  OpenReadingList();
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeSigninWithAccount];
}

// Test that when an identity is available on the device, the user can sign-in
// with a tap on the promo primary button, and when the sign-in is done, a
// snackbar with the user's email and a undo button is shown.
- (void)testSignInWithPromoPrimaryButton {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  // Sign-in with the existing identity with the promo.
  OpenReadingList();
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Wait for the signed-in snackbar with undo button.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:SignedInSnackbar(
                                              fakeIdentity.userEmail)];
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:SignedInSnackbarUndoButton()];

  // Dismiss the snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity.userEmail)]
      performAction:grey_tap()];

  // Verify that the identity is signed-in without sync and the promo is hidden.
  [SigninEarlGrey verifyPrimaryAccountWithEmail:fakeIdentity.userEmail
                                        consent:signin::ConsentLevel::kSignin];
  [SigninEarlGreyUI verifySigninPromoNotVisible];
}

// Test that when the "undo" button on the signed-in snackbar is tapped after a
// successful sign-in using the promo, the snackbar disappears, the identity is
// signed-out, and the promo reappears.
- (void)testUndoSignInWithSnackbar {
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  // Sign-in with the existing identity with the promo.
  OpenReadingList();
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Verify the snackbar is shown after sign-in and the identity is signed-in.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:SignedInSnackbar(
                                              fakeIdentity.userEmail)];
  [SigninEarlGrey verifyPrimaryAccountWithEmail:fakeIdentity.userEmail
                                        consent:signin::ConsentLevel::kSignin];
  // Tap on "Undo".
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(SignedInSnackbarUndoButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Verify that the snackbar disappears, the promo is shown, and the user is
  // signed-out.
  [ChromeEarlGrey
      waitForUIElementToDisappearWithMatcher:SignedInSnackbar(
                                                 fakeIdentity.userEmail)];
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeSigninWithAccount];
  [SigninEarlGrey verifySignedOut];
}

// Test that when multiple identities exist on the device, the user can sign-in
// with a secondary identity using the secondary button in the promo.
- (void)testSignInWithSecondaryAccountInPromo {
  FakeSystemIdentity* fakeIdentity1 = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity1];
  FakeSystemIdentity* fakeIdentity2 = [FakeSystemIdentity fakeIdentity2];
  [SigninEarlGrey addFakeIdentity:fakeIdentity2];
  // Use sign-in with the second account using the promo's secondary button.
  OpenReadingList();
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(SecondarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:IdentityCellMatcherForEmail(
                                          fakeIdentity2.userEmail)]
      performAction:grey_tap()];
  // Verify that the identity2 is signed-in without sync, and that the promo is
  // hidden.
  [SigninEarlGrey verifyPrimaryAccountWithEmail:fakeIdentity2.userEmail
                                        consent:signin::ConsentLevel::kSignin];
  [SigninEarlGreyUI verifySigninPromoNotVisible];
}

// Test that if the identity is signed-in.
- (void)testPromoHiddenAfterSignIn {
  // Sign-in.
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey signinWithFakeIdentity:fakeIdentity];
  // Verify that the promo is hidden in the Reading List.
  OpenReadingList();
  [SigninEarlGreyUI verifySigninPromoNotVisible];
}

// Tests that the sign-in is re-shown after the user signs-in and then signs-out
// while the reading list screen is still shown.
// See http://crbug.com/1432611.
- (void)testPromoReshowAfterSignInAndSignOut {
  FakeSystemIdentity* fakeIdentity1 = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity1];
  // Sign-in with identity1 with the promo.
  OpenReadingList();
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeSigninWithAccount];
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Verify that identity1 is signed-in and the promo is hidden.
  [SigninEarlGrey verifyPrimaryAccountWithEmail:fakeIdentity1.userEmail
                                        consent:signin::ConsentLevel::kSignin];
  [SigninEarlGreyUI verifySigninPromoNotVisible];

  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity1.userEmail)]
      performAction:grey_tap()];

  // Sign-out without changing the UI and verify that the promo is shown,
  // without spinner.
  [SigninEarlGrey signOut];
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeSigninWithAccount];
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(grey_accessibilityID(
                                              kSigninPromoActivityIndicatorId),
                                          grey_sufficientlyVisible(), nil)]
      assertWithMatcher:grey_nil()];
}

// Tests to sign-in with one identity, sign-out, and use the sign-in promo
// from Reading List to sign-in with a different identity.
- (void)testPromoSignInAfterSignOut {
  FakeSystemIdentity* fakeIdentity1 = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity1];
  FakeSystemIdentity* fakeIdentity2 = [FakeSystemIdentity fakeIdentity2];
  [SigninEarlGrey addFakeIdentity:fakeIdentity2];
  // Sign-in with identity1 with the promo.
  OpenReadingList();
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeSigninWithAccount];
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:SignedInSnackbar(
                                              fakeIdentity1.userEmail)];
  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity1.userEmail)]
      performAction:grey_tap()];

  // Sign-out & sign-in with the identity2.
  [SigninEarlGrey signOut];
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(SecondarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:IdentityCellMatcherForEmail(
                                          fakeIdentity2.userEmail)]
      performAction:grey_tap()];

  // Verify that the second account is signed-in.
  [SigninEarlGrey verifyPrimaryAccountWithEmail:fakeIdentity2.userEmail
                                        consent:signin::ConsentLevel::kSignin];
}

// Tests that the signin promo is shown again when last signed-in user removes
// data during sign-out.
- (void)testPromoShownWhenSyncDataIsRemoved {
  // Sign-in with sync with `fakeIdentity1`.
  [SigninEarlGrey signinWithFakeIdentity:[FakeSystemIdentity fakeIdentity1]];
  // Sign-out and remove data.
  [ChromeEarlGrey signOutAndClearIdentities];

  OpenReadingList();
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeNoAccounts];
}

// Tests that the signin promo is shown when last syncing user did not remove
// data during sign-out but the batch upload promo is visible in the bookamrks
// manager.
- (void)testPromoShownWhenSyncDataNotRemovedWithBookmarksUpload {
  // Add last syncing account to mimic signing out without clearing data.
  [ChromeEarlGrey setStringValue:[FakeSystemIdentity fakeIdentity1].gaiaID
                     forUserPref:prefs::kGoogleServicesLastSyncingGaiaId];

  OpenReadingList();
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeNoAccounts];
}

// Tests to sign-in in incognito mode with the promo.
// See http://crbug.com/1432747.
- (void)testSignInPromoInIncognito {
  // Add identity to sign-in with.
  FakeSystemIdentity* fakeIdentity1 = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity1];
  // Open the Reading List in incognito mode.
  [ChromeEarlGrey openNewIncognitoTab];
  OpenReadingList();
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeSigninWithAccount];
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(
                                   chrome_test_util::PrimarySignInButton(),
                                   grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];

  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity1.userEmail)]
      performAction:grey_tap()];

  // Result: the sign-in is successful without any issue.
  [SigninEarlGrey verifyPrimaryAccountWithEmail:fakeIdentity1.userEmail
                                        consent:signin::ConsentLevel::kSignin];
}

// Tests that if the data is reloaded after the account storage promo is shown,
// the promo item is still shown.
// See https://crbug.com/1439243.
- (void)testPromoShownAfterContentReload {
  OpenReadingList();
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeNoAccounts];
  GREYAssertNil(
      [ReadingListAppInterface addEntryWithURL:[NSURL URLWithString:kReadURL]
                                         title:kReadTitle
                                          read:YES],
      @"Unable to add Reading List item");
  [SigninEarlGreyUI
      verifySigninPromoVisibleWithMode:SigninPromoViewModeNoAccounts];
}

#pragma mark - Local & account storage items

// When adding an item and there's no signed-in account, test that the standard
// "Added to Reading List" snackbar is shown and there's no cloud icon on the
// new item.
- (void)testAddItemWhenSignedOut {
  AddURLToReadingListWithoutSnackbarDismiss(self.testServer->GetURL(kPage1URL));
  // Verify that the right snackbar appears and there's no undo button on it.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:AddedToLocalReadingListSnackbar()];
  [[EarlGrey selectElementWithMatcher:
                 grey_allOf(AddedToAccountReadingListSnackbarUndoButton(),
                            grey_sufficientlyVisible(), nil)]
      assertWithMatcher:grey_nil()];

  // Dismiss the snackbar.
  [[EarlGrey selectElementWithMatcher:AddedToLocalReadingListSnackbar()]
      performAction:grey_tap()];

  // Verify there's no cloud icon on the new item in the Reading List.
  OpenReadingList();
  [[EarlGrey selectElementWithMatcher:VisibleLocalItemIcon(kPage1Title)]
      assertWithMatcher:grey_nil()];
}

// Add a page when signed-out and another after sign-in with the Reading List
// promo. Test that only the first item has the cloud icon in the Reading List.
- (void)testAddItemWithAccountStorage {
  AddURLToReadingListWithoutSnackbarDismiss(self.testServer->GetURL(kPage1URL));
  // Dismiss the snackbar.
  [[EarlGrey selectElementWithMatcher:AddedToLocalReadingListSnackbar()]
      performAction:grey_tap()];

  // Sign-in with the Reading List promo.
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  OpenReadingList();
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity.userEmail)]
      performAction:grey_tap()];

  // Ensure that the first sync spinner has disappeared.
  [ChromeEarlGreyUI waitForAppToIdle];
  [ChromeEarlGrey
      waitForSyncTransportStateActiveWithTimeout:kSyncInitializedTimeout];
  // Verify that the cloud icon is shown on the first item.
  [[EarlGrey selectElementWithMatcher:VisibleLocalItemIcon(kPage1Title)]
      assertWithMatcher:grey_notNil()];
  // Close the Reading List.
  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                          kTableViewNavigationDismissButtonId)]
      performAction:grey_tap()];
  // Add Page 2 to the Reading List and verify that the snackbar containing the
  // user's email and an undo button appears.
  AddURLToReadingListWithSnackbarDismiss(self.testServer->GetURL(kPage2URL),
                                         fakeIdentity.userEmail);

  // Verify that both items are visible in the Reading List, and that there's
  // one cloud icon on the first item, but none on the second.
  OpenReadingList();
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage1Title)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage2Title)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:VisibleLocalItemIcon(kPage1Title)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:VisibleLocalItemIcon(kPage2Title)]
      assertWithMatcher:grey_nil()];
}

// When signed-in with the Reading List promo, test that tapping on "Undo" from
// the "item added" snackbar removes the item from the Reading List.
- (void)testUndoAddItemWithAccountStorage {
  // Sign-in with the Reading List promo.
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  OpenReadingList();
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity.userEmail)]
      performAction:grey_tap()];

  [ChromeEarlGrey
      waitForSyncTransportStateActiveWithTimeout:kSyncInitializedTimeout];
  // Close the Reading List.
  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                          kTableViewNavigationDismissButtonId)]
      performAction:grey_tap()];
  // Add Page 1 to the Reading List.
  AddURLToReadingListWithoutSnackbarDismiss(self.testServer->GetURL(kPage1URL));
  // Tap on undo when the snackbar appears.
  [ChromeEarlGrey
      waitForAndTapButton:grey_allOf(
                              AddedToAccountReadingListSnackbarUndoButton(),
                              grey_sufficientlyVisible(), nil)];
  // Verify that Page 1 is not in the Reading List.
  OpenReadingList();
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage1Title)]
      assertWithMatcher:grey_nil()];
}

// Test that the item added to account Reading List disappears when signed-out.
- (void)testAddAccountItemThenSignOut {
  AddURLToReadingListWithSnackbarDismiss(self.testServer->GetURL(kPage1URL),
                                         nil);

  // Sign-in with fakeIdentity in the Reading List.
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  OpenReadingList();
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity.userEmail)]
      performAction:grey_tap()];

  [ChromeEarlGrey
      waitForSyncTransportStateActiveWithTimeout:kSyncInitializedTimeout];
  // Close the Reading List.
  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                          kTableViewNavigationDismissButtonId)]
      performAction:grey_tap()];
  // Add Page 2 to the Reading List.
  AddURLToReadingListWithSnackbarDismiss(self.testServer->GetURL(kPage2URL),
                                         fakeIdentity.userEmail);

  // Sign-out.
  [SigninEarlGrey signOut];
  [ChromeEarlGrey waitForSyncEngineInitialized:NO
                                   syncTimeout:kSyncInitializedTimeout];
  // Verify that only Page 1 is visible with no cloud icon.
  OpenReadingList();
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage1Title)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage2Title)]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:VisibleLocalItemIcon(kPage1Title)]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:VisibleLocalItemIcon(kPage2Title)]
      assertWithMatcher:grey_nil()];
}

// Test that after sign-in with the Reading List promo, if two items are added
// and one is removed, then after a sign-out and a new sign-in with the Reading
// List sign-in promo with the same account, the removed item is not visible.
- (void)testRemoveItemAfterSignInThenRefreshSignin {
  // Sign-in with the Reading List Promo.
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  OpenReadingList();
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity.userEmail)]
      performAction:grey_tap()];

  [ChromeEarlGrey
      waitForSyncTransportStateActiveWithTimeout:kSyncInitializedTimeout];
  // Close the Reading List.
  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                          kTableViewNavigationDismissButtonId)]
      performAction:grey_tap()];
  // Add pages to the Reading List and dismiss the snackbars.
  AddURLToReadingListWithSnackbarDismiss(self.testServer->GetURL(kPage1URL),
                                         fakeIdentity.userEmail);
  AddURLToReadingListWithSnackbarDismiss(self.testServer->GetURL(kPage2URL),
                                         fakeIdentity.userEmail);
  // Remove Page 1 from the Reading List.
  OpenReadingList();
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage1Title)]
      performAction:grey_longPressWithDuration(kLongPressDuration)];
  [[EarlGrey selectElementWithMatcher:DeleteButton()] performAction:grey_tap()];
  // Verify that only Page 2 is in the Reading List.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:VisibleReadingListItem(kPage2Title)];
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage1Title)]
      assertWithMatcher:grey_nil()];
  // Sign-out and sign-in with the same account.
  [SigninEarlGrey signOut];
  [ChromeEarlGrey waitForSyncEngineInitialized:NO
                                   syncTimeout:kSyncInitializedTimeout];
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity.userEmail)]
      performAction:grey_tap()];

  [ChromeEarlGrey
      waitForSyncTransportStateActiveWithTimeout:kSyncInitializedTimeout];
  // Verify that only the page 2 is still in the Reading list.
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage1Title)]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage2Title)]
      assertWithMatcher:grey_notNil()];
}

// Test that after a sign-in with the Reading List sign-in promo, if two unread
// entries are added, then the first item is marked as unread, the read and
// unread items sections should be shown correctly and remain so after a
// sign-out & sign-in with the same account.
- (void)testMoveItemThenRefreshSignIn {
  // Sign-in with the Reading List Promo.
  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
  [SigninEarlGrey addFakeIdentity:fakeIdentity];
  OpenReadingList();
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity.userEmail)]
      performAction:grey_tap()];

  [ChromeEarlGrey
      waitForSyncTransportStateActiveWithTimeout:kSyncInitializedTimeout];
  // Close the Reading List.
  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                          kTableViewNavigationDismissButtonId)]
      performAction:grey_tap()];
  // Add pages to the Reading List and dismiss the snackbars.
  AddURLToReadingListWithSnackbarDismiss(self.testServer->GetURL(kPage1URL),
                                         fakeIdentity.userEmail);
  AddURLToReadingListWithSnackbarDismiss(self.testServer->GetURL(kPage2URL),
                                         fakeIdentity.userEmail);

  // Mark Page 1 as read.
  OpenReadingList();
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage1Title)]
      performAction:grey_longPressWithDuration(kLongPressDuration)];
  [[EarlGrey selectElementWithMatcher:ReadingListMarkAsReadButton()]
      performAction:grey_tap()];
  // Wait one second since the reading list items may update multiple times.
  // TODO(crbug.com/40268339): Check if this delay can be replaced by the use of
  // waitForUIElementToAppearWithMatcher instead.
  base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(1));
  // Verify that the unread and the read sections headers are visible.
  NSString* readHeaderText =
      l10n_util::GetNSString(IDS_IOS_READING_LIST_READ_HEADER);
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(grey_text(readHeaderText),
                                          grey_sufficientlyVisible(), nil)]
      assertWithMatcher:grey_notNil()];
  NSString* unreadHeaderText =
      l10n_util::GetNSString(IDS_IOS_READING_LIST_UNREAD_HEADER);
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(grey_text(unreadHeaderText),
                                          grey_sufficientlyVisible(), nil)]
      assertWithMatcher:grey_notNil()];
  // Verify that both items are visible and only one of them is unread.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:ReadingListItem(kPage1Title)];
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage2Title)]
      assertWithMatcher:grey_notNil()];
  GREYAssertEqual([ReadingListAppInterface readEntriesCount], 1,
                  @"The read entries count is incorrect.");
  GREYAssertEqual([ReadingListAppInterface unreadEntriesCount], 1,
                  @"The unread entries count is incorrect.");
  // Sign-out and sign-in with the same account.
  [SigninEarlGrey signOut];
  [ChromeEarlGrey waitForSyncEngineInitialized:NO
                                   syncTimeout:kSyncInitializedTimeout];
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(PrimarySignInButton(),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_tap()];
  // Dismiss the sign-in snackbar.
  [[EarlGrey selectElementWithMatcher:SignedInSnackbar(fakeIdentity.userEmail)]
      performAction:grey_tap()];

  [ChromeEarlGrey
      waitForSyncTransportStateActiveWithTimeout:kSyncInitializedTimeout];
  // Verify that both items are visible and only one of them is unread.
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage1Title)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:VisibleReadingListItem(kPage2Title)]
      assertWithMatcher:grey_notNil()];
  GREYAssertEqual([ReadingListAppInterface readEntriesCount], 1,
                  @"The read entries count is incorrect.");
  GREYAssertEqual([ReadingListAppInterface unreadEntriesCount], 1,
                  @"The unread entries count is incorrect.");
}

@end