chromium/ios/chrome/browser/ui/omnibox/omnibox_egtest.mm

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

#import <XCTest/XCTest.h>

#import "base/apple/foundation_util.h"
#import "base/containers/contains.h"
#import "base/functional/bind.h"
#import "base/ios/ios_util.h"
#import "base/strings/stringprintf.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "build/build_config.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/iph_for_new_chrome_user/model/features.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
#import "ios/chrome/browser/ui/omnibox/omnibox_app_interface.h"
#import "ios/chrome/browser/ui/omnibox/omnibox_earl_grey.h"
#import "ios/chrome/browser/ui/omnibox/omnibox_test_util.h"
#import "ios/chrome/browser/ui/omnibox/omnibox_ui_features.h"
#import "ios/chrome/browser/ui/omnibox/popup/omnibox_popup_accessibility_identifier_constants.h"
#import "ios/chrome/browser/ui/toolbar/public/toolbar_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_matchers_app_interface.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/chrome/test/earl_grey/chrome_xcui_actions.h"
#import "ios/chrome/test/earl_grey/test_switches.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/web/public/test/element_selector.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "net/test/embedded_test_server/http_request.h"
#import "net/test/embedded_test_server/http_response.h"
#import "ui/base/l10n/l10n_util.h"

using base::test::ios::kWaitForUIElementTimeout;
using chrome_test_util::WebViewMatcher;

namespace {

// Web page 1.
const char kPage1[] = "This is the first page";
const char kPage1Title[] = "Title 1";
const char kPage1URL[] = "/page1.html";

// Web page 2.
const char kPage2[] = "This is the second page";
const char kPage2Title[] = "Title 2";
const char kPage2URL[] = "/page2.html";

// Web page to try X-Client-Data header.
const char kHeaderPageURL[] = "/page3.html";
const char kHeaderPageSuccess[] = "header found!";
const char kHeaderPageFailure[] = "header failure";

// Path to a page containing the chromium logo and the text `kLogoPageText`.
const char kLogoPagePath[] = "/chromium_logo_page.html";
// The text of the message on the logo page.
const char kLogoPageText[] = "Page with some text and the chromium logo image.";
// The DOM element ID of the chromium image on the logo page.
const char kLogoPageChromiumImageId[] = "chromium_image";
// Y offset to tap on the middle of the text.
const CGFloat kOmniboxTextFieldMidY = 18;
// X offset to tap on the beginning of the text, tapping on the left edge
// inserts a carret instead of showing the menu.
const CGFloat kOmniboxTextFieldOffsetX = 30;

// Defocus the omnibox.
void DefocusOmnibox() {
  if ([ChromeEarlGrey isIPadIdiom]) {
    // "escape" is a hardcoded key string in hardware_keyboard_util that maps to
    // a HIDUsageCode.
    [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"escape" flags:0];
  } else {
    id<GREYMatcher> cancel_button =
        grey_accessibilityID(kToolbarCancelOmniboxEditButtonIdentifier);
    [[EarlGrey
        selectElementWithMatcher:grey_allOf(cancel_button,
                                            grey_sufficientlyVisible(), nil)]
        performAction:grey_tap()];
  }
  [ChromeEarlGreyUI waitForAppToIdle];
}

// Taps the pre edit text in the omnibox.
void TapOnPreEditTextInOmnibox() {
  // TODO(crbug.com/40266963): Find a better way to tap on the selected url.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_tapAtPoint(CGPointMake(kOmniboxTextFieldOffsetX,
                                                kOmniboxTextFieldMidY))];
}

// Provides responses for the different pages.
std::unique_ptr<net::test_server::HttpResponse> StandardResponse(
    const net::test_server::HttpRequest& request) {
  std::unique_ptr<net::test_server::BasicHttpResponse> http_response =
      std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HTTP_OK);

  if (request.relative_url == kPage1URL) {
    http_response->set_content(
        "<html><head><title>" + std::string(kPage1Title) +
        "</title></head><body>" + std::string(kPage1) + "</body></html>");
    return std::move(http_response);
  }

  if (request.relative_url == kPage2URL) {
    http_response->set_content(
        "<html><head><title>" + std::string(kPage2Title) +
        "</title></head><body>" + std::string(kPage2) + "</body></html>");
    return std::move(http_response);
  }

  if (request.relative_url == kHeaderPageURL) {
    std::string result = kHeaderPageFailure;
    if (base::Contains(request.headers, "X-Client-Data")) {
      result = kHeaderPageSuccess;
    }
    http_response->set_content("<html><body>" + result + "</body></html>");
    return std::move(http_response);
  }

  return nil;
}

// Returns Visit Copied Link button matcher from UIMenuController.
id<GREYMatcher> VisitCopiedLinkButton() {
  NSString* a11yLabelCopiedLink =
      l10n_util::GetNSString(IDS_IOS_VISIT_COPIED_LINK);
  return grey_allOf(grey_accessibilityLabel(a11yLabelCopiedLink),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

// Returns Paste button matcher from UIMenuController.
id<GREYMatcher> PasteButton() {
  NSString* a11yLabelPaste = @"Paste";
  return grey_allOf(grey_accessibilityLabel(a11yLabelPaste),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

// Returns Select button from UIMenuController.
id<GREYMatcher> SelectButton() {
  NSString* a11yLabelSelect = @"Select";
  return grey_allOf(grey_accessibilityLabel(a11yLabelSelect),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

// Returns Select All button from UIMenuController.
id<GREYMatcher> SelectAllButton() {
  NSString* a11yLabelSelectAll = @"Select All";
  return grey_allOf(grey_accessibilityLabel(a11yLabelSelectAll),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

// Returns Cut button from UIMenuController.
id<GREYMatcher> CutButton() {
  NSString* a11yLabelCut = @"Cut";
  return grey_allOf(grey_accessibilityLabel(a11yLabelCut),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

// Returns Copy button from UIMenuController.
id<GREYMatcher> CopyButton() {
  NSString* a11yLabelCopy = @"Copy";
  return grey_allOf(grey_accessibilityLabel(a11yLabelCopy),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

// Returns Search Copied Text button from UIMenuController.
id<GREYMatcher> SearchCopiedTextButton() {
  NSString* a11yLabelCopiedText =
      l10n_util::GetNSString(IDS_IOS_SEARCH_COPIED_TEXT);
  return grey_allOf(grey_accessibilityLabel(a11yLabelCopiedText),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

// Returns Search Copied Image button from UIMenuController.
id<GREYMatcher> SearchCopiedImageButton() {
  NSString* a11yLabelCopiedImage =
      l10n_util::GetNSString(IDS_IOS_SEARCH_COPIED_IMAGE);
  return grey_allOf(grey_accessibilityLabel(a11yLabelCopiedImage),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

// Returns Clear button at the trailing edge of the omnibox's text field.
id<GREYMatcher> ClearButton() {
  return chrome_test_util::ButtonWithAccessibilityLabelId(
      IDS_IOS_ACCNAME_CLEAR_TEXT);
}

#pragma mark LocationBar context menu buttons

// LocationBar context menu buttons can be showed in different orders depending
// on the omnibox position (top or bottom toolbar). The context menu is
// configured with UIContextMenuConfigurationElementOrderPriority that will
// create the buttons two times (one for earch ordering) with one of them
// hidden. The added `grey_hidden(NO)` prevents conflict when matching with
// GREYMatcher.

// Returns Copy button from the location bar context menu.
id<GREYMatcher> CopyContextMenuButton() {
  int copyButtonId = IDS_IOS_SHARE_MENU_COPY;
  if ([ChromeEarlGrey isBottomOmniboxAvailable]) {
    copyButtonId = IDS_IOS_COPY_LINK_ACTION_TITLE;
  }
  return grey_allOf(
      chrome_test_util::ContextMenuItemWithAccessibilityLabelId(copyButtonId),
      grey_accessibilityTrait(UIAccessibilityTraitButton), grey_hidden(NO),
      nil);
}

// Returns Visit Copied Link button from the location bar context menu.
id<GREYMatcher> VisitCopiedLinkContextMenuButton() {
  return grey_allOf(chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
                        IDS_IOS_VISIT_COPIED_LINK),
                    grey_accessibilityTrait(UIAccessibilityTraitButton),
                    grey_hidden(NO), nil);
}

// Returns Search Copied Text button from the location bar context menu.
id<GREYMatcher> SearchCopiedTextContextMenuButton() {
  return grey_allOf(chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
                        IDS_IOS_SEARCH_COPIED_TEXT),
                    grey_accessibilityTrait(UIAccessibilityTraitButton),
                    grey_hidden(NO), nil);
}

// Returns Search Copied Image button from the location bar context menu.
id<GREYMatcher> SearchCopiedImageContextMenuButton() {
  return grey_allOf(chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
                        IDS_IOS_SEARCH_COPIED_IMAGE),
                    grey_accessibilityTrait(UIAccessibilityTraitButton),
                    grey_hidden(NO), nil);
}

// Returns Search Copied Image button from the location bar context menu.
id<GREYMatcher> SearchCopiedImageWithLensContextMenuButton() {
  return grey_allOf(chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
                        IDS_IOS_SEARCH_COPIED_IMAGE_WITH_LENS),
                    grey_accessibilityTrait(UIAccessibilityTraitButton),
                    grey_hidden(NO), nil);
}

// Taps the fake omnibox and waits for the real omnibox to be visible.
void FocusFakebox() {
  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
      performAction:grey_tap()];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];
}

}  //  namespace

@interface OmniboxTestCase : ChromeTestCase {
  GURL _URL1;
}

@end

@implementation OmniboxTestCase

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

- (void)setUp {
  [super setUp];

  // Start a server to be able to navigate to a web page.
  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&StandardResponse));
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  _URL1 = self.testServer->GetURL(kPage1URL);

  [ChromeEarlGrey clearPasteboard];
  [ChromeEarlGrey clearBrowsingHistory];

  [ChromeEarlGrey setBoolValue:NO forLocalStatePref:prefs::kBottomOmnibox];
}

- (void)tearDown {
  [ChromeEarlGrey setBoolValue:NO forLocalStatePref:prefs::kBottomOmnibox];
  [super tearDown];
}

#pragma mark - Helpers

// Navigates to Page 1 in a tab and waits for it to load.
- (void)openPage1 {
  // Go to a web page to have a normal location bar.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
}

// Copies image from `kLogoPagePath` into the clipboard using web context menu
// interactions.
- (void)copyImageIntoClipboard {
  [ChromeEarlGrey clearPasteboard];
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kLogoPagePath)];
  [ChromeEarlGrey waitForWebStateContainingText:kLogoPageText];
  [[EarlGrey selectElementWithMatcher:WebViewMatcher()]
      performAction:chrome_test_util::LongPressElementForContextMenu(
                        [ElementSelector
                            selectorWithElementID:kLogoPageChromiumImageId],
                        true /* menu should appear */)];
  [[EarlGrey selectElementWithMatcher:
                 chrome_test_util::ContextMenuItemWithAccessibilityLabelId(
                     IDS_IOS_CONTENT_CONTEXT_COPYIMAGE)]
      performAction:grey_tap()];

  GREYCondition* copyCondition =
      [GREYCondition conditionWithName:@"Image copied condition"
                                 block:^BOOL {
                                   return [ChromeEarlGrey pasteboardHasImages];
                                 }];
  // Wait for copy to happen or timeout after 5 seconds.
  GREYAssertTrue([copyCondition waitWithTimeout:5], @"Copying image failed");
}

// Tests that the XClientData header is sent when navigating to
// https://google.com through the omnibox.
- (void)testXClientData {
  // TODO(crbug.com/40145916) This test is flakily because of a DCHECK in
  // ios/web.  Clearing browser history first works around the problem, but
  // shouldn't be necessary otherwise.  Remove once the bug is fixed.
  [ChromeEarlGrey clearBrowsingHistory];

  // Rewrite the google URL to localhost URL.
  [OmniboxAppInterface rewriteGoogleURLToLocalhost];

  // Force variations to send the requests.
  GREYAssert([OmniboxAppInterface forceVariationID:100],
             @"Variation not enabled.");

  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
      performAction:grey_tap()];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];

  // The headers are only sent with https requests.
  GURL::Replacements httpsReplacements;
  httpsReplacements.SetSchemeStr(url::kHttpsScheme);

  NSString* URL = base::SysUTF8ToNSString(
      self.testServer->GetURL("www.google.com", kHeaderPageURL)
          .ReplaceComponents(httpsReplacements)
          .spec());

  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(URL)];
  // TODO(crbug.com/40916974): Use simulatePhysicalKeyboardEvent until
  // replaceText can properly handle \n.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"\n" flags:0];

  [ChromeEarlGrey waitForWebStateContainingText:kHeaderPageSuccess];
}

#pragma mark - Omnibox Menu Paste to Search

// Tests that Visit Copied Link, Search Copied Text, Search Copied Image and
// Paste menu buttons are not shown with an empty Clipboard.
- (void)testOmniboxMenuEmptyPasteboard {
  FocusFakebox();

  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_longPress()];

  // Pressing should not allow pasting when pasteboard is empty.
  // Verify that system text selection callout is not displayed.
  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchCopiedTextButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchCopiedImageButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:PasteButton()]
      assertWithMatcher:grey_nil()];
}

// Tests that Search Copied Text menu button is shown with text in the clipboard
// and is starting a search.
- (void)testOmniboxMenuPasteTextToSearch {
  FocusFakebox();
  NSString* textToSearch = @"TextToCopy";
  // Copy text in clipboard.
  [ChromeEarlGrey copyTextToPasteboard:textToSearch];
  // Tap Search Copied Text menu button.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_longPress()];

  // Wait for UIMenuController to appear or timeout after 2 seconds.
  GREYCondition* SearchTextButtonIsDisplayed = [GREYCondition
      conditionWithName:@"Search Copied Text button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey
                        selectElementWithMatcher:SearchCopiedTextButton()]
                        assertWithMatcher:grey_notNil()
                                    error:&error];
                    return error == nil;
                  }];
  GREYAssertTrue([SearchTextButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"Search Copied Text button display failed");
  [[EarlGrey selectElementWithMatcher:SearchCopiedTextButton()]
      performAction:grey_tap()];
  // Check that the omnibox contains the copied text.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxContainingText(
                            base::SysNSStringToUTF8(textToSearch))];
}

// Tests that Visit Copied Link menu button is shown with a link in the
// clipboard and is visiting the URL.
- (void)testOmniboxMenuPasteURLToSearch {
  FocusFakebox();
  // Copy URL into clipboard.
  [ChromeEarlGrey copyTextToPasteboard:base::SysUTF8ToNSString(_URL1.spec())];
  // Tap Visit Copied Link menu button.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_longPress()];

  // Wait for UIMenuController to appear or timeout after 2 seconds.
  GREYCondition* VisitLinkButtonIsDisplayed = [GREYCondition
      conditionWithName:@"Visit Copied Link button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey selectElementWithMatcher:VisitCopiedLinkButton()]
                        assertWithMatcher:grey_notNil()
                                    error:&error];
                    return error == nil;
                  }];
  GREYAssertTrue([VisitLinkButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"Visit Copied Link button display failed");
  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkButton()]
      performAction:grey_tap()];
  // Verify that the page is loaded.
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
}

// Tests that Search Copied Image menu button is shown with an image in the
// clipboard and is starting an image search.
// TODO(crbug.com/40928559): Fix flakiness and re-enable.
- (void)DISABLED_testOmniboxMenuPasteImageToSearch {
  [self copyImageIntoClipboard];

  // Wait for the context menu to dismiss, so the omnibox can be tapped.
  [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                      chrome_test_util::DefocusedLocationView()];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:ClearButton()] performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_longPress()];

  // Wait for UIMenuController to appear or timeout after 2 seconds.
  GREYCondition* SearchImageButtonIsDisplayed = [GREYCondition
      conditionWithName:@"Search Copied Image button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey
                        selectElementWithMatcher:SearchCopiedImageButton()]
                        assertWithMatcher:grey_notNil()
                                    error:&error];
                    return error == nil;
                  }];
  GREYAssertTrue([SearchImageButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"Search Copied Image button display failed");
  [[EarlGrey selectElementWithMatcher:SearchCopiedImageButton()]
      performAction:grey_tap()];

  // Check that the omnibox started a google search.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxContainingText("google")];
}

// Tests that copying in the omnibox will trigger the share button IPH to be
// displayed, if certain conditions are met, with bottom omnibox for phone form
// factor.
- (void)testCopyInOmniboxTriggersShareButtonIPHWithBottomOmnibox {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad (no bottom omnibox in tablet)");
  }

  [ChromeEarlGrey setBoolValue:YES forLocalStatePref:prefs::kBottomOmnibox];

  // Enable the IPH flag to ensure the IPH triggersenable)
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  config.iph_feature_enabled = "IPH_iOSShareToolbarItemFeature";
  config.additional_args.push_back("--enable-features=IPHForSafariSwitcher");
  // Force the conditions that allow the iph to show.
  config.additional_args.push_back("-ForceExperienceForDeviceSwitcher");
  config.additional_args.push_back("SyncedAndFirstDevice");
  config.relaunch_policy = ForceRelaunchByCleanShutdown;

  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [self openPage1];

  [ChromeEarlGreyUI focusOmnibox];

  // By default the address should be selected. Tapping should open the system
  // menu.
  TapOnPreEditTextInOmnibox();

  // Verify that the Copy button is displayed.
  [[EarlGrey selectElementWithMatcher:CopyButton()]
      assertWithMatcher:grey_notNil()];

  // Tapping it should copy the URL.
  [[EarlGrey selectElementWithMatcher:CopyButton()] performAction:grey_tap()];
  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];

  DefocusOmnibox();

  // Verify the share button IPH is shown.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:grey_accessibilityID(
                                              @"BubbleViewLabelIdentifier")];
}

// Tests that copying in the omnibox will trigger the share button IPH to be
// displayed, if certain conditions are met, when omnibox is at the top.
- (void)testCopyInOmniboxTriggersShareButtonIPHWithTopOmnibox {
  // Enable the IPH flag to ensure the IPH triggers
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  config.iph_feature_enabled = "IPH_iOSShareToolbarItemFeature";
  config.additional_args.push_back("--enable-features=IPHForSafariSwitcher");
  // Force the conditions that allow the iph to show.
  config.additional_args.push_back("-ForceExperienceForDeviceSwitcher");
  config.additional_args.push_back("SyncedAndFirstDevice");

  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [self openPage1];

  [ChromeEarlGreyUI focusOmnibox];

  // By default the address should be selected. Tapping should open the system
  // menu.
  TapOnPreEditTextInOmnibox();

  // Verify that the Copy button is displayed.
  [[EarlGrey selectElementWithMatcher:CopyButton()]
      assertWithMatcher:grey_notNil()];

  // Tapping it should copy the URL.
  [[EarlGrey selectElementWithMatcher:CopyButton()] performAction:grey_tap()];
  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];

  DefocusOmnibox();

  // Verify the omnibox is at the top.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::OmniboxOnTop()];

  // Verify the share button IPH is shown.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:grey_accessibilityID(
                                              @"BubbleViewLabelIdentifier")];
}

@end

#pragma mark - Steady state tests

@interface LocationBarSteadyStateTestCase : ChromeTestCase {
  GURL _URL1;
  GURL _URL2;
}

@end

@implementation LocationBarSteadyStateTestCase

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

- (void)setUp {
  [super setUp];

  // Start a server to be able to navigate to a web page.
  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&StandardResponse));
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  _URL1 = self.testServer->GetURL(kPage1URL);
  _URL2 = self.testServer->GetURL(kPage2URL);

  [ChromeEarlGrey clearBrowsingHistory];

  // Clear the pasteboard in case there is a URL copied.
  UIPasteboard* pasteboard = UIPasteboard.generalPasteboard;
  [pasteboard setValue:@"" forPasteboardType:UIPasteboardNameGeneral];

  [ChromeEarlGrey setBoolValue:NO forLocalStatePref:prefs::kBottomOmnibox];
}

- (void)tearDown {
  [ChromeEarlGrey setBoolValue:NO forLocalStatePref:prefs::kBottomOmnibox];
  [super tearDown];
}

// Tapping on steady view starts editing.
- (void)testTapSwitchesToEditing {
  [self openPage1];

  [ChromeEarlGreyUI focusOmnibox];
  [self checkLocationBarEditState];
}

// Tests that in compact, a share button is visible.
// Voice search is not enabled on the bots, so the voice search button is
// not tested here.
- (void)testTrailingButton {
  [self openPage1];

  if ([ChromeEarlGrey isCompactWidth]) {
    [[EarlGrey selectElementWithMatcher:chrome_test_util::TabShareButton()]
        assertWithMatcher:grey_sufficientlyVisible()];
  }
}
// Tests that a security info icon is visible on a web page.
- (void)testSecurityInfoIcon {
  [self openPage1];

  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::PageSecurityInfoIndicator()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that location bar shows the current URL in a short form.
- (void)testLocationBarUrl {
  [self openPage1];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      assertWithMatcher:chrome_test_util::LocationViewContainingText(
                            _URL1.host())];
}

- (void)testCopyPaste {
  [self openPage1];

  // Long pressing should allow copying.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_longPress()];

  // Verify that the Copy button is displayed.
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      assertWithMatcher:grey_notNil()];

  // Pressing should not allow pasting when pasteboard is empty.
  // Verify that system text selection callout is not displayed.
  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkContextMenuButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchCopiedTextContextMenuButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchCopiedImageContextMenuButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey
      selectElementWithMatcher:SearchCopiedImageWithLensContextMenuButton()]
      assertWithMatcher:grey_nil()];

  [self checkLocationBarSteadyState];

  // Tapping it should copy the URL.
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];

  // Go to another web page.
  [self openPage2];

  // Visit copied link should now be available.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_longPress()];
  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkContextMenuButton()]
      assertWithMatcher:grey_notNil()];

  [self checkLocationBarSteadyState];

  // Tapping it should navigate to Page 1.
  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkContextMenuButton()]
      performAction:grey_tap()];

  [ChromeEarlGrey waitForPageToFinishLoading];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
}

// Tests that copying the location bar by long pressing will trigger the share
// button IPH to be displayed, if certain conditions are met, with the omnibox
// at the bottom. Runs for phone factor only.
- (void)testCopyLocationBarTriggersShareButtonIPHWithBottomOmnibox {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad (no bottom omnibox in tablet)");
  }

  [ChromeEarlGrey setBoolValue:YES forLocalStatePref:prefs::kBottomOmnibox];

  // Enable the IPH flag to ensure the IPH triggers
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  config.iph_feature_enabled = "IPH_iOSShareToolbarItemFeature";
  config.additional_args.push_back("--enable-features=IPHForSafariSwitcher");
  // Force the conditions that allow the iph to show.
  config.additional_args.push_back("-ForceExperienceForDeviceSwitcher");
  config.additional_args.push_back("SyncedAndFirstDevice");

  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [self openPage1];
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::OmniboxAtBottom()];

  // Long pressing should allow copying.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_longPress()];

  // Verify that the Copy button is displayed.
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      assertWithMatcher:grey_notNil()];

  // Tapping it should copy the URL.
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];

  // Verify the share button IPH is shown.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:grey_accessibilityID(
                                              @"BubbleViewLabelIdentifier")];
}

// Tests that copying the location bar by long pressing will trigger the share
// button IPH to be displayed, if certain conditions are met, with the omnibox
// at the top.
- (void)testCopyLocationBarTriggersShareButtonIPHWithTopOmnibox {
  // Enable the IPH flag to ensure the IPH triggers
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  config.iph_feature_enabled = "IPH_iOSShareToolbarItemFeature";
  config.additional_args.push_back("--enable-features=IPHForSafariSwitcher");
  // Force the conditions that allow the iph to show.
  config.additional_args.push_back("-ForceExperienceForDeviceSwitcher");
  config.additional_args.push_back("SyncedAndFirstDevice");

  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [self openPage1];

  // Long pressing should allow copying.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_longPress()];

  // Verify that the Copy button is displayed.
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      assertWithMatcher:grey_notNil()];

  // Tapping it should copy the URL.
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];

  // Verify the omnibox is at the top.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::OmniboxOnTop()];

  // Verify the share button IPH is shown.
  GREYAssert([ChromeEarlGrey testUIElementAppearanceWithMatcher:
                                 chrome_test_util::TabShareButton()],
             @"The share button is not found");
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:grey_accessibilityID(
                                              @"BubbleViewLabelIdentifier")];
}

// Tests that copying the location bar by long pressing will NOT trigger the
// share button IPH to be displayed, if the share button is disabled because the
// url is not sharable.
- (void)testCopyLocationBarNotTriggersShareButtonIPHWhenButtonDisabled {
  // Enable the IPH flag to ensure the IPH triggers
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  config.iph_feature_enabled = "IPH_iOSShareToolbarItemFeature";
  config.additional_args.push_back("--enable-features=IPHForSafariSwitcher");
  // Force the conditions that allow the iph to show.
  config.additional_args.push_back("-ForceExperienceForDeviceSwitcher");
  config.additional_args.push_back("SyncedAndFirstDevice");
  config.additional_args.push_back(
      base::StringPrintf("--disable-features=BottomOmniboxSteadyState"));

  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [ChromeEarlGrey loadURL:GURL("chrome://version")];

  // Long pressing should allow copying.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_longPress()];

  // Verify that the Copy button is displayed.
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      assertWithMatcher:grey_notNil()];

  // Tapping it should copy the URL.
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      performAction:grey_tap()];
  [ChromeEarlGrey
      verifyStringCopied:base::SysUTF8ToNSString("chrome://version")];

  // Verify the share button is shown but the IPH is not shown.
  GREYAssert([ChromeEarlGrey testUIElementAppearanceWithMatcher:
                                 chrome_test_util::TabShareButton()],
             @"The share button is not found");
  GREYAssert(
      ![ChromeEarlGrey
          testUIElementAppearanceWithMatcher:grey_accessibilityID(
                                                 @"BubbleViewLabelIdentifier")],
      @"The share button IPH is displayed");
}

- (void)testDismissesEditMenu {
  [self openPage1];

  // Long pressing should open edit menu.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_longPress()];
  [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
      assertWithMatcher:grey_notNil()];

  // Dismiss context menu.
  GREYAssertTrue([ChromeEarlGreyUI dismissContextMenuIfPresent],
                 @"Failed to dismiss context menu.");

  GREYCondition* contextMenuDismissed = [GREYCondition
      conditionWithName:@"Wait for context menu to be dismissed"
                  block:^BOOL {
                    NSError* error;
                    [[EarlGrey selectElementWithMatcher:CopyContextMenuButton()]
                        assertWithMatcher:grey_nil()
                                    error:&error];
                    return error == nil;
                  }];

  // Verify that the context menu disappeared.
  GREYAssertTrue([contextMenuDismissed
                     waitWithTimeout:base::test::ios::kWaitForUIElementTimeout
                                         .InSecondsF()],
                 @"Context menu is still visible.");
}

// Tests that the omnibox defocuses when switching to another tab.
- (void)testOmniboxDefocusesOnTabSwitch {
  [ChromeEarlGrey closeAllTabs];

  [ChromeEarlGrey openNewTab];
  [self openPage1];

  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey waitForMainTabCount:2];
  [self openPage2];

  [ChromeEarlGreyUI focusOmnibox];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"Obama")];

  // The popup should open.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxPopupList()]
      assertWithMatcher:grey_notNil()];

  // Switch to the first tab.
  [ChromeEarlGrey selectTabAtIndex:0];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // The omnibox shouldn't be focused and the popup should be closed.
  [self checkLocationBarSteadyState];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxPopupList()]
      assertWithMatcher:grey_notVisible()];
}

// Tests that the omnibox defocuses when switching to another incognito tab.
- (void)testOmniboxDefocusesOnTabSwitchIncognito {
  [ChromeEarlGrey closeAllTabs];

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

  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey waitForIncognitoTabCount:2];
  [self openPage2];

  [ChromeEarlGreyUI focusOmnibox];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"Obama")];

  // The popup should open.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxPopupList()]
      assertWithMatcher:grey_notNil()];

  // Switch to the first tab.
  [ChromeEarlGrey selectTabAtIndex:0];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // The omnibox shouldn't be focused and the popup should be closed.
  [self checkLocationBarSteadyState];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxPopupList()]
      assertWithMatcher:grey_notVisible()];

  // Exit incognito.
  [ChromeEarlGrey closeAllTabs];
}

#pragma mark - Helpers

// Navigates to Page 1 in a tab and waits for it to load.
- (void)openPage1 {
  // Go to a web page to have a normal location bar.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
}

// Navigates to Page 2 in a tab and waits for it to load.
- (void)openPage2 {
  // Go to a web page to have a normal location bar.
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kPage2];
}

// Checks that the location bar is currently in steady state.
- (void)checkLocationBarSteadyState {
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:grey_notVisible()];
}

// Checks that the location bar is currently in edit state.
- (void)checkLocationBarEditState {
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:grey_sufficientlyVisible()];
}

@end

#pragma mark - Pre-edit state tests

@interface LocationBarPreEditStateTestCase : ChromeTestCase
@end

@implementation LocationBarPreEditStateTestCase {
  GURL _URL;
}

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

- (void)setUp {
  [super setUp];

  [ChromeEarlGrey clearBrowsingHistory];

  // Start a server to be able to navigate to a web page.
  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&StandardResponse));
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  _URL = self.testServer->GetURL(kPage1URL);

  // Clear the pasteboard in case there is a URL copied.
  [ChromeEarlGrey clearPasteboard];
}

- (void)tearDown {
  // Clear the pasteboard after every test.
  [ChromeEarlGrey clearPasteboard];
  [super tearDown];
}

// Tests that tapping on steady view on webpage and starting typing a query
// would clear the page url and have instead the typed query in the omnibox text
// field.
- (void)testTypingOnPreEditState {
  // Load a web page.
  [ChromeEarlGrey loadURL:_URL];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  [ChromeEarlGreyUI focusOmnibox];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText(_URL.GetContent())];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"hello")];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText("hello")];
}

// Tests that tapping on omnibox in pre-edit state in SRP or webpage would
// display a callout menu with cut,copy and paste (if there is a text on the
// pasteboard).
- (void)testTapOmniboxOnPreEditState {
  // Load a web page.
  [ChromeEarlGrey loadURL:_URL];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Focus Omnibox.
  [ChromeEarlGreyUI focusOmnibox];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Add a text to the pasteBoard (So that we have the paste button displayed).
  [ChromeEarlGrey copyTextToPasteboard:@"hello"];

  // Tap on Omnibox on pre edit state.
  TapOnPreEditTextInOmnibox();

  // Wait for callout copy button to be displayed.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:
                      chrome_test_util::SystemSelectionCalloutCopyButton()];

  // Verify that system text cut callout is displayed.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          SystemSelectionCalloutCutButton()]
      assertWithMatcher:grey_notNil()];

  // Verify that system text paste callout is displayed.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          SystemSelectionCalloutPasteButton()]
      assertWithMatcher:grey_notNil()];
}

// Tests that Cut callout button would erase the current url and copy it on the
// pasteboard.
- (void)testCutCalloutButton {
  // Load a web page.
  [ChromeEarlGrey loadURL:_URL];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Focus Omnibox.
  [ChromeEarlGreyUI focusOmnibox];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap on Omnibox on pre edit state.
  TapOnPreEditTextInOmnibox();

  // Wait for callout cut button to be displayed.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:
                      chrome_test_util::SystemSelectionCalloutCutButton()];

  // Tapping on Cut.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          SystemSelectionCalloutCutButton()]
      performAction:grey_tap()];

  // Omnibox now should be empty.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText("")];
}

// Tests that Paste callout button would erase the current url and replace it
// with text on pasteboard.
- (void)testPasteCalloutButton {
  // Load a web page.
  [ChromeEarlGrey loadURL:_URL];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Focus Omnibox.
  [ChromeEarlGreyUI focusOmnibox];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Add a text to the pasteBoard (So that we have the paste button displayed).
  [ChromeEarlGrey copyTextToPasteboard:@"hello"];

  // Tap on Omnibox on pre edit state.
  TapOnPreEditTextInOmnibox();

  // Wait for callout paste button to be displayed.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:
                      chrome_test_util::SystemSelectionCalloutPasteButton()];

  // Tapping on Paste.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::
                                          SystemSelectionCalloutPasteButton()]
      performAction:grey_tap()];

  // Omnibox now should be empty.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText("hello")];
}

@end

#pragma mark - Edit state tests

@interface LocationBarEditStateTestCase : ChromeTestCase
@end

@implementation LocationBarEditStateTestCase {
  GURL _URL1;
  GURL _URL2;
}

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

- (void)setUp {
  [super setUp];

  [ChromeEarlGrey clearBrowsingHistory];

  // Start a server to be able to navigate to a web page.
  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&StandardResponse));
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  _URL1 = self.testServer->GetURL(kPage1URL);
  _URL2 = self.testServer->GetURL(kPage2URL);

  // Clear the pasteboard in case there is a URL copied.
  [ChromeEarlGrey clearPasteboard];
}

// Copy button should be hidden when the omnibox is empty otherwise it should be
// displayed. Paste button should be hidden when pasteboard is empty otherwise
// it should be displayed. Select & SelectAll buttons should be hidden when the
// omnibox is empty.
// TODO(b/325908456): This test fails on iPad device.
#if TARGET_OS_SIMULATOR
#define MAYBE_testEmptyOmnibox testEmptyOmnibox
#else
#define MAYBE_testEmptyOmnibox DISABLED_testEmptyOmnibox
#endif
- (void)MAYBE_testEmptyOmnibox {
  // Focus omnibox.
  [self focusFakebox];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_longPress()];

  // Pressing should not allow copying when omnibox is empty.
  // Wait for Copy button to appear or timeout after 2 seconds.
  GREYCondition* CopyButtonIsDisplayed = [GREYCondition
      conditionWithName:@"Copy button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey selectElementWithMatcher:
                                   chrome_test_util::
                                       SystemSelectionCalloutCopyButton()]
                        assertWithMatcher:grey_notNil()
                                    error:&error];
                    return error == nil;
                  }];
  // Verify that system text selection callout is not displayed.
  GREYAssertFalse([CopyButtonIsDisplayed
                      waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                  @"Copy button should not be displayed");

  // Pressing should not allow select or selectAll when omnibox is empty.
  // Verify that system text selection callout is not displayed.
  [[EarlGrey selectElementWithMatcher:SelectButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SelectAllButton()]
      assertWithMatcher:grey_nil()];

  // Pressing should not allow pasting when pasteboard is empty.
  // Verify that system text selection callout is not displayed.
  [[EarlGrey selectElementWithMatcher:VisitCopiedLinkButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:SearchCopiedTextButton()]
      assertWithMatcher:grey_nil()];
  [[EarlGrey selectElementWithMatcher:PasteButton()]
      assertWithMatcher:grey_nil()];

  // Writing in the omnibox field.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"this is a test")];

  // Long press on the omnibox.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_longPress()];

  // SelectAll the omnibox field.
  GREYCondition* SelectAllButtonIsDisplayed = [GREYCondition
      conditionWithName:@"SelectAll button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey selectElementWithMatcher:SelectAllButton()]
                        performAction:grey_tap()
                                error:&error];
                    return error == nil;
                  }];
  GREYAssertTrue([SelectAllButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"SelectAll button display failed");

  // Cut the text.
  GREYCondition* CutButtonIsDisplayed = [GREYCondition
      conditionWithName:@"Cut button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey selectElementWithMatcher:CutButton()]
                        assertWithMatcher:grey_notNil()
                                    error:&error];
                    return error == nil;
                  }];
  GREYAssertTrue([CutButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"Cut button display failed");
  [[EarlGrey selectElementWithMatcher:CutButton()] performAction:grey_tap()];

  // Long pressing should allow pasting.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_longPress()];
  // Verify that system text selection callout is displayed (Search Copied
  // Text).
  GREYCondition* searchCopiedTextButtonIsDisplayed = [GREYCondition
      conditionWithName:@"Search Copied Text button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey
                        selectElementWithMatcher:SearchCopiedTextButton()]
                        assertWithMatcher:grey_notNil()
                                    error:&error];
                    return error == nil;
                  }];
  GREYAssertTrue([searchCopiedTextButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"Search Copied Text button display failed");
  // Verify that system text selection callout is displayed (Paste).
  [[EarlGrey selectElementWithMatcher:PasteButton()]
      assertWithMatcher:grey_notNil()];
}

// Select & SelectAll buttons should be displayed when the omnibox is not empty
// and no text is selected. If the selected text is a sub part of the omnibox
// fied, Select button should be hidden & SelectAll button should be displayed.
// If the selected text is the entire omnibox field, select & SelectAll button
// should be hidden.
// TODO(b/325908456): This test fails on iPad device.
#if TARGET_OS_SIMULATOR
#define MAYBE_testSelection testSelection
#else
#define MAYBE_testSelection DISABLED_testSelection
#endif
- (void)MAYBE_testSelection {
  // Focus omnibox.
  [self focusFakebox];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:grey_sufficientlyVisible()];
  // Write in the omnibox field.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"this is a test")];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_longPress()];

  // Pressing should allow select and selectAll.
  // Wait for UIMenuController to appear or timeout after 2 seconds.
  GREYCondition* SelectButtonIsDisplayed = [GREYCondition
      conditionWithName:@"Select button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey selectElementWithMatcher:SelectButton()]
                        assertWithMatcher:grey_notNil()
                                    error:&error];
                    return error == nil;
                  }];
  // Verify that system text selection callout is displayed.
  GREYAssertTrue([SelectButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"Select button display failed");
  [[EarlGrey selectElementWithMatcher:SelectAllButton()]
      assertWithMatcher:grey_notNil()];

  // Wait for UIMenuController to appear or timeout after 2 seconds.
  GREYCondition* CopyButtonIsDisplayed = [GREYCondition
      conditionWithName:@"Copy button display condition"
                  block:^BOOL {
                    NSError* error = nil;
                    [[EarlGrey selectElementWithMatcher:
                                   chrome_test_util::
                                       SystemSelectionCalloutCopyButton()]
                        assertWithMatcher:grey_notNil()
                                    error:&error];
                    return error == nil;
                  }];
  // Pressing select should allow copy.
  // select should be hidden.
  [[EarlGrey selectElementWithMatcher:SelectButton()] performAction:grey_tap()];
  GREYAssertTrue([CopyButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"Copy button display failed");
  [[EarlGrey selectElementWithMatcher:SelectButton()]
      assertWithMatcher:grey_nil()];

  // Pressing selectAll should allow copy.
  // selectAll should be hidden.
  [[EarlGrey selectElementWithMatcher:SelectAllButton()]
      performAction:grey_tap()];
  GREYAssertTrue([CopyButtonIsDisplayed
                     waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()],
                 @"Copy button display failed");
  [[EarlGrey selectElementWithMatcher:SelectAllButton()]
      assertWithMatcher:grey_nil()];
}

// TODO(b/325908456): This test fails on iPad device.
#if TARGET_OS_SIMULATOR
#define MAYBE_testNoDefaultMatch testNoDefaultMatch
#else
#define MAYBE_testNoDefaultMatch DISABLED_testNoDefaultMatch
#endif
- (void)MAYBE_testNoDefaultMatch {
  NSString* copiedText = @"test no default match1";

  // Put some text in pasteboard.
  [ChromeEarlGrey copyTextToPasteboard:copiedText];

  [ChromeEarlGrey verifyStringCopied:copiedText];

  // Focus the omnibox.
  [self focusFakebox];

  // Make sure that:
  // 1. Chrome didn't crash (See crbug.com/1024885 for historic context)
  // 2. There's nothing in the omnibox
  // 3. There's a "text you copied" match

  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText("")];

  // Returns the "Text you copied" row.
  NSString* textYouCopiedLabel =
      l10n_util::GetNSString(IDS_TEXT_FROM_CLIPBOARD);
  id<GREYMatcher> textYouCopiedMatch = grey_allOf(
      chrome_test_util::OmniboxPopupRow(),
      grey_descendant(grey_accessibilityLabel(textYouCopiedLabel)), nil);
  [[EarlGrey selectElementWithMatcher:textYouCopiedMatch]
      assertWithMatcher:grey_notNil()];
}

// Tests inline autocomplete is shown.
- (void)testInlineAutocompleteSuggestion {
  // Disable all autocomplete providers except the history url provider.
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  omnibox::DisableAutocompleteProviders(config, 524279);

  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [self populateHistory];

  // Clears the url and replace it with local url prefix.
  // TODO(crbug.com/40916974): This should use grey_typeText when fixed.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"127")];

  // We expect to have an autocomplete.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assert:[OmniboxAppInterface displaysInlineAutocompleteText:YES]];
}

// Verifies that tapping an autocomplete suggestion in the omnibox successfully
// completes the user's query.
- (void)testTapBehaviors {
  // Disable all autocomplete providers except the history url provider.
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  omnibox::DisableAutocompleteProviders(config, 524279);

  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [self populateHistory];

  // Clears the url and replace it with local url prefix.
  // TODO(crbug.com/40916974): This should use grey_typeText when fixed.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"127")];

  // We expect to have an autocomplete.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assert:[OmniboxAppInterface displaysInlineAutocompleteText:YES]];
  // Tapping the inline autocomplete should accept it.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_tap()];

  // Inline autocomplete gets accepted and it is not presented anymore.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assert:[OmniboxAppInterface displaysInlineAutocompleteText:NO]];

  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxContainingAutocompleteText(
                            @"")];
}

#pragma mark - Helpers

// Taps the fake omnibox and waits for the real omnibox to be visible.
- (void)focusFakebox {
  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
      performAction:grey_tap()];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];
}

// Populate history by visiting the 2 different pages.
- (void)populateHistory {
  // Add all the pages to the history.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kPage2];
}

@end

#pragma mark - Hardware keyboard simulation interaction with omnibox

@interface OmniboxHardwareKeyboardInteractionTestCase : ChromeTestCase
@end

@implementation OmniboxHardwareKeyboardInteractionTestCase {
  GURL _URL1;
  GURL _URL2;
}

- (void)setUp {
  [super setUp];

  [ChromeEarlGrey clearBrowsingHistory];

  // Start a server to be able to navigate to a web page.
  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&StandardResponse));
  GREYAssertTrue(self.testServer->Start(), @"Test server failed to start.");

  _URL1 = self.testServer->GetURL(kPage1URL);
  _URL2 = self.testServer->GetURL(kPage2URL);

  // Clear the pasteboard in case there is a URL copied.
  [ChromeEarlGrey clearPasteboard];
}

- (void)tearDown {
  [super tearDown];
  // HW keyboard simulation can mess up the SW keyboard simulator state.
  // Relaunching resets the state.
  AppLaunchConfiguration config = [super appConfigurationForTestCase];
  config.relaunch_policy = ForceRelaunchByCleanShutdown;
  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
}

// Tests that in SRP/webpage state, pressing on forward delete HW key would
// erase the selected url.
- (void)testHardwareBackspaceKey {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  [ChromeEarlGreyUI focusOmnibox];
  // Omnibox contains the page url.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText(_URL1.GetContent())];

  // Press the backspace HW keyboard key.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"\b" flags:0];

  // Omnibox now should be empty.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::OmniboxText("")];
}

// Tests that in SRP/webpage state, pressing Left/right arrows HW keyboard keys
// would exit pre-edit state and put the cursor to the end (right arrow) or to
// the begining (left arrow)
- (void)testHardwareLeftRightArrowKey {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  [ChromeEarlGreyUI focusOmnibox];
  // Omnibox contains the page url.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText(_URL1.GetContent())];

  // Simulate press the HW right arrow key.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"rightArrow" flags:0];

  // Type something
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"hello" flags:0];

  // Omnibox now should contain the page url suffixed with 'hello'
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::Omnibox()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText(_URL1.GetContent() +
                                                      "hello")];

  DefocusOmnibox();

  [ChromeEarlGreyUI focusOmnibox];
  // Omnibox contains the page url.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::Omnibox()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText(_URL1.GetContent())];
  // Simulate press the HW right arrow key.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"leftArrow" flags:0];

  // Type something
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"hello" flags:0];

  // Omnibox now should contain the page url prefixed with 'hello'
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::Omnibox()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText("hello" +
                                                      _URL1.GetContent())];
}

// Copies and pastes a URL, then performs an undo of the paste, and attempts to
// perform a second undo.
- (void)testCopyPasteUndo {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  [ChromeEarlGreyUI focusOmnibox];

  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"C"
                                          flags:UIKeyModifierCommand];

  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];

  // Defocus the omnibox.
  DefocusOmnibox();

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

  [ChromeEarlGreyUI focusOmnibox];

  // Attempt to paste.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"V"
                                          flags:UIKeyModifierCommand];

  // Verify that paste happened.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxContainingText(kPage1URL)];

  // Attempt to undo.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"Z"
                                          flags:UIKeyModifierCommand];

  // Verify that undo happened.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxContainingText(kPage2URL)];

  // Attempt to undo again. Nothing should happen. In the past this could lead
  // to a crash.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"Z"
                                          flags:UIKeyModifierCommand];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxContainingText(kPage2URL)];
}

// Focus the omnibox and hit "cmd+X". This should remove all text from the
// omnibox and put it in the clipboard. This had been broken before because of
// the preedit state complexity. Paste to verify that the URL was indeed copied.
- (void)testCutInPreedit {
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  [ChromeEarlGreyUI focusOmnibox];

  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"X"
                                          flags:UIKeyModifierCommand];
  [ChromeEarlGrey verifyStringCopied:base::SysUTF8ToNSString(_URL1.spec())];

  // Verify that the omnibox is empty.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxText("")];

  // Attempt to paste.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"V"
                                          flags:UIKeyModifierCommand];

  // Verify that paste happened.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assertWithMatcher:chrome_test_util::OmniboxContainingText(kPage1URL)];
}

// Tests that pressing spacebar key when having an autocomplete, removes it.
- (void)testHWspacebarKeyOnAutocomplete {
  // Disable all autocomplete providers except the history url provider.
  AppLaunchConfiguration config = [self appConfigurationForTestCase];
  omnibox::DisableAutocompleteProviders(config, 524279);

  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [self populateHistory];

  // Clears the url and replace it with local url prefix.
  // TODO(crbug.com/40916974): This should use grey_typeText when fixed.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"127")];

  // Autocomplete is present.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assert:[OmniboxAppInterface displaysInlineAutocompleteText:YES]];

  // Pressing spacebar.
  // TODO(crbug.com/40916974): This should use grey_typeText when fixed.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@" " flags:0];

  // Autocomplete removed.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:chrome_test_util::Omnibox()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      assert:[OmniboxAppInterface displaysInlineAutocompleteText:NO]];
}

#pragma mark - Helpers

// Populate history by visiting the 2 different pages.
- (void)populateHistory {
  // Add all the pages to the history.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kPage2];
}

@end