// 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/format_macros.h"
#import "base/i18n/message_formatter.h"
#import "base/ios/ios_util.h"
#import "base/strings/string_util.h"
#import "base/strings/stringprintf.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/time/time.h"
#import "components/bookmarks/common/bookmark_pref_names.h"
#import "components/commerce/core/proto/price_tracking.pb.h"
#import "components/unified_consent/pref_names.h"
#import "ios/chrome/browser/authentication/test/signin_earl_grey.h"
#import "ios/chrome/browser/authentication/test/signin_earl_grey_ui_test_util.h"
#import "ios/chrome/browser/authentication/ui_bundled/signin/signin_constants.h"
#import "ios/chrome/browser/bookmarks/model/bookmark_storage_type.h"
#import "ios/chrome/browser/bookmarks/test/bookmark_earl_grey.h"
#import "ios/chrome/browser/history/ui_bundled/history_ui_constants.h"
#import "ios/chrome/browser/metrics/model/metrics_app_interface.h"
#import "ios/chrome/browser/optimization_guide/model/optimization_guide_test_app_interface.h"
#import "ios/chrome/browser/popup_menu/public/popup_menu_constants.h"
#import "ios/chrome/browser/reading_list/ui_bundled/reading_list_app_interface.h"
#import "ios/chrome/browser/settings/ui_bundled/settings_app_interface.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_features.h"
#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/grid/grid_constants.h"
#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_grid_constants.h"
#import "ios/chrome/browser/tab_switcher/ui_bundled/tab_grid/tab_groups/tab_groups_eg_utils.h"
#import "ios/chrome/browser/tab_switcher/ui_bundled/test/tabs_egtest_util.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_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/chrome/test/earl_grey/chrome_xcui_actions.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "net/base/apple/url_conversions.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "net/test/embedded_test_server/http_request.h"
#import "net/test/embedded_test_server/http_response.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util.h"

using base::test::ios::kWaitForUIElementTimeout;
using chrome_test_util::AddToBookmarksButton;
using chrome_test_util::AddToReadingListButton;
using chrome_test_util::CloseTabMenuButton;
using chrome_test_util::IncognitoTabGrid;
using chrome_test_util::LongPressCellAndDragToEdge;
using chrome_test_util::LongPressCellAndDragToOffsetOf;
using chrome_test_util::RegularTabGrid;
using chrome_test_util::TabGridCellAtIndex;
using chrome_test_util::TabGridInactiveTabsButton;
using chrome_test_util::TabGridIncognitoTabsPanelButton;
using chrome_test_util::TabGridNewIncognitoTabButton;
using chrome_test_util::TabGridNormalModePageControl;
using chrome_test_util::TabGridOpenTabsPanelButton;
using chrome_test_util::TabGridOtherDevicesPanelButton;
using chrome_test_util::TabGridOverflowMenuButton;
using chrome_test_util::TabGridOverflowMenuCloseAllButton;
using chrome_test_util::TabGridOverflowMenuSelectTabsButton;
using chrome_test_util::TabGridSearchBar;
using chrome_test_util::TabGridSearchCancelButton;
using chrome_test_util::TabGridSearchModeToolbar;
using chrome_test_util::TabGridSearchTabsButton;
using chrome_test_util::TabGridTabGroupsPanelButton;
using chrome_test_util::TapAtOffsetOf;
using chrome_test_util::WindowWithNumber;

namespace {
const char kSearchEngineURL[] = "/searchengine/?q={searchTerms}";
const char kSearchEngineHost[] = "searchengine";

char kCountryCode[] = "us";
char kURL1[] = "/firstURL";
char kURL2[] = "/secondURL";
char kURL3[] = "/thirdURL";
char kURL4[] = "/fourthURL";
NSString* const kTitle1 = @"Page one";
NSString* const kTitle2 = @"Page two";
NSString* const kTitle4 = @"Page four";
char kResponse1[] = "Test Page 1 content";
char kResponse2[] = "Test Page 2 content";
char kResponse3[] = "Test Page 3 content";
char kResponse4[] = "Test Page 4 content";
NSString* const kTypeURL =
    @"type.googleapis.com/optimization_guide.proto.PriceTrackingData";
const int64_t kCurrentPriceMicros = 5'000'000;
const int64_t kPreviousPriceMicros = 10'000'000;
const int64_t kOfferId = 50;
const char kCurrencyCode[] = "USD";
NSString* const kExpectedCurrentPrice = @"$5.00";
NSString* const kExpectedPreviousPrice = @"$10";

// Key string for Escape in keyboard simulation.
NSString* const kEscapeKey = @"escape";

// Identifier for cell at given `index` in the tab grid.
NSString* IdentifierForCellAtIndex(unsigned int index) {
  return [NSString stringWithFormat:@"%@%u", kGridCellIdentifierPrefix, index];
}

id<GREYMatcher> DeselectAllButton() {
  return grey_allOf(chrome_test_util::ButtonWithAccessibilityLabelId(
                        IDS_IOS_TAB_GRID_DESELECT_ALL_BUTTON),
                    grey_userInteractionEnabled(), nullptr);
}

id<GREYMatcher> SelectAllButton() {
  return grey_allOf(chrome_test_util::ButtonWithAccessibilityLabelId(
                        IDS_IOS_TAB_GRID_SELECT_ALL_BUTTON),
                    grey_userInteractionEnabled(), nullptr);
}


// Returns a matcher for the scrim view on the tab search.
id<GREYMatcher> VisibleSearchScrim() {
  return grey_allOf(grey_accessibilityID(kTabGridScrimIdentifier),
                    grey_minimumVisiblePercent(0.5), nil);
}

// Returns a matcher for the search bar text field containing `searchText`.
id<GREYMatcher> SearchBarWithSearchText(NSString* searchText) {
  return grey_accessibilityID([kTabGridSearchTextFieldIdentifierPrefix
      stringByAppendingString:searchText]);
}

// Returns a matcher for the search results header with title set with
// `title_id`.
id<GREYMatcher> SearchSectionHeaderWithTitleID(int title_id) {
  id<GREYMatcher> title_matcher =
      grey_allOf(grey_accessibilityLabel(l10n_util::GetNSString(title_id)),
                 grey_sufficientlyVisible(), nil);
  return grey_allOf(grey_accessibilityID(kGridSectionHeaderIdentifier),
                    grey_descendant(title_matcher), grey_sufficientlyVisible(),
                    nil);
}

// Returns a matcher for the search results open tabs section header.
id<GREYMatcher> SearchOpenTabsSectionHeader() {
  return SearchSectionHeaderWithTitleID(
      IDS_IOS_TABS_SEARCH_OPEN_TABS_SECTION_HEADER_TITLE);
}

// Returns a matcher for the search results suggested actions section header.
id<GREYMatcher> SearchSuggestedActionsSectionHeader() {
  return SearchSectionHeaderWithTitleID(IDS_IOS_TABS_SEARCH_SUGGESTED_ACTIONS);
}

// Returns a matcher for the search results open tabs section header with
// `count` set in the value label .
id<GREYMatcher> SearchOpenTabsHeaderWithValue(size_t count) {
  NSString* count_str = [NSString stringWithFormat:@"%" PRIuS, count];
  NSString* value = l10n_util::GetNSStringF(
      IDS_IOS_TABS_SEARCH_OPEN_TABS_COUNT, base::SysNSStringToUTF16(count_str));
  id<GREYMatcher> value_matcher = grey_allOf(grey_accessibilityLabel(value),
                                             grey_sufficientlyVisible(), nil);

  return grey_allOf(SearchOpenTabsSectionHeader(),
                    grey_descendant(value_matcher), grey_sufficientlyVisible(),
                    nil);
}

// Returns a matcher for the "Search on web" suggested action.
id<GREYMatcher> SearchOnWebSuggestedAction() {
  return grey_allOf(chrome_test_util::StaticTextWithAccessibilityLabelId(
                        IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_WEB),
                    grey_sufficientlyVisible(), nil);
}

// Returns a matcher for the "Search history" suggested action.
id<GREYMatcher> SearchHistorySuggestedAction() {
  return grey_allOf(grey_accessibilityID(kTabGridSearchSuggestedHistoryItemId),
                    grey_sufficientlyVisible(), nil);
}

// Returns a matcher for the "Search history (`matches_count` Found)" suggested
// action on the regular tab grid.
id<GREYMatcher> SearchHistorySuggestedActionWithMatches(size_t matches_count) {
  NSString* count_str = [NSString stringWithFormat:@"%" PRIuS, matches_count];
  NSString* history_label = l10n_util::GetNSStringF(
      IDS_IOS_TABS_SEARCH_SUGGESTED_ACTION_SEARCH_HISTORY,
      base::SysNSStringToUTF16(count_str));
  return grey_allOf(grey_accessibilityLabel(history_label),
                    grey_sufficientlyVisible(), nil);
}

// Returns a matcher for the search suggested actions section.
id<GREYMatcher> SearchSuggestedActionsSection() {
  return grey_allOf(grey_accessibilityID(kSuggestedActionsGridCellIdentifier),
                    grey_sufficientlyVisible(), nil);
}

// Returns a matcher for the search suggested actions section with the history
// item matches count set to `matches_count`.
id<GREYMatcher> SearchSuggestedActionsSectionWithHistoryMatchesCount(
    size_t matches_count) {
  return grey_allOf(
      SearchSuggestedActionsSection(),
      grey_descendant(SearchHistorySuggestedActionWithMatches(matches_count)),
      grey_sufficientlyVisible(), nil);
}
// Matcher for the select tabs button in the context menu.
id<GREYMatcher> SelectTabsContextMenuItem() {
  return chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
      IDS_IOS_CONTENT_CONTEXT_SELECTTABS);
}

// Matcher for the Close Other Tabs button in the context menu.
id<GREYMatcher> CloseOtherTabsButton() {
  return chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
      IDS_IOS_CONTENT_CONTEXT_CLOSEOTHERTABS);
}

// Type `text` into the TabGridSearchBar and press enter.
void PerformTabGridSearch(NSString* text) {
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(text)];
  // TODO(crbug.com/40916974): Use simulatePhysicalKeyboardEvent until
  // replaceText can properly handle \n.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"\n" flags:0];
}

// Handles requests to `/searchengine` by echoing the URL in the response body.
std::unique_ptr<net::test_server::HttpResponse> HandleSearchEngineRequest(
    const net::test_server::HttpRequest& request) {
  if (!base::StartsWith(request.relative_url, "/searchengine")) {
    return nullptr;
  }
  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
  response->set_code(net::HTTP_OK);
  response->set_content_type("text/html");
  std::string url_string = base::ToLowerASCII(request.relative_url);
  response->set_content(
      base::StringPrintf("<html><body>%s</body></html>", url_string.c_str()));
  return response;
}

// Handles requests by looking up the relative URL in the provided response map.
std::unique_ptr<net::test_server::HttpResponse> HandleMappedResponseRequest(
    const std::map<std::string, std::string>* responses,
    const net::test_server::HttpRequest& request) {
  auto it = responses->find(request.relative_url);
  if (it == responses->end()) {
    return nullptr;
  }
  auto response = std::make_unique<net::test_server::BasicHttpResponse>();
  response->set_code(net::HTTP_OK);
  response->set_content_type("text/html");
  response->set_content(it->second);
  return response;
}

// Main request handler that delegates to specific handlers.
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
    const std::map<std::string, std::string>* responses,
    const net::test_server::HttpRequest& request) {
  if (auto response = HandleSearchEngineRequest(request)) {
    return response;
  }
  return HandleMappedResponseRequest(responses, request);
}

}  // namespace

@interface TabGridTestCase : ChromeTestCase {
  GURL _URL1;
  GURL _URL2;
  GURL _URL3;
  GURL _URL4;
  std::map<std::string, std::string> _responses;
}
@end

@implementation TabGridTestCase

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config = [super appConfigurationForTestCase];
  if ([self isRunningTest:@selector(testTapOnSearchScrimExitsSearchMode)]) {
    config.features_disabled.push_back(kChromeNextIa);
  } else {
    config.features_enabled.push_back(kChromeNextIa);
  }

  return config;
}

- (void)setUp {
  [super setUp];

  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&HandleRequest, base::Unretained(&_responses)));

  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  _URL1 = self.testServer->GetURL(kURL1);
  _URL2 = self.testServer->GetURL(kURL2);
  _URL3 = self.testServer->GetURL(kURL3);
  _URL4 = self.testServer->GetURL(kURL4);

  const char kPageFormat[] = "<head><title>%s</title></head><body>%s</body>";
  _responses[kURL1] = base::StringPrintf(
      kPageFormat, base::SysNSStringToUTF8(kTitle1).c_str(), kResponse1);
  _responses[kURL2] = base::StringPrintf(
      kPageFormat, base::SysNSStringToUTF8(kTitle2).c_str(), kResponse2);
  _responses[kURL3] = kResponse3;
  _responses[kURL4] = base::StringPrintf(
      kPageFormat, base::SysNSStringToUTF8(kTitle4).c_str(), kResponse4);
}

- (void)tearDownHelper {
  // Ensure that the default search engine is reset.
  if ([self isRunningTest:@selector
            (testSearchOnWebSuggestedActionInRegularTabsSearch)]) {
    [SettingsAppInterface resetSearchEngine];
  }

  // Ensure that pref set in testTabGridItemContextMenuAddToBookmarkGreyed is
  // reset even if the test failed.
  if ([self isRunningTest:@selector
            (testTabGridItemContextMenuAddToBookmarkGreyed)]) {
    [ChromeEarlGrey setBoolValue:YES
                     forUserPref:bookmarks::prefs::kEditBookmarksEnabled];
  }

  // Shutdown network process after tests run to avoid hanging from
  // clearing browsing data.
  // See https://crbug.com/1419875.
  [ChromeEarlGrey killWebKitNetworkProcess];

  [ChromeEarlGrey removeUserDefaultsObjectForKey:@"InactiveTabsTestMode"];

  [super tearDownHelper];
}

// Tests entering and leaving the tab grid.
- (void)testEnteringAndLeavingTabGrid {
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

// Tests entering the tab grid using the button and leaving it using the Escape
// keyboard shortcut.
- (void)testEnteringAndLeavingTabGridWithEscape {
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      assertWithMatcher:grey_sufficientlyVisible()];
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:kEscapeKey flags:0];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that pressing a TabGrid keyboard shortcut (such as Cmd+1 to switch to
// the Incognito panel) when the tab grid is not visible does not trigger the
// TabGrid page change.
- (void)testShortcutIgnoredWhenTabGridNotVisible {
  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Send Cmd+1 (which is the shortcut for TabGridPageIncognitoTabs).
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"1"
                                          flags:UIKeyModifierCommand];

  // Open the TabGrid and verify it opened to the Regular Tabs panel.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridOpenTabsPanelButton()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that tapping on the first cell shows that tab.
- (void)testTappingOnFirstCell {
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that closing the cell shows no tabs, and displays the empty state.
- (void)testClosingFirstCell {
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridCloseButtonForCellAtIndex(0)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridRegularTabsEmptyStateView()]
      assertWithMatcher:grey_notNil()];
}

// Tests that tapping Close All shows no tabs and displays the empty state,
// and ensures that it doesn't affect the other tab grid.
- (void)testCloseAll {
  // Also add a tab in incognito.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey waitForIncognitoTabCount:1];

  // Open tab grid and go to regular tab page.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridNormalModePageControl()]
      performAction:grey_tap()];

  // Close all tabs.
  OpenTabGridOverflowMenu();
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridOverflowMenuCloseAllButton()]
      performAction:grey_tap()];

  // Confirm in action sheet.
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabelId(
                     IDS_IOS_CONTENT_CONTEXT_CLOSEALLTABSANDGROUPS)]
      performAction:grey_tap()];

  // Ensure normal tabs were closed.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      assertWithMatcher:grey_nil()];

  // Ensure the incognito tab isn't closed.
  GREYAssertEqual(1UL, [ChromeEarlGrey incognitoTabCount],
                  @"Expected that the \"Close All Tabs\" button should not "
                  @"close tabs in other pages.");

  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridRegularTabsEmptyStateView()]
      assertWithMatcher:grey_notNil()];
}

// Tests that Close All closes both regular and inactive tabs.
- (void)testCloseAllWithInactiveTabs {
  [self loadTestURLsInNewTabs];
  [self relaunchAppWithInactiveTabsTestMode];

  [ChromeEarlGreyUI openTabGrid];
  GREYAssertEqual(1UL, [ChromeEarlGrey mainTabCount],
                  @"Expected only one tab (NTP), all other tabs should have "
                  @"been in inactive tab grid.");
  GREYAssertEqual(4UL, [ChromeEarlGrey inactiveTabCount],
                  @"Expected 4 inactive tabs.");

  // Verify that the Inactive Tabs button is showing.
  [[EarlGrey selectElementWithMatcher:TabGridInactiveTabsButton()]
      assertWithMatcher:grey_notNil()];

  // Close all tabs.
  OpenTabGridOverflowMenu();
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridOverflowMenuCloseAllButton()]
      performAction:grey_tap()];

  // Confirm in action sheet.
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabelId(
                     IDS_IOS_CONTENT_CONTEXT_CLOSEALLTABSANDGROUPS)]
      performAction:grey_tap()];

  // Ensure regular and inactive tabs were closed.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridRegularTabsEmptyStateView()]
      assertWithMatcher:grey_notNil()];
  GREYAssertEqual(0UL, [ChromeEarlGrey mainTabCount],
                  @"Expected all regular tab to be closed.");
  GREYAssertEqual(0UL, [ChromeEarlGrey inactiveTabCount],
                  @"Expected all inactive tab to be closed.");

  // Verify that the Inactive Tabs button is not showing.
  [[EarlGrey selectElementWithMatcher:TabGridInactiveTabsButton()]
      assertWithMatcher:grey_nil()];
}

- (void)testPriceDrops {
  // TODO(crbug.com/439174222) mock ShoppingService rather than
  // OptimizationGuide.
  commerce::PriceTrackingData price_tracking_data;
  price_tracking_data.mutable_product_update()->set_offer_id(kOfferId);
  price_tracking_data.mutable_product_update()
      ->mutable_old_price()
      ->set_currency_code(kCurrencyCode);
  price_tracking_data.mutable_product_update()
      ->mutable_new_price()
      ->set_currency_code(kCurrencyCode);
  price_tracking_data.mutable_product_update()
      ->mutable_new_price()
      ->set_amount_micros(kCurrentPriceMicros);
  price_tracking_data.mutable_product_update()
      ->mutable_old_price()
      ->set_amount_micros(kPreviousPriceMicros);
  price_tracking_data.mutable_buyable_product()->set_country_code(kCountryCode);
  price_tracking_data.mutable_buyable_product()
      ->mutable_current_price()
      ->set_currency_code(kCurrencyCode);
  price_tracking_data.mutable_buyable_product()
      ->mutable_current_price()
      ->set_amount_micros(kCurrentPriceMicros);

  std::string serialized_price_tracking_data;
  price_tracking_data.SerializeToString(&serialized_price_tracking_data);
  [OptimizationGuideTestAppInterface
      addHintForTesting:base::SysUTF8ToNSString(_URL1.spec())
                   type:optimization_guide::proto::OptimizationType::
                            PRICE_TRACKING
         serialized_any:[[NSData alloc]
                            initWithBytes:serialized_price_tracking_data.c_str()
                                   length:serialized_price_tracking_data.size()]
               type_url:kTypeURL];
  [ChromeEarlGrey setBoolValue:YES
                   forUserPref:unified_consent::prefs::
                                   kUrlKeyedAnonymizedDataCollectionEnabled];
  [ChromeEarlGrey setBoolValue:YES
                   forUserPref:prefs::kTrackPricesOnTabsEnabled];

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

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGreyUI openTabGrid];
  id<GREYMatcher> new_price = grey_text(kExpectedCurrentPrice);
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:new_price];
  id<GREYMatcher> prev_price = grey_text(kExpectedPreviousPrice);
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:prev_price];
}

// Tests that tapping Close All from the incognito grid shows no tabs and does
// not show Undo button. Also ensure that it closes the expected tabs to avoid
// crbug.com/1475005.
- (void)testCloseAllForIncognitoGrid {
  // Opens 3 incognito tabs and 1 regular.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey openNewTab];

  [ChromeEarlGrey waitForMainTabCount:2];
  [ChromeEarlGrey waitForIncognitoTabCount:3];

  // Open the regular grid.
  [ChromeEarlGreyUI openTabGrid];

  // Scroll the tab grid to switch from regular grid to incognito grid.
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(grey_accessibilityID(
                                              kTabGridScrollViewIdentifier),
                                          grey_sufficientlyVisible(), nil)]
      performAction:grey_scrollToContentEdge(kGREYContentEdgeLeft)];

  // Close all incognito tabs
  OpenTabGridOverflowMenu();
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridOverflowMenuCloseAllButton()]
      performAction:grey_tap()];

  // Confirm in action sheet.
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabelId(
                     IDS_IOS_CONTENT_CONTEXT_CLOSEALLTABSANDGROUPS)]
      performAction:grey_tap()];

  // Ensure only incognito tabs were closed
  [ChromeEarlGrey waitForMainTabCount:2];
  [ChromeEarlGrey waitForIncognitoTabCount:0];
}

// Tests opening an incognito tabs after closing all incognito tabs with Chrome
// Next IA flag enabled. This is to prevent regression during development.
- (void)testIncognitoTabOperationsWithChromeNextIa {
  // Opens an incognito tab.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey waitForIncognitoTabCount:1];

  [ChromeEarlGreyUI openTabGrid];

  [ChromeEarlGrey closeAllIncognitoTabs];
  [ChromeEarlGrey waitForIncognitoTabCount:0];

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridNewIncognitoTabButton()]
      performAction:grey_tap()];

  [ChromeEarlGrey waitForIncognitoTabCount:1];
}

// Tests "Close Other Tabs" functionality from the Overflow menu.
- (void)testCloseOtherTabsUsingOverflowMenu {
  // Load 3 tabs with distinct content.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];
  [ChromeEarlGrey waitForMainTabCount:3];
  [ChromeEarlGreyUI openTabGrid];

  // Open Overflow Menu.
  OpenTabGridOverflowMenu();

  // Tap "Close Other Tabs" in the Overflow menu.
  [[EarlGrey selectElementWithMatcher:CloseOtherTabsButton()]
      performAction:grey_tap()];
  GREYWaitForAppToIdle(@"App failed to idle");

  // Expect 1 tab remaining.
  [ChromeEarlGrey waitForMainTabCount:1];

  // Verify the remaining tab is the one that was active (third tab).
  [[EarlGrey selectElementWithMatcher:TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];
}

// Tests "Close Other Tabs" functionality from the Context menu.
- (void)testCloseOtherTabsUsingContextMenu {
  // Load 3 tabs with distinct content.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];
  [ChromeEarlGrey waitForMainTabCount:3];
  [ChromeEarlGreyUI openTabGrid];

  // Long press on the second tab.
  [self longPressTabWithTitle:kTitle2];

  // Tap "Close Other Tabs" in the context menu.
  [[EarlGrey selectElementWithMatcher:CloseOtherTabsButton()]
      performAction:grey_tap()];

  // Wait for the context menu to disappear.
  [self waitForContextMenuToDisappear];

  // Expect 1 tab remaining.
  [ChromeEarlGrey waitForMainTabCount:1];

  // Verify the remaining tab is Page two.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:TabWithTitle(kTitle2)];
}

// Tests that "Close Other Tabs" is not available in the Overflow Menu when
// there is only one tab.
- (void)testCloseOtherTabsUnavailableInOverflowMenu {
  [ChromeEarlGrey waitForMainTabCount:1];
  [ChromeEarlGreyUI openTabGrid];

  // Open Overflow Menu.
  OpenTabGridOverflowMenu();

  // Verify the menu is open by checking for "Close All Tabs".
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuCloseAllButton()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Verify "Close Other Tabs" is NOT present.
  [[EarlGrey selectElementWithMatcher:CloseOtherTabsButton()]
      assertWithMatcher:grey_nil()];
}

// Tests that "Close Other Tabs" is not available in the Context Menu when there
// is only one tab.
- (void)testCloseOtherTabsUnavailableInContextMenu {
  [ChromeEarlGrey waitForMainTabCount:1];
  [ChromeEarlGreyUI openTabGrid];

  // Open Context Menu.
  [[EarlGrey selectElementWithMatcher:TabGridCellAtIndex(0)]
      performAction:grey_longPress()];

  // Verify the menu is open by checking for "Select Tabs" (which should be
  // present).
  [[EarlGrey selectElementWithMatcher:SelectTabsContextMenuItem()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Verify "Close Other Tabs" is NOT present.
  [[EarlGrey selectElementWithMatcher:CloseOtherTabsButton()]
      assertWithMatcher:grey_nil()];
}

// Tests simulating a swipe with Voice Over from the tab groups page, making
// sure that the new tab button is working as expected.
- (void)testSwipeUsingVoiceOver {
  [ChromeEarlGreyUI openTabGrid];

  // Switch over to the tab groups page.
  [[EarlGrey selectElementWithMatcher:TabGridTabGroupsPanelButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridNewTabButton()]
      assertWithMatcher:grey_nil()];

  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                          kTabGridScrollViewIdentifier)]
      performAction:chrome_test_util::AccessibilitySwipeRight()];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridNewTabButton()]
      performAction:grey_tap()];
}

// Tests that dragging and dropping cell1 onto cell2 creates a group with the
// title of cell2.
- (void)testDragAndDropCreatesGroup {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGreyUI openTabGrid];

  GREYAssert(chrome_test_util::LongPressCellAndDragToOffsetOf(
                 IdentifierForCellAtIndex(1), 0, IdentifierForCellAtIndex(0), 0,
                 CGVectorMake(0.5, 0.5)),
             @"Failed to DND cell into another cell in the same window");

  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle1)]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      assertWithMatcher:grey_nil()];

  chrome_test_util::OpenTabGroupAtIndex(0);

  // Check that both tabs are in the group.
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      assertWithMatcher:grey_notNil()];
}

// Tests that the incognito buttons are correctly displayed (regression for
// crbug.com/359698935).
- (void)testIncognitoButtons {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuButton()]
      assertWithMatcher:grey_interactable()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

#pragma mark - Tab Groups

- (void)testTabGroupsDoneButtonAndRegularTabs {
  // Load 1 regular tab.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGrey waitForMainTabCount:1];
  [ChromeEarlGrey waitForIncognitoTabCount:0];

  // Go to regular and open the previously created tab.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridOpenTabsPanelButton()]
      performAction:grey_tap()];
  [self verifyVisibleTabsCount:1];
  [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle1, 0)]
      performAction:grey_tap()];

  // Go to the Tab Groups panel and tap the Done button.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridTabGroupsPanelButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];

  // Ensure that we opened a regular tab.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:RegularTabGrid()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Close the only regular tab.
  OpenTabGridOverflowMenu();
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuCloseAllButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabelId(
                     IDS_IOS_CONTENT_CONTEXT_CLOSEALLTABSANDGROUPS)]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForMainTabCount:0];
  [ChromeEarlGrey waitForIncognitoTabCount:0];

  // Go to the Tab Groups panel.
  [[EarlGrey selectElementWithMatcher:TabGridTabGroupsPanelButton()]
      performAction:grey_tap()];
  // Ensure the Done button is disabled (or enabled in Chrome Next IA).
  if ([ChromeEarlGrey isChromeNextEnabled] && ![ChromeEarlGrey isIPadIdiom]) {
    [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
        assertWithMatcher:grey_enabled()];
  } else {
    [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
        assertWithMatcher:grey_accessibilityTrait(
                              UIAccessibilityTraitNotEnabled)];
  }
}

#pragma mark - Tab Grid Item Context Menu

// Tests the Share action on a tab grid item's context menu.
- (void)testTabGridItemContextMenuShare {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGreyUI openTabGrid];

  [self longPressTabWithTitle:kTitle1];

  [ChromeEarlGrey verifyShareActionWithURL:_URL1 pageTitle:kTitle1];
}

// Tests the Add to Reading list action on a tab grid item's context menu.
- (void)testTabGridItemContextMenuAddToReadingList {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGreyUI openTabGrid];

  [self longPressTabWithTitle:kTitle1];

  [self waitForSnackBarMessage:IDS_IOS_READING_LIST_SNACKBAR_MESSAGE
      triggeredByTappingItemWithMatcher:AddToReadingListButton()];
}

// Tests the Add to Bookmarks action on a tab grid item's context menu.
- (void)testTabGridItemContextMenuAddToBookmarks {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [BookmarkEarlGrey waitForBookmarkModelLoaded];
  [ChromeEarlGreyUI openTabGrid];

  [self longPressTabWithTitle:kTitle1];

  NSString* snackbarMessage = base::SysUTF16ToNSString(
      l10n_util::GetPluralStringFUTF16(IDS_IOS_BOOKMARKS_BULK_SAVED, 1));
  [self waitForSnackBarMessageText:snackbarMessage
      triggeredByTappingItemWithMatcher:AddToBookmarksButton()];

  // The snackbar disappearance might have been faster than the context menu
  // disappearance. Wait for it to disappear.
  [self waitForContextMenuToDisappear];

  [self longPressTabWithTitle:kTitle1];

  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
                     IDS_IOS_TOOLS_MENU_EDIT_BOOKMARK)]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::NavigationBarTitleWithAccessibilityLabelId(
                     IDS_IOS_BOOKMARK_EDIT_SCREEN_TITLE)]
      assertWithMatcher:grey_notNil()];

  [BookmarkEarlGrey
      verifyExistenceOfBookmarkWithURL:base::SysUTF8ToNSString(_URL1.spec())
                                  name:kTitle1
                             inStorage:BookmarkStorageType::kLocalOrSyncable];
}

// Tests that Add to Bookmarks action is greyed out when editBookmarksEnabled
// pref is set to false.
- (void)testTabGridItemContextMenuAddToBookmarkGreyed {
  [ChromeEarlGrey setBoolValue:NO
                   forUserPref:bookmarks::prefs::kEditBookmarksEnabled];

  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGreyUI openTabGrid];

  [self longPressTabWithTitle:kTitle1];
  [[EarlGrey selectElementWithMatcher:AddToBookmarksButton()]
      assertWithMatcher:grey_allOf(grey_notNil(),
                                   grey_accessibilityTrait(
                                       UIAccessibilityTraitNotEnabled),
                                   nil)];
  [ChromeEarlGrey setBoolValue:YES
                   forUserPref:bookmarks::prefs::kEditBookmarksEnabled];
}

// Tests the Share action on a tab grid item's context menu.
- (void)testTabGridItemContextCloseTab {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGreyUI openTabGrid];

  [self longPressTabWithTitle:kTitle1];

  // Close Tab.
  [[EarlGrey selectElementWithMatcher:CloseTabMenuButton()]
      performAction:grey_tap()];

  // Make sure that the tab is no longer present.
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle1)]
      assertWithMatcher:grey_nil()];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridRegularTabsEmptyStateView()]
      assertWithMatcher:grey_notNil()];
}

- (void)testTabGridItemContextSelectTabs {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGreyUI openTabGrid];

  [self longPressTabWithTitle:kTitle1];

  [[EarlGrey selectElementWithMatcher:SelectTabsContextMenuItem()]
      performAction:grey_tap()];

  // Wait for the select all button to appear to confirm that edit mode was
  // entered.
  ConditionBlock condition = ^{
    NSError* error = nil;
    [[EarlGrey
        selectElementWithMatcher:chrome_test_util::TabGridEditSelectAllButton()]
        assertWithMatcher:grey_sufficientlyVisible()
                    error:&error];
    if (error == nil) {
      // Bypass egtest bug on iOS 15 in which the grid cell element briefly
      // unselectable.
      [[EarlGrey
          selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
          assertWithMatcher:grey_sufficientlyVisible()
                      error:&error];
    }
    return (error == nil);
  };
  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
                 kWaitForUIElementTimeout, condition),
             @"Wait for select all button to appear in tab grid mode.");

  // Confirm that the tab is selectable.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  NSString* tabSelected = base::SysUTF16ToNSString(
      l10n_util::GetPluralStringFUTF16(IDS_IOS_TAB_GRID_SELECTED_TABS_TITLE,
                                       /*number=*/1));
  [[EarlGrey selectElementWithMatcher:grey_text(tabSelected)]
      assertWithMatcher:grey_sufficientlyVisible()];
}

#pragma mark - Drag and drop in Multiwindow

// Tests that dragging a tab grid item to the edge opens a new window and that
// the tab is properly transferred, including navigation stack.
- (void)testDragAndDropAtEdgeToCreateNewWindow {
  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
    EARL_GREY_TEST_SKIPPED(@"Multiple windows can't be opened.");
  }

  // TODO(crbug.com/40752508): Test is failing on iPad devices and simulator.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"This test is failing.");
  }

  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:0];

  [ChromeEarlGreyUI openTabGrid];

  GREYWaitForAppToIdle(@"App failed to idle");

  GREYAssert(LongPressCellAndDragToEdge(IdentifierForCellAtIndex(0),
                                        kGREYContentEdgeRight, 0),
             @"Failed to DND cell");

  GREYWaitForAppToIdle(@"App failed to idle");

  // Assert two windows and the expected tabs in each.
  [ChromeEarlGrey waitForForegroundWindowCount:2];
  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:0];
  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2
                             inWindowWithNumber:0];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:1];

  // Navigate back on second window to check the navigation stack is intact.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1
                             inWindowWithNumber:1];

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

// Tests that dragging a tab grid incognito item to the edge opens a new window
// and that the tab is properly transferred, incuding navigation stack.
- (void)testIncognitoDragAndDropAtEdgeToCreateNewWindow {
  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
    EARL_GREY_TEST_SKIPPED(@"Multiple windows can't be opened.");
  }
  if (@available(iOS 26.0, *)) {
    // TODO(crbug.com/427699033): Re-enable test on iOS 26.
    // Drag creates new window, but test fails to interact with it.
    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26.");
  }

  [ChromeEarlGrey closeAllNormalTabs];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];

  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey waitForIncognitoTabCount:2 inWindowWithNumber:0];

  [ChromeEarlGreyUI openTabGrid];

  GREYWaitForAppToIdle(@"App failed to idle");

  GREYAssert(LongPressCellAndDragToEdge(IdentifierForCellAtIndex(0),
                                        kGREYContentEdgeRight, 0),
             @"Failed to DND cell");

  GREYWaitForAppToIdle(@"App failed to idle");

  // Assert two windows and the expected tabs in each.
  [ChromeEarlGrey waitForForegroundWindowCount:2];
  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2
                             inWindowWithNumber:0];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:1];

  // Navigate back on second window to check the navigation stack is intact.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1
                             inWindowWithNumber:1];

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

// Tests dragging all tab grid items to another window.
// TODO(crbug.com/431223435): Re-enable test.
- (void)DISABLED_testDragAndDropAllItemsToOtherWindow {
  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
    EARL_GREY_TEST_SKIPPED(@"Multiple windows can't be opened.");
  }

  // Setup first window with tab 1 and 2.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:0];

  // Open second window.
  [ChromeEarlGrey openNewWindow];
  [ChromeEarlGrey waitUntilReadyWindowWithNumber:1];
  [ChromeEarlGrey waitForForegroundWindowCount:2];

  // Setup second window with tabs 3 and 4.
  [ChromeEarlGrey loadURL:_URL3 inWindowWithNumber:1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:1];

  [ChromeEarlGrey openNewTabInWindowWithNumber:1];
  [ChromeEarlGrey loadURL:_URL4 inWindowWithNumber:1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse4
                             inWindowWithNumber:1];

  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:1];

  // Open tab grid in both windows.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [ChromeEarlGreyUI openTabGrid];
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
  [ChromeEarlGreyUI openTabGrid];

  GREYWaitForAppToIdle(@"App failed to idle");

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  OpenTabGridOverflowMenu();
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuSelectTabsButton()]
      performAction:grey_tap()];
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridEditSelectAllButton()]
      performAction:grey_tap()];

  // For unknown reasons the drag below sometimes fails to recognize the second
  // window as a drop point. Triggering a background / foreground seems to
  // reduce this flake.
  [[AppLaunchManager sharedManager] backgroundAndForegroundApp];

  // DnD first tab of left window to left edge of first tab in second window.
  // Note: move to left half of the destination tile, to avoid unwanted
  // scrolling that would happen closer to the left edge.
  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 0,
                                            IdentifierForCellAtIndex(0), 1,
                                            CGVectorMake(0.5, 0.5)),
             @"Failed to DND cell on cell");

  GREYWaitForAppToIdle(@"App failed to idle");

  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
  [ChromeEarlGrey waitForMainTabCount:4 inWindowWithNumber:1];

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

// Tests dragging tab grid item as URL between windows.
// TODO(crbug.com/40864920): Re-enable this test.
- (void)DISABLED_testDragAndDropURLBetweenWindows {
  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
    EARL_GREY_TEST_SKIPPED(@"Multiple windows can't be opened.");
  }

  // Setup first window with tabs 1 and 2.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:0];

  // Open second window.
  [ChromeEarlGrey openNewWindow];
  [ChromeEarlGrey waitUntilReadyWindowWithNumber:1];
  [ChromeEarlGrey waitForForegroundWindowCount:2];

  // Setup second window with tab 3.
  [ChromeEarlGrey loadURL:_URL3 inWindowWithNumber:1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:1];

  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];

  // Open tab grid in first window.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [ChromeEarlGreyUI openTabGrid];

  GREYWaitForAppToIdle(@"App failed to idle");

  // DnD first tab of left window to second window.
  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 0, nil,
                                            1, CGVectorMake(0.5, 0.5)),
             @"Failed to DND cell on window");

  GREYWaitForAppToIdle(@"App failed to idle");

  // Tabs should not have changed.
  [ChromeEarlGrey waitForMainTabCount:2 inWindowWithNumber:0];
  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];

  // Second window should show URL1
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1
                             inWindowWithNumber:1];

  // Navigate back to check the navigation stack is intact.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:1];

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

// Tests dragging tab grid incognito item as URL to a main windows.
- (void)testDragAndDropIncognitoURLInMainWindow {
  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
    EARL_GREY_TEST_SKIPPED(@"Multiple windows can't be opened.");
  }

  // TODO(crbug.com/40868899): Test is failing on iPad devices and simulator.
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_DISABLED(@"Test disabled on iPad.");
  }

  // Setup first window with one incognito tab 1.
  [ChromeEarlGrey closeAllNormalTabs];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];

  // Open second window.
  [ChromeEarlGrey openNewWindow];
  [ChromeEarlGrey waitUntilReadyWindowWithNumber:1];
  [ChromeEarlGrey waitForForegroundWindowCount:2];

  // Setup second window with tab 3.
  [ChromeEarlGrey loadURL:_URL3 inWindowWithNumber:1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:1];

  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];

  // Open incognito tab grid in first window.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridIncognitoTabsPanelButton()]
      performAction:grey_tap()];

  GREYWaitForAppToIdle(@"App failed to idle");

  // DnD first tab of left window to second window.
  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 0, nil,
                                            1, CGVectorMake(0.5, 0.5)),
             @"Failed to DND cell on window");

  GREYWaitForAppToIdle(@"App failed to idle");

  // Tabs should not have changed.
  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];

  // Second window should show URL1
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1
                             inWindowWithNumber:1];

  // Navigate back to check the navigation stack is intact.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:1];

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

// Tests dragging tab grid main item as URL to an incognito windows.
// TODO(crbug.com/40240640): Re-enable this test.
- (void)DISABLED_testDragAndDropMainURLInIncognitoWindow {
  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
    EARL_GREY_TEST_SKIPPED(@"Multiple windows can't be opened.");
  }

  // Setup first window with one incognito tab 1.
  [ChromeEarlGrey closeAllNormalTabs];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];

  // Open second window.
  [ChromeEarlGrey openNewWindow];
  [ChromeEarlGrey waitUntilReadyWindowWithNumber:1];
  [ChromeEarlGrey waitForForegroundWindowCount:2];

  // Setup second window with tab 3.
  [ChromeEarlGrey loadURL:_URL3 inWindowWithNumber:1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:1];

  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];

  // Open incognito tab grid in first window.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
  [ChromeEarlGreyUI openTabGrid];

  GREYWaitForAppToIdle(@"App failed to idle");

  // DnD first tab of second window to first window.
  GREYAssert(LongPressCellAndDragToOffsetOf(IdentifierForCellAtIndex(0), 1, nil,
                                            0, CGVectorMake(0.5, 0.5)),
             @"Failed to DND cell on window");

  GREYWaitForAppToIdle(@"App failed to idle");

  // Tabs should not have changed.
  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
  [ChromeEarlGrey waitForIncognitoTabCount:1 inWindowWithNumber:0];
  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:1];

  // First window should show URL3
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3
                             inWindowWithNumber:0];

  // Navigate back to check the navigation stack is intact.
  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(0)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::BackButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1
                             inWindowWithNumber:0];

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];
}

#pragma mark - Bulk Actions

// Tests closing a tab in the tab grid edit mode and that edit mode is exited
// after closing all tabs.
- (void)testTabGridBulkActionCloseTabs {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGreyUI openTabGrid];

  OpenTabGridOverflowMenu();
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuSelectTabsButton()]
      performAction:grey_tap()];

  // Tap tab to select.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridEditCloseTabsButton()]
      performAction:grey_tap()];

  NSString* closeTabsButtonText =
      base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
          IDS_IOS_TAB_GRID_CLOSE_ALL_TABS_CONFIRMATION,
          /*number=*/1));

  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabel(
                     closeTabsButtonText)] performAction:grey_tap()];

  // Make sure that the tab is no longer present.
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle1)]
      assertWithMatcher:grey_nil()];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridRegularTabsEmptyStateView()]
      assertWithMatcher:grey_notNil()];

  // Verify edit mode is exited.
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuButton()]
      assertWithMatcher:grey_notNil()];
}

// Tests selecting all items in the tab grid edit mode using the "Select all"
// button.
- (void)testTabGridBulkActionSelectAll {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];

  [ChromeEarlGreyUI openTabGrid];

  OpenTabGridOverflowMenu();
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuSelectTabsButton()]
      performAction:grey_tap()];

  // Tap "Select all" and close selected tabs.
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridEditSelectAllButton()]
      performAction:grey_tap()];
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridEditCloseTabsButton()]
      performAction:grey_tap()];
  NSString* closeTabsButtonText =
      base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
          IDS_IOS_TAB_GRID_CLOSE_ALL_TABS_CONFIRMATION,
          /*number=*/3));
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabel(
                     closeTabsButtonText)] performAction:grey_tap()];

  // Make sure that the tab grid is empty.
  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];

  // Verify edit mode is exited.
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuButton()]
      assertWithMatcher:grey_notNil()];
}

// Tests deselecting all items in the tab grid edit mode using the "Deselect
// all" button.
- (void)testTabGridBulkActionDeselectAll {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];

  [ChromeEarlGreyUI openTabGrid];

  OpenTabGridOverflowMenu();
  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuSelectTabsButton()]
      performAction:grey_tap()];

  // Ensure button label is "Select All" and select all items.
  [[EarlGrey selectElementWithMatcher:SelectAllButton()]
      performAction:grey_tap()];

  // Deselect all button should be visible when all items are selected.
  // Tapping deselect all button should deselect all items.
  [[EarlGrey selectElementWithMatcher:DeselectAllButton()]
      performAction:grey_tap()];

  // Verify deselection by manually tapping each item (to re-select) and closing
  // the selected items.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(1)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(2)]
      performAction:grey_tap()];

  // All tabs should have been re-selected and closing selected tabs should
  // empty the tab grid.
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridEditCloseTabsButton()]
      performAction:grey_tap()];
  NSString* closeTabsButtonText =
      base::SysUTF16ToNSString(l10n_util::GetPluralStringFUTF16(
          IDS_IOS_TAB_GRID_CLOSE_ALL_TABS_CONFIRMATION,
          /*number=*/3));
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ActionSheetItemWithAccessibilityLabel(
                     closeTabsButtonText)] performAction:grey_tap()];

  // Make sure that the tab grid is empty.
  [ChromeEarlGrey waitForMainTabCount:0 inWindowWithNumber:0];
}

// Tests adding items to Bookmarks from the tab grid edit mode.
- (void)testTabGridBulkActionAddToBookmarks {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL4];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse4];

  [BookmarkEarlGrey waitForBookmarkModelLoaded];
  [ChromeEarlGreyUI openTabGrid];

  OpenTabGridOverflowMenu();

  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuSelectTabsButton()]
      performAction:grey_tap()];

  // Select the first and last items.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(2)]
      performAction:grey_tap()];

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridEditAddToButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:AddToBookmarksButton()]
      performAction:grey_tap()];

  [[EarlGrey
      selectElementWithMatcher:grey_accessibilityLabel(l10n_util::GetNSString(
                                   IDS_IOS_BOOKMARK_CHOOSE_GROUP_BUTTON))]
      assertWithMatcher:grey_notNil()];

  // Choose "Mobile Bookmarks" folder as the destination.
  // Duplicate matcher here instead of using +[BookmarkEarlGreyUI
  // openMobileBookmarks] in order to properly wait for the snackbar message.
  std::u16string pattern =
      l10n_util::GetStringUTF16(IDS_IOS_BOOKMARK_PAGE_SAVED_FOLDER);
  NSString* snackbarMessage = base::SysUTF16ToNSString(
      base::i18n::MessageFormatter::FormatWithNamedArgs(
          pattern, "count", 2, "title",
          base::SysNSStringToUTF16(@"Mobile bookmarks")));
  [self waitForSnackBarMessageText:snackbarMessage
      triggeredByTappingItemWithMatcher:grey_allOf(grey_kindOfClassName(
                                                       @"UITableViewCell"),
                                                   grey_descendant(grey_text(
                                                       @"Mobile bookmarks")),
                                                   nil)];

  [BookmarkEarlGrey
      verifyExistenceOfBookmarkWithURL:base::SysUTF8ToNSString(_URL1.spec())
                                  name:kTitle1
                             inStorage:BookmarkStorageType::kLocalOrSyncable];
  [BookmarkEarlGrey
      verifyExistenceOfBookmarkWithURL:base::SysUTF8ToNSString(_URL4.spec())
                                  name:kTitle4
                             inStorage:BookmarkStorageType::kLocalOrSyncable];
  [BookmarkEarlGrey
      verifyAbsenceOfBookmarkWithURL:base::SysUTF8ToNSString(_URL2.spec())
                           inStorage:BookmarkStorageType::kLocalOrSyncable];
}

// Tests adding items to the Reading List from the tab grid edit mode.
// TODO(crbug.com/40900596): Test flakes.
- (void)FLAKY_testTabGridBulkActionAddToReadingList {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];

  [ChromeEarlGreyUI openTabGrid];

  OpenTabGridOverflowMenu();

  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuSelectTabsButton()]
      performAction:grey_tap()];

  // Select the first and last items.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(2)]
      performAction:grey_tap()];

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridEditAddToButton()]
      performAction:grey_tap()];

  [self waitForSnackBarMessage:IDS_IOS_READING_LIST_SNACKBAR_MESSAGE
      triggeredByTappingItemWithMatcher:AddToReadingListButton()];
}

// Tests sharing multiple tabs from the tab grid edit mode.
- (void)testTabGridBulkActionShare {
  // TODO(crbug.com/40193498): The pasteboard is "not available at this time"
  // when running on device.

#if !TARGET_OS_SIMULATOR
  EARL_GREY_TEST_SKIPPED(
      @"The pasteboard is inaccessible when running on device.");
#endif

  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];

  [ChromeEarlGreyUI openTabGrid];

  OpenTabGridOverflowMenu();

  [[EarlGrey selectElementWithMatcher:TabGridOverflowMenuSelectTabsButton()]
      performAction:grey_tap()];

  // Select the first and last items.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(2)]
      performAction:grey_tap()];

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridEditShareButton()]
      performAction:grey_tap()];

  [ChromeEarlGrey tapButtonInActivitySheetWithID:@"Copy"];

  NSString* URL1String = base::SysUTF8ToNSString(_URL1.spec());
  NSString* URL3String = base::SysUTF8ToNSString(_URL3.spec());

  [ChromeEarlGrey verifyStringCopied:URL1String];
  [ChromeEarlGrey verifyStringCopied:URL3String];
}

#pragma mark - Tab Grid Search

// Tests entering and exit of the tab grid search mode.
- (void)testEnterExitSearch {
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Verify that search mode is active.
  [[EarlGrey selectElementWithMatcher:TabGridSearchModeToolbar()]
      assertWithMatcher:grey_notNil()];

  // Exit search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
      performAction:grey_tap()];

  // Verify that normal mode is active.
  [[EarlGrey selectElementWithMatcher:TabGridNormalModePageControl()]
      assertWithMatcher:grey_notNil()];
}

// Tests that exiting search mode reset the tabs count to the original number.
- (void)testTabGridResetAfterExitingSearch {
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode & search with a query that produce no results.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"hello")];

  // Verify that search reduced the number of visible tabs.
  [self verifyVisibleTabsCount:0];

  // Exit search mode & verify that tabs grid was reset.
  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
      performAction:grey_tap()];
  [self verifyVisibleTabsCount:2];
}

// Tests that the scrim view is always shown when the search bar is empty in the
// search mode.
- (void)testScrimVisibleInSearchModeWhenSearchBarIsEmpty {
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGreyUI openTabGrid];

  // Make sure the tab grid is on the regular tabs panel.
  [[EarlGrey selectElementWithMatcher:TabGridNormalModePageControl()]
      performAction:grey_tap()];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Upon entry, the search bar is empty. Verify that scrim is visible.
  [[EarlGrey selectElementWithMatcher:VisibleSearchScrim()]
      assertWithMatcher:grey_notNil()];

  // Searching with any query should render scrim invisible.
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"text")];
  [[EarlGrey selectElementWithMatcher:VisibleSearchScrim()]
      assertWithMatcher:grey_nil()];

  // Clearing search bar text should render scrim visible again.
  // TODO(crbug.com/40916973): Revert to grey_clearText when fixed in EG.
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"")];
  [[EarlGrey selectElementWithMatcher:VisibleSearchScrim()]
      assertWithMatcher:grey_notNil()];

  // Cancel search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
      performAction:grey_tap()];

  // Verify that scrim is not visible anymore.
  [[EarlGrey selectElementWithMatcher:VisibleSearchScrim()]
      assertWithMatcher:grey_nil()];
}

// Tests that tapping on the scrim view while in search mode dismisses the scrim
// and exits search mode.
- (void)testTapOnSearchScrimExitsSearchMode {
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Tap on scrim.
  [[EarlGrey selectElementWithMatcher:VisibleSearchScrim()]
      performAction:grey_tap()];

  // Verify that search mode is exit, scrim not visible, and transition to
  // normal mode was successful.
  [[EarlGrey selectElementWithMatcher:VisibleSearchScrim()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:TabGridNormalModePageControl()]
      assertWithMatcher:grey_notNil()];
  [self verifyVisibleTabsCount:2];
}

// Tests that searching in open tabs in the regular mode will filter the tabs
// correctly.
// TODO(crbug.com/332714545): Test is flaky.
- (void)FLAKY_testSearchRegularOpenTabs {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  [self verifyVisibleTabsCount:4];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"Page")];

  // Verify that the header of the open tabs section has the correct results
  // count.
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(3)]
      assertWithMatcher:grey_notNil()];

  // Verify that there are 3 results for the query "Page" and they are in the
  // expected order.
  [self verifyVisibleTabsCount:3];
  [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle1, 0)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle2, 1)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle4, 2)]
      assertWithMatcher:grey_notNil()];

  // Update the search query with one that doesn't match any results.
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"Foo")];

  // Verify that the header of the open tabs section has 0 as the results count.
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(0)]
      assertWithMatcher:grey_notNil()];

  // Verify that no tabs are visible and previously shown tabs disappeared.
  [self verifyVisibleTabsCount:0];

  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle1)]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle4)]
      assertWithMatcher:grey_nil()];
}

// Tests that open tabs search results header appear only when there is a query
// on the search bar.
- (void)testOpenTabsHeaderVisibleInSearchModeWhenSearchBarIsNotEmpty {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Verify that the header doesn't exist in normal mode.
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
      assertWithMatcher:grey_nil()];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Upon entry, the search bar is empty. Verify that the header doesn't exist.
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
      assertWithMatcher:grey_nil()];

  // Searching with any query should render the header visible.
  PerformTabGridSearch(@"text");

  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
      assertWithMatcher:grey_notNil()];

  // Clearing search bar text should render the header invisible again.
  // TODO(crbug.com/40916973): Revert to grey_clearText when fixed in EG.
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"")];
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
      assertWithMatcher:grey_nil()];

  // Searching a word then canceling the search mode should hide the section
  // header.
  PerformTabGridSearch(@"page");

  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
      performAction:grey_tap()];
  [[self scrollUpViewMatcher:RegularTabGrid()
             toSelectMatcher:SearchOpenTabsSectionHeader()]
      assertWithMatcher:grey_nil()];
}

// Tests that suggested actions section is available whenever there is a query
// in the normal tabs search mode.
- (void)testSuggestedActionsVisibleInSearchModeWhenSearchBarIsNotEmpty {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Verify that the suggested actions section doesn't exist in normal mode.
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_nil()];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Upon entry, the search bar is empty. Verify that the suggested actions
  // section doesn't exist.
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_nil()];

  // Searching with a query with no results should show the suggested actions
  // section.
  PerformTabGridSearch(@"text");

  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(0)]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_notNil()];

  // Clearing search bar text should hide the suggested actions section.
  // TODO(crbug.com/40916973): Revert to grey_clearText when fixed in EG.
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"")];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_nil()];

  // Searching with a query with results should show the suggested actions
  // section.
  // TODO(crbug.com/40916973): Revert to grey_clearText when fixed in EG.
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"")];
  PerformTabGridSearch(kTitle2);

  // Check that the header is set correctly.
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(1)]
      assertWithMatcher:grey_notNil()];

  [[self scrollDownViewMatcher:RegularTabGrid()
               toSelectMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_notNil()];
  [[self scrollDownViewMatcher:RegularTabGrid()
               toSelectMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_notNil()];

  // Canceling search mode should hide the suggested actions section.
  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_nil()];
}

// Tests that suggested actions section does not appear in search mode for
// incognito page.
- (void)testSuggestedActionsNotAvailableInIncognitoPageSearchMode {
  [self loadTestURLsInNewIncognitoTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Upon entry, the search bar is empty. Verify that the suggested actions
  // section doesn't exist.
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_nil()];

  // Searching with a query should not show suggested actions section.
  PerformTabGridSearch(kTitle2);

  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_nil()];
  [[self scrollDownViewMatcher:chrome_test_util::IncognitoTabGrid()
               toSelectMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_nil()];
}

// Tests that the search suggested actions section has the right rows in the
// regular grid.
- (void)testSearchSuggestedActionsSectionContentInRegularGrid {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode and enter a search query.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];
  // TODO(crbug.com/40827691): Scrolling doesn't work properly in very small
  // devices. Once that is fixed a more broad query can be used for searching
  // (eg. "page").
  PerformTabGridSearch(kTitle2);

  // Verify that the suggested actions section exist and has "Search on web" and
  // "Search history" rows.
  [[self scrollDownViewMatcher:RegularTabGrid()
               toSelectMatcher:SearchSuggestedActionsSectionHeader()]
      assertWithMatcher:grey_notNil()];

  [[self
      scrollDownViewMatcher:RegularTabGrid()
            toSelectMatcher:grey_allOf(
                                SearchSuggestedActionsSection(),
                                grey_descendant(SearchOnWebSuggestedAction()),
                                grey_descendant(SearchHistorySuggestedAction()),
                                grey_sufficientlyVisible(), nil)]
      assertWithMatcher:grey_notNil()];
}

// Tests that history row in the search suggested actions section displays the
// correct number of matches.
- (void)testSearchSuggestedActionsDisplaysCorrectHistoryMatchesCount {
  [ChromeEarlGrey clearBrowsingHistory];
  [self loadTestURLs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Verify that the suggested actions section is not visible.
  [[EarlGrey selectElementWithMatcher:SearchSuggestedActionsSection()]
      assertWithMatcher:grey_nil()];

  // Searching the word "page" matches 2 items from history.
  PerformTabGridSearch(@"page");

  [[self scrollDownViewMatcher:RegularTabGrid()
               toSelectMatcher:
                   SearchSuggestedActionsSectionWithHistoryMatchesCount(2)]
      assertWithMatcher:grey_notNil()];

  // Adding to the existing query " two" will search for "page two" and should
  // only match 1 item from the history.
  PerformTabGridSearch(@"page two");

  [[self scrollDownViewMatcher:RegularTabGrid()
               toSelectMatcher:
                   SearchSuggestedActionsSectionWithHistoryMatchesCount(1)]
      assertWithMatcher:grey_notNil()];

  // Cancel search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
      performAction:grey_tap()];
}

// Tests that selecting an open tab search result in the regular mode will
// correctly open the expected tab.
- (void)testSearchRegularOpenTabsSelectResult {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(kTitle2)];

  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
      assertWithMatcher:grey_sufficientlyVisible()];

  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];
  const GURL currentURL = [ChromeEarlGrey webStateVisibleURL];
  GREYAssertEqual(_URL2, currentURL, @"Page navigated unexpectedly to %s",
                  currentURL.spec().c_str());
}

// Tests that selecting an open tab search result in incognito mode will
// correctly open the expected tab.
- (void)testSearchIncognitoOpenTabsSelectResult {
  [self loadTestURLsInNewIncognitoTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(kTitle2)];

  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
      assertWithMatcher:grey_sufficientlyVisible()];

  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];
  const GURL currentURL = [ChromeEarlGrey webStateVisibleURL];
  GREYAssertEqual(_URL2, currentURL, @"Page navigated unexpectedly to %s",
                  currentURL.spec().c_str());
}

// Tests that share action works successfully from the long press context menu
// on search results.
- (void)testSearchOpenTabsContextMenuShare {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(kTitle2)];

  [self longPressTabWithTitle:kTitle2];

  [ChromeEarlGrey verifyShareActionWithURL:_URL1 pageTitle:kTitle2];
}

// Tests that add to reading list action works successfully from the long press
// context menu on search results.
- (void)testSearchOpenTabsContextMenuAddToReadingList {
  // Clear the Reading List.
  GREYAssertNil([ReadingListAppInterface clearEntries],
                @"Unable to clear Reading List entries");
  GREYAssertEqual(0, [ReadingListAppInterface unreadEntriesCount],
                  @"Reading List should be empty");

  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(kTitle2)];

  [self longPressTabWithTitle:kTitle2];

  [[EarlGrey selectElementWithMatcher:AddToReadingListButton()]
      performAction:grey_tap()];

  // Check that the tab was added to Reading List.
  GREYAssertEqual(1, [ReadingListAppInterface unreadEntriesCount],
                  @"Reading List should have one element");
}

// Tests that add to bookmarks action works successfully from the long press
// context menu on search results.
- (void)testSearchOpenTabsContextMenuAddToBookmarks {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(kTitle2)];

  [self longPressTabWithTitle:kTitle2];

  NSString* snackbarMessage = base::SysUTF16ToNSString(
      l10n_util::GetPluralStringFUTF16(IDS_IOS_BOOKMARKS_BULK_SAVED, 1));
  [self waitForSnackBarMessageText:snackbarMessage
      triggeredByTappingItemWithMatcher:AddToBookmarksButton()];

  [self longPressTabWithTitle:kTitle2];

  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
                     IDS_IOS_TOOLS_MENU_EDIT_BOOKMARK)]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::NavigationBarTitleWithAccessibilityLabelId(
                     IDS_IOS_BOOKMARK_EDIT_SCREEN_TITLE)]
      assertWithMatcher:grey_notNil()];
}

// Tests that close tab action works successfully from the long press context
// menu on search results.
- (void)testSearchOpenTabsContextMenuCloseTab {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(kTitle2)];

  [self longPressTabWithTitle:kTitle2];

  // Close Tab.
  [[EarlGrey selectElementWithMatcher:CloseTabMenuButton()]
      performAction:grey_tap()];

  // Make sure that the tab is no longer present.
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      assertWithMatcher:grey_nil()];
}

- (void)testSearchOpenTabsContextMenuSelectTabsUnavailable {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(kTitle2)];

  [self longPressTabWithTitle:kTitle2];

  [[EarlGrey selectElementWithMatcher:SelectTabsContextMenuItem()]
      assertWithMatcher:grey_nil()];

  // Dismiss the context menu.
  [[EarlGrey selectElementWithMatcher:RegularTabGrid()]
      performAction:grey_tapAtPoint(CGPointMake(0, 0))];
}

// Tests that tapping on search history action in the regular tabs search mode
// opens the history modal and dismissing it returns to the search mode.
- (void)testHistorySuggestedActionInRegularTabsSearch {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];
  // Enter search mode & perform a search.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  PerformTabGridSearch(kTitle2);

  // Tap on search history.
  [[self scrollDownViewMatcher:RegularTabGrid()
               toSelectMatcher:SearchHistorySuggestedAction()]
      performAction:grey_tap()];

  // Check that the history table is presented.
  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                          kHistoryTableViewIdentifier)]
      assertWithMatcher:grey_sufficientlyVisible()];
  // Close History.
  [[EarlGrey selectElementWithMatcher:
                 grey_accessibilityID(
                     kHistoryNavigationControllerDoneButtonIdentifier)]
      performAction:grey_tap()];

  // Make sure that search mode is still active and searching the same query.
  [[EarlGrey selectElementWithMatcher:TabGridSearchModeToolbar()]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:SearchBarWithSearchText(kTitle2)]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Make sure that we can change the search text and so update the query.
  PerformTabGridSearch(kTitle1);

  // Make sure that search mode is still active and searching the new query.
  [[EarlGrey selectElementWithMatcher:TabGridSearchModeToolbar()]
      assertWithMatcher:grey_notNil()];
  [[EarlGrey selectElementWithMatcher:SearchBarWithSearchText(kTitle1)]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that tapping the search on web action in the regular tabs search mode
// opens a new tab on the default search engine with the search term from tab
// search. Additionally, checks that tab search mode is exited when the user
// returns to the tab grid.
- (void)testSearchOnWebSuggestedActionInRegularTabsSearch {
  // Configure a testing search engine to prevent real external url requests.
  GURL searchEngineURL = self.testServer->GetURL(kSearchEngineURL);
  NSString* searchEngineURLString =
      base::SysUTF8ToNSString(searchEngineURL.spec());
  [SettingsAppInterface overrideSearchEngineWithURL:searchEngineURLString];

  // Enter tab grid search mode & perform a search.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  PerformTabGridSearch(@"queryfromtabsearch");

  // Scroll to search on web.
  [[self scrollDownViewMatcher:RegularTabGrid()
               toSelectMatcher:SearchOnWebSuggestedAction()]
      performAction:grey_tap()];

  // Ensure that the tab grid was exited.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Ensure the loaded page is a default search engine page for `searchQuery`.
  [ChromeEarlGrey waitForWebStateContainingText:kSearchEngineHost];
  [ChromeEarlGrey waitForWebStateContainingText:"queryfromtabsearch"];

  // Re-enter the tab grid and ensure search mode was exited.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that a search with no results in incognito mode will show the empty
// state.
- (void)testEmptyStateAfterNoResultsSearchForIncognitoTabGrid {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Search with a word that will produce no results and verify that the header
  // has 0 found results and that the empty state is visible.
  PerformTabGridSearch(@"text");

  [[EarlGrey selectElementWithMatcher:SearchOpenTabsHeaderWithValue(0)]
      assertWithMatcher:grey_notNil()];

  [[EarlGrey
      selectElementWithMatcher:grey_accessibilityID(
                                   kTabGridIncognitoTabsEmptyStateIdentifier)]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that closing a tab works successfully in search results.
- (void)testSearchResultCloseTab {
  [self loadTestURLsInNewTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  PerformTabGridSearch(kTitle2);

  [self verifyVisibleTabsCount:1];
  [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle2, 0)]
      assertWithMatcher:grey_notNil()];

  // Close Tab.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridCloseButtonForCellAtIndex(0)]
      performAction:grey_tap()];

  // Make sure that the tab is no longer present.
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      assertWithMatcher:grey_nil()];
}

// Tests that closing a tab works successfully in incognito search results.
- (void)testSearchResultCloseTabInIncognito {
  [self loadTestURLsInNewIncognitoTabs];
  [ChromeEarlGreyUI openTabGrid];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  PerformTabGridSearch(kTitle2);

  [self verifyVisibleTabsCount:1];
  [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle2, 0)]
      assertWithMatcher:grey_notNil()];

  // Close Tab.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridCloseButtonForCellAtIndex(0)]
      performAction:grey_tap()];

  // Make sure that the tab is no longer present.
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle2)]
      assertWithMatcher:grey_nil()];
}

// Tests that interacting with the Tab Grid search UI shows the correct header
// at each step.
- (void)testSearchHeaderWithInactiveTabs {
  [self loadTestURLsInNewTabs];
  [self relaunchAppWithInactiveTabsTestMode];

  [ChromeEarlGreyUI openTabGrid];
  GREYAssertEqual(1UL, [ChromeEarlGrey mainTabCount],
                  @"Expected only one tab (NTP), all other tabs should have "
                  @"been in inactive tab grid.");
  GREYAssertEqual(4UL, [ChromeEarlGrey inactiveTabCount],
                  @"Expected 4 inactive tabs.");

  // Verify that the Inactive Tabs button is showing.
  [[EarlGrey selectElementWithMatcher:TabGridInactiveTabsButton()]
      assertWithMatcher:grey_notNil()];

  // Enter search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Verify that the Inactive Tabs button is not showing.
  [[EarlGrey selectElementWithMatcher:TabGridInactiveTabsButton()]
      assertWithMatcher:grey_nil()];

  // Verify that search mode is active.
  [[EarlGrey selectElementWithMatcher:TabGridSearchModeToolbar()]
      assertWithMatcher:grey_notNil()];

  // Verify that the Search results header is not showing, as the search field
  // is empty.
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
      assertWithMatcher:grey_nil()];

  // Enter some text.
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(@"Page")];

  // Verify that the Search results header is showing now.
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
      assertWithMatcher:grey_notNil()];

  // Exit search mode.
  [[EarlGrey selectElementWithMatcher:TabGridSearchCancelButton()]
      performAction:grey_tap()];

  // Verify that normal mode is active.
  [[EarlGrey selectElementWithMatcher:TabGridNormalModePageControl()]
      assertWithMatcher:grey_notNil()];

  // Verify that the Inactive Tabs button is showing.
  [[EarlGrey selectElementWithMatcher:TabGridInactiveTabsButton()]
      assertWithMatcher:grey_notNil()];

  // Verify that the Search results header is not showing, as the search field
  // is empty.
  [[EarlGrey selectElementWithMatcher:SearchOpenTabsSectionHeader()]
      assertWithMatcher:grey_nil()];
}

// Tests that closing a tab works successfully in incognito search results.
- (void)testLastIncognitoTabCloses {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGreyUI openTabGrid];

  [self verifyVisibleTabsCount:1];
  [[EarlGrey selectElementWithMatcher:TabWithTitleAndIndex(kTitle1, 0)]
      assertWithMatcher:grey_notNil()];

  // Close Tab.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          TabGridCloseButtonForCellAtIndex(0)]
      performAction:grey_tap()];

  // Make sure that the tab is no longer present.
  [[EarlGrey selectElementWithMatcher:TabWithTitle(kTitle1)]
      assertWithMatcher:grey_nil()];
}

// Tests that the Done button in the Incognito panel is correctly
// enabled/disabled based on the presence of Incognito tabs, and correctly opens
// the Tab Grid after the Incognito browser has been recreated.
- (void)testDoneInIncognitoAfterClosingLastIncognitoTab {
  // Open an Incognito tab.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey waitForIncognitoTabCount:1];

  // Go to Incognito grid.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridIncognitoTabsPanelButton()]
      performAction:grey_tap()];
  [self verifyVisibleTabsCount:1];

  // Tab the Done button.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];

  // Go back to Incognito grid.
  [ChromeEarlGreyUI openTabGrid];

  // Close the Incognito tab. This recreates the Incognito browser and opens the
  // tab grid, as it was the last tab.
  [ChromeEarlGrey closeCurrentTab];
  [self verifyVisibleTabsCount:0];

  // Verify the Done button is disabled (or enabled in Chrome Next IA).
  if ([ChromeEarlGrey isChromeNextEnabled] && ![ChromeEarlGrey isIPadIdiom]) {
    [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
        assertWithMatcher:grey_enabled()];
  } else {
    [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
        assertWithMatcher:grey_accessibilityTrait(
                              UIAccessibilityTraitNotEnabled)];
  }

  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey waitForIncognitoTabCount:1];

  // Verify the Tab Grid's Done button is enabled and opens the tab.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridDoneButton()]
      performAction:grey_tap()];

  // Verify that the Tab Grid is closed.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Checks opening a new incognito tab after entering the tab grid in regular.
- (void)testSwitchModeFromTabGrid {
  GREYAssertNil([MetricsAppInterface setupUserActionTester],
                @"Cannot setup user action tester.");

  // Go to incognito from the TabGrid.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:TabGridIncognitoTabsPanelButton()]
      performAction:grey_tap()];

  // Open a new incognito tab.
  [[EarlGrey selectElementWithMatcher:TabGridNewIncognitoTabButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForIncognitoTabCount:1];

  GREYAssertNil(
      [MetricsAppInterface expectCount:1
                         forUserAction:@"MobileTabGridCreateIncognitoTab"],
      @"No successful new tab recorded");
  GREYAssertNil([MetricsAppInterface
                      expectCount:0
                    forUserAction:@"MobileTabGridFailedCreateIncognitoTab"],
                @"Failed new tab recorded");

  GREYAssertNil([MetricsAppInterface releaseUserActionTester],
                @"Cannot release user action tester.");
}

// Ensures that when users tap on a tab from tab search result and this tab is
// in another window currently displaying tab grid, the tab is opened.
// TODO(crbug.com/448400563): Re-enable this test.
- (void)DISABLED_testOpenSearchedTabFromAnotherWindowWhenTabGridIsVisible {
  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
    EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened.");
  }

  // Loads regular tab 1 on the first window.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGrey waitForMainTabCount:1 inWindowWithNumber:0];

  // Opens a incognito tab on the first window.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey waitForIncognitoTabCount:1];

  // Opens tabGrid in incognito mode on the first window.
  [ChromeEarlGreyUI openTabGrid];

  // Opens a second window.
  [ChromeEarlGrey openNewWindow];
  [ChromeEarlGrey waitUntilReadyWindowWithNumber:1];
  [ChromeEarlGrey waitForForegroundWindowCount:2];

  [EarlGrey setRootMatcherForSubsequentInteractions:WindowWithNumber(1)];

  // Opens tabGrid on the second window.
  [ChromeEarlGreyUI openTabGrid];

  // Enters search mode in the second window.
  [[EarlGrey selectElementWithMatcher:TabGridSearchTabsButton()]
      performAction:grey_tap()];

  // Verifies that search mode is active.
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridSearchModeToolbar()]
      assertWithMatcher:grey_notNil()];

  // Performs the search : tab 1 which is a regular tab present on the first
  // window.
  [[EarlGrey selectElementWithMatcher:TabGridSearchBar()]
      performAction:grey_replaceText(kTitle1)];

  // Taps on it from the second window.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];

  // Closes the second window.
  [ChromeEarlGrey closeWindowWithNumber:1];

  // Checks the content of the first window.
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
}

#pragma mark - Helper Methods

- (void)loadTestURLs {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];
}

- (void)loadTestURLsInNewTabs {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL4];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse4];
}

// Open and close 2 unique tabs.
- (void)loadTestURLsAndCloseTabs {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];
  [ChromeEarlGrey closeCurrentTab];
  [ChromeEarlGrey closeCurrentTab];
}

- (void)loadTestURLsInNewIncognitoTabs {
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse1];

  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse2];

  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse3];

  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL4];
  [ChromeEarlGrey waitForWebStateContainingText:kResponse4];
}

// Long press on the tab item in the tab grid with `title`.
- (void)longPressTabWithTitle:(NSString*)title {
  // The test page may be there multiple times.
  // Don't use -waitForUIElementToAppearWithMatcher here as it doesn't support
  // the atIndex:0 check for multiple elements.
  ConditionBlock condition = ^{
    NSError* error = nil;
    [[[EarlGrey
        selectElementWithMatcher:grey_allOf(grey_accessibilityLabel(title),
                                            grey_sufficientlyVisible(), nil)]
        atIndex:0] assertWithMatcher:grey_notNil() error:&error];
    return error == nil;
  };
  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
                 kWaitForUIElementTimeout, condition),
             @"Tab did not appear.");

  [[[EarlGrey
      selectElementWithMatcher:grey_allOf(grey_accessibilityLabel(title),
                                          grey_sufficientlyVisible(), nil)]
      atIndex:0] performAction:grey_longPress()];
}

// Checks if the content of the given tab in the given window matches given
// text. This method exits the tab grid and re-enters it afterward.
- (void)fromGridCheckTabAtIndex:(int)tabIndex
                 inWindowNumber:(int)windowNumber
                   containsText:(const char*)text {
  [EarlGrey
      setRootMatcherForSubsequentInteractions:WindowWithNumber(windowNumber)];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(
                                          tabIndex)] performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:text
                             inWindowWithNumber:windowNumber];
  [ChromeEarlGreyUI openTabGrid];
}

- (void)waitForSnackBarMessage:(int)messageIdentifier
    triggeredByTappingItemWithMatcher:(id<GREYMatcher>)matcher {
  NSString* snackBarLabel = l10n_util::GetNSStringWithFixup(messageIdentifier);
  WaitForSnackbarTriggeredByTappingItem(snackBarLabel, matcher);
}

- (void)waitForSnackBarMessageText:(NSString*)snackBarLabel
    triggeredByTappingItemWithMatcher:(id<GREYMatcher>)matcher {
  WaitForSnackbarTriggeredByTappingItem(snackBarLabel, matcher);
}

- (void)waitForContextMenuToDisappear {
  ConditionBlock wait_for_disappearance = ^{
    NSError* error = nil;
    [[EarlGrey
        selectElementWithMatcher:grey_allOf(grey_kindOfClassName(
                                                @"_UIContextMenuContainerView"),
                                            grey_sufficientlyVisible(), nil)]
        assertWithMatcher:grey_nil()
                    error:&error];
    return error == nil;
  };
  GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(
                 kWaitForUIElementTimeout, wait_for_disappearance),
             @"Context menu did not disappear.");
}

// Verifies that the tab grid has exactly `expectedCount` tabs.
- (void)verifyVisibleTabsCount:(NSUInteger)expectedCount {
  // Verify that the cell # `expectedCount` exist.
  if (expectedCount == 0) {
    [[EarlGrey selectElementWithMatcher:TabGridCell()]
        assertWithMatcher:grey_nil()];
  } else {
    [[[EarlGrey selectElementWithMatcher:TabGridCell()]
        atIndex:expectedCount - 1] assertWithMatcher:grey_notNil()];
  }
  // Then verify that there is no more cells after that.
  [[EarlGrey
      selectElementWithMatcher:grey_allOf(TabGridCell(),
                                          TabGridCellAtIndex(expectedCount),
                                          nil)] assertWithMatcher:grey_nil()];
}

// Returns an interaction that scrolls down on the view matched by `viewMatcher`
// to search for the given `matcher`.
- (id<GREYInteraction>)scrollDownViewMatcher:(id<GREYMatcher>)viewMatcher
                             toSelectMatcher:(id<GREYMatcher>)matcher {
  return [[EarlGrey selectElementWithMatcher:matcher]
         usingSearchAction:grey_scrollInDirectionWithStartPoint(
                               kGREYDirectionDown, /*amount=*/100,
                               /*xOriginStartPercentage=*/0.5,
                               /*yOriginStartPercentage=*/0.5)
      onElementWithMatcher:viewMatcher];
}

// Returns an interaction that scrolls up on the view matched by `viewMatcher`
// to search for the given `matcher`.
- (id<GREYInteraction>)scrollUpViewMatcher:(id<GREYMatcher>)viewMatcher
                           toSelectMatcher:(id<GREYMatcher>)matcher {
  return [[EarlGrey selectElementWithMatcher:matcher]
         usingSearchAction:grey_scrollInDirectionWithStartPoint(
                               kGREYDirectionUp, /*amount=*/100,
                               /*xOriginStartPercentage=*/0.5,
                               /*yOriginStartPercentage=*/0.5)
      onElementWithMatcher:viewMatcher];
}

// Relaunches the app with Inactive Tabs in test mode (i.e. considers tabs as
// inactive immediately).
- (void)relaunchAppWithInactiveTabsTestMode {
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  config.relaunch_policy = ForceRelaunchByCleanShutdown;
  config.additional_args.push_back("-InactiveTabsTestMode");
  config.additional_args.push_back("true");
  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
}

@end
