chromium/ios/chrome/browser/ui/omnibox/popup/omnibox_popup_egtest.mm

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

#import <XCTest/XCTest.h>

#import "base/functional/bind.h"
#import "base/ios/ios_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "components/omnibox/common/omnibox_features.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_constants.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/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "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_mac.h"

namespace {

/// Returns the popup row containing the `url` as suggestion.
id<GREYMatcher> PopupRowWithUrl(GURL url) {
  NSString* urlString = base::SysUTF8ToNSString(url.GetContent());
  id<GREYMatcher> URLMatcher = grey_allOf(
      grey_descendant(
          chrome_test_util::StaticTextWithAccessibilityLabel(urlString)),
      grey_sufficientlyVisible(), nil);
  return grey_allOf(chrome_test_util::OmniboxPopupRow(), URLMatcher, nil);
}

/// Returns the switch to open tab element for the `url`.
id<GREYMatcher> SwitchTabElementForUrl(const GURL& url) {
  return grey_allOf(
      grey_ancestor(PopupRowWithUrl(url)),
      grey_accessibilityID(kOmniboxPopupRowSwitchTabAccessibilityIdentifier),
      grey_interactable(), nil);
}

void TapSwitchToTabButton(const GURL& url) {
  [[EarlGrey selectElementWithMatcher:grey_allOf(SwitchTabElementForUrl(url),
                                                 grey_interactable(), nil)]
      performAction:grey_tap()];
}

id<GREYMatcher> OmniboxWithLeadingImageElement(
    NSString* const leadingImageIdentifier) {
  return grey_allOf(
      grey_ancestor(grey_kindOfClassName(@"OmniboxContainerView")),
      grey_accessibilityID(leadingImageIdentifier), grey_interactable(), nil);
}

void ScrollToSwitchToTabElement(const GURL& url) {
  [[[EarlGrey selectElementWithMatcher:grey_allOf(SwitchTabElementForUrl(url),
                                                  grey_interactable(), nil)]
         usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 200)
      onElementWithMatcher:chrome_test_util::OmniboxPopupList()]
      assertWithMatcher:grey_interactable()];
}

// 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 2.
const char kPage3[] = "This is the third page";
const char kPage3Title[] = "Title 3";
const char kPage3URL[] = "/page3.html";

/// 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 == kPage3URL) {
    http_response->set_content(
        "<html><head><title>" + std::string(kPage3Title) +
        "</title></head><body>" + std::string(kPage3) + "</body></html>");
    return std::move(http_response);
  }

  return nil;
}

}  //  namespace

@interface OmniboxPopupTestCase : ChromeTestCase
@end

@implementation OmniboxPopupTestCase {
  GURL _URL1;
  GURL _URL2;
  GURL _URL3;
}

- (AppLaunchConfiguration)appConfigurationForTestCase {
  AppLaunchConfiguration config = [super appConfigurationForTestCase];

  // Disable AutocompleteProvider types: TYPE_SEARCH and TYPE_ON_DEVICE_HEAD.
  omnibox::DisableAutocompleteProviders(config, 1056);

  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);
  _URL3 = self.testServer->GetURL(kPage3URL);

  [ChromeEarlGrey clearBrowsingHistory];
}

// Tests that tapping the switch to open tab button, switch to the open tab,
// doesn't close the tab.
- (void)testSwitchToOpenTab {
  // Open the first page.
  GURL firstPageURL = self.testServer->GetURL(kPage1URL);
  [ChromeEarlGrey loadURL:firstPageURL];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Open the second page in another tab.
  [ChromeEarlGreyUI openNewTab];
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPage2URL)];
  [ChromeEarlGrey waitForWebStateContainingText:kPage2];

  // Type the URL of the first page in the omnibox to trigger it as suggestion.
  [ChromeEarlGreyUI
      focusOmniboxAndReplaceText:base::SysUTF8ToNSString(kPage1URL)];

  // Switch to the first tab, scrolling the popup if necessary.
  ScrollToSwitchToTabElement(firstPageURL);
  TapSwitchToTabButton(firstPageURL);

  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Check that both tabs are opened (and that we switched tab and not just
  // navigated.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey
      selectElementWithMatcher:
          grey_allOf(chrome_test_util::StaticTextWithAccessibilityLabel(
                         base::SysUTF8ToNSString(kPage2Title)),
                     grey_ancestor(chrome_test_util::TabGridCellAtIndex(1)),
                     nil)] assertWithMatcher:grey_sufficientlyVisible()];
}

// Tests that the switch to open tab button isn't displayed for the current tab.
- (void)testNotSwitchButtonOnCurrentTab {
  // Open the first page.
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPage1URL)];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Open the second page in another tab.
  [ChromeEarlGreyUI openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kPage2];

  // Type the URL of the first page in the omnibox to trigger it as suggestion.
  [ChromeEarlGreyUI
      focusOmniboxAndReplaceText:base::SysUTF8ToNSString(kPage2URL)];

  // Check that we have the suggestion for the second page, but not the switch
  // as it is the current page.

  [[EarlGrey selectElementWithMatcher:PopupRowWithUrl(_URL2)]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:SwitchTabElementForUrl(_URL2)]
      assertWithMatcher:grey_not(grey_interactable())];
}

// Test that swiping left on a historical suggestion and tapping
// the delete button , removes the suggestions.
- (void)testDeleteHistoricalSuggestion {
  [self populateHistory];
  NSString* omniboxInput = [NSString
      stringWithFormat:@"%@:%@", base::SysUTF8ToNSString(_URL3.host()),
                       base::SysUTF8ToNSString(_URL3.port())];

  [ChromeEarlGreyUI focusOmniboxAndReplaceText:omniboxInput];

  // Swipe one of the historical suggestions, to the left.
  if ([ChromeEarlGrey isIPadIdiom]) {
    [[EarlGrey selectElementWithMatcher:PopupRowWithUrl(_URL1)]
        performAction:GREYSwipeSlowInDirectionWithStartPoint(kGREYDirectionLeft,
                                                             0.09, 0.5)];
  } else {
    [[EarlGrey selectElementWithMatcher:PopupRowWithUrl(_URL1)]
        performAction:grey_swipeSlowInDirection(kGREYDirectionLeft)];
  }

  // Delete button is displayed.
  [[EarlGrey selectElementWithMatcher:grey_kindOfClassName(
                                          @"UISwipeActionStandardButton")]
      assertWithMatcher:grey_sufficientlyVisible()];

  // Tap on the delete button.
  [[EarlGrey selectElementWithMatcher:grey_kindOfClassName(
                                          @"UISwipeActionStandardButton")]
      performAction:grey_tap()];

  // Historical suggestion with URL1 is now deleted.
  [[EarlGrey selectElementWithMatcher:PopupRowWithUrl(_URL1)]
      assertWithMatcher:grey_nil()];
}

// Tests that the incognito tabs aren't displayed as "opened" tab in the
// non-incognito suggestions and vice-versa.
- (void)testIncognitoSeparation {
  [self populateHistory];
  [[self class] closeAllTabs];

  // Load page 1 in non-incognito and page 2 in incognito.
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

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

  // Open page 3 in non-incognito.
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kPage3];

  NSString* omniboxInput = [NSString
      stringWithFormat:@"%@:%@", base::SysUTF8ToNSString(_URL3.host()),
                       base::SysUTF8ToNSString(_URL3.port())];
  [ChromeEarlGreyUI focusOmniboxAndReplaceText:omniboxInput];

  // Check that we have the switch button for the first page.
  [[EarlGrey
      selectElementWithMatcher:
          grey_allOf(grey_ancestor(PopupRowWithUrl(_URL1)),
                     grey_accessibilityID(
                         kOmniboxPopupRowSwitchTabAccessibilityIdentifier),
                     nil)] assertWithMatcher:grey_sufficientlyVisible()];

  // Check that we have the suggestion for the second page, but not the switch.
  [[EarlGrey selectElementWithMatcher:PopupRowWithUrl(_URL2)]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:SwitchTabElementForUrl(_URL2)]
      assertWithMatcher:grey_nil()];

  // Open page 3 in incognito.
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:_URL3];
  [ChromeEarlGrey waitForWebStateContainingText:kPage3];

  [ChromeEarlGreyUI
      focusOmniboxAndReplaceText:base::SysUTF8ToNSString(_URL3.host())];

  // Check that we have the switch button for the second page.
  [[EarlGrey
      selectElementWithMatcher:
          grey_allOf(grey_ancestor(PopupRowWithUrl(_URL2)),
                     grey_accessibilityID(
                         kOmniboxPopupRowSwitchTabAccessibilityIdentifier),
                     nil)] assertWithMatcher:grey_sufficientlyVisible()];

  // Check that we have the suggestion for the first page, but not the switch.
  [[EarlGrey selectElementWithMatcher:PopupRowWithUrl(_URL1)]
      assertWithMatcher:grey_sufficientlyVisible()];
  [[EarlGrey selectElementWithMatcher:SwitchTabElementForUrl(_URL1)]
      assertWithMatcher:grey_nil()];
}

- (void)testCloseNTPWhenSwitching {
  // Open the first page.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Open a new tab and switch to the first tab.
  [ChromeEarlGrey openNewTab];
  NSString* omniboxInput = [NSString
      stringWithFormat:@"%@:%@", base::SysUTF8ToNSString(_URL1.host()),
                       base::SysUTF8ToNSString(_URL1.port())];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
      performAction:grey_tap()];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(omniboxInput)];

  TapSwitchToTabButton(_URL1);
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Check that the other tab is closed.
  [ChromeEarlGrey waitForMainTabCount:1];
}

- (void)testDontCloseNTPWhenSwitchingWithForwardHistory {
  // Open the first page.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Open a new tab, navigate to a page and go back to have forward history.
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
  [ChromeEarlGrey goBack];

  // Navigate to the other tab.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
      performAction:grey_tap()];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(base::SysUTF8ToNSString(_URL1.host()))];

  // Omnibox can reorder itself in multiple animations, so add an extra wait
  // here.
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:SwitchTabElementForUrl(
                                                       _URL1)];
  [[EarlGrey selectElementWithMatcher:SwitchTabElementForUrl(_URL1)]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Check that the other tab is not closed.
  [ChromeEarlGrey waitForMainTabCount:2];
}

// Tests that switching to closed tab opens the tab in foreground, except if it
// is from NTP without history.
- (void)testSwitchToClosedTab {
  // Open the first page.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Open a new tab and load another URL.
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:self.testServer->GetURL(kPage2URL)];
  [ChromeEarlGrey waitForWebStateContainingText:kPage2];

  // Start typing url of the first page.
  [ChromeEarlGreyUI
      focusOmniboxAndReplaceText:base::SysUTF8ToNSString(kPage1URL)];

  // Make sure that the "Switch to Open Tab" element is visible, scrolling the
  // popup if necessary.
  ScrollToSwitchToTabElement(_URL1);

  // Close the first page.
  [ChromeEarlGrey closeTabAtIndex:0];
  [ChromeEarlGrey waitForMainTabCount:1];

  // Try to switch to the first tab.
  TapSwitchToTabButton(_URL1);
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
  [ChromeEarlGreyUI waitForAppToIdle];

  // Check that the URL has been opened in a new foreground tab.
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];
  [ChromeEarlGrey waitForMainTabCount:2];
}

// Tests that having multiple suggestions with corresponding opened tabs display
// multiple buttons.

- (void)testMultiplePageOpened {
  // Open the first page.
  [ChromeEarlGrey loadURL:_URL1];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Open the second page in a new tab.
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:_URL2];
  [ChromeEarlGrey waitForWebStateContainingText:kPage2];

  // Start typing url of the two opened pages in a new tab.
  [ChromeEarlGrey openNewTab];
  NSString* omniboxInput = [NSString
      stringWithFormat:@"%@:%@", base::SysUTF8ToNSString(_URL1.host()),
                       base::SysUTF8ToNSString(_URL1.port())];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
      performAction:grey_tap()];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(omniboxInput)];

  // Check that both elements are displayed.
  // Omnibox can reorder itself in multiple animations, so add an extra wait
  // here.
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:SwitchTabElementForUrl(
                                                       _URL1)];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:SwitchTabElementForUrl(
                                                       _URL2)];
}

// Tests that selecting a suggestion in the omnibox and successfully navigating
// to it adds an entry in the shortcuts database.
- (void)testShortcutsDatabasePopulation {
  [ChromeEarlGrey clearBrowsingHistory];
  // Ensure the database is initialized and empty.
  [OmniboxEarlGrey waitForShortcutsBackendInitialization];
  [OmniboxEarlGrey waitForNumberOfShortcutsInDatabase:0];

  [self populateHistory];
  NSString* omniboxInput = [NSString
      stringWithFormat:@"%@:%@", base::SysUTF8ToNSString(_URL3.host()),
                       base::SysUTF8ToNSString(_URL3.port())];

  [ChromeEarlGreyUI focusOmniboxAndReplaceText:omniboxInput];

  [[EarlGrey selectElementWithMatcher:PopupRowWithUrl(_URL1)]
      performAction:grey_tap()];
  [ChromeEarlGrey waitForWebStateContainingText:kPage1];

  // Verify that the shortcut database has been populated.
  [OmniboxEarlGrey waitForNumberOfShortcutsInDatabase:1];
}

#pragma mark - Helpers

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

@end

@interface OmniboxPopupWithFakeSuggestionTestCase : ChromeTestCase
@end

@implementation OmniboxPopupWithFakeSuggestionTestCase

- (void)setUp {
  [super setUp];
  [ChromeEarlGrey clearBrowsingHistory];

  [OmniboxAppInterface
      setUpFakeSuggestionsService:@"fake_suggestions_pedal.json"];
}

- (void)tearDown {
  [OmniboxAppInterface tearDownFakeSuggestionsService];
  [super tearDown];
}

- (void)testTapAppendArrowButton {
  [ChromeEarlGrey loadURL:GURL("about:blank")];

  // Clears the url and replace it with local url host.
  [ChromeEarlGreyUI focusOmniboxAndReplaceText:@"abc"];

  // Wait for the suggestions to show.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:
                      chrome_test_util::OmniboxPopupRowWithString(@"abcdef")];

  id<GREYMatcher> appendArrowButtonMatcher = grey_allOf(
      grey_ancestor(chrome_test_util::OmniboxPopupRowWithString(@"abcdef")),
      grey_accessibilityID(kOmniboxPopupRowAppendAccessibilityIdentifier), nil);

  // Wait for the append button to show.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:appendArrowButtonMatcher];

  // Tap on the append arrow button.
  [[EarlGrey selectElementWithMatcher:grey_allOf(appendArrowButtonMatcher,
                                                 grey_interactable(), nil)]
      performAction:grey_tap()];

  // Omnibox should now contain the suggestion row string 'abcdef '.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:
                      chrome_test_util::OmniboxContainingText("abcdef ")];

  // Wait for the new suggestions to show.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:
          chrome_test_util::OmniboxPopupRowWithString(@"abcdefghi")];
}

// Test when the popup is scrolled, the keyboard is dismissed
// but the omnibox is still expanded and the suggestions are visible.
- (void)testScrollingDismissesKeyboard {
  [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
      performAction:grey_tap()];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(@"abc")];

  // Matcher for a URL-what-you-typed suggestion.
  id<GREYMatcher> textMatcher = grey_descendant(
      chrome_test_util::StaticTextWithAccessibilityLabel(@"abc"));
  id<GREYMatcher> row =
      grey_allOf(chrome_test_util::OmniboxPopupRow(), textMatcher,
                 grey_sufficientlyVisible(), nil);

  // Omnibox can reorder itself in multiple animations, so add an extra wait
  // here.
  [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:row];
  [ChromeEarlGrey waitForKeyboardToAppear];

  // Scroll the popup. This swipes from the point located at 50% of the width of
  // the frame horizontally and most importantly 10% of the height of the frame
  // vertically. This is necessary if the center of the list's accessibility
  // frame is not visible, as it is the default start point.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxPopupList()]
      performAction:grey_swipeFastInDirectionWithStartPoint(kGREYDirectionDown,
                                                            0.5, 0.1)];

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

  // The keyboard should be dismissed.
  [ChromeEarlGrey waitForKeyboardToDisappear];
}

@end

@interface HardwareKeyboardInteractionTestCase : ChromeTestCase
@end

@implementation HardwareKeyboardInteractionTestCase

- (void)setUp {
  [super setUp];
  [ChromeEarlGrey clearBrowsingHistory];

  [OmniboxAppInterface
      setUpFakeSuggestionsService:@"fake_suggestions_pedal.json"];
}

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

// Tests up down interaction in omnibox popup using a hardware keyboard.
- (void)testUpDownArrowAutocomplete {
  // Focus omnibox from Web.
  [ChromeEarlGrey loadURL:GURL("about:blank")];
  [ChromeEarlGreyUI focusOmniboxAndReplaceText:@"testupdown"];

  // Matcher for the first autocomplete suggestions.
  id<GREYMatcher> testupDownAutocomplete1 =
      chrome_test_util::OmniboxPopupRowWithString(@"testupdownautocomplete1");

  // Wait for the suggestions to show.
  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:testupDownAutocomplete1];

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

  // The omnibox popup may update multiple times.  Don't downArrow until this
  // is done.
  base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(1));

  // Go down to testautocomplete1 popup row.
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"downArrow" flags:0];
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"downArrow" flags:0];

  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:
          chrome_test_util::OmniboxContainingText("testupdownautocomplete1")];

  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"upArrow" flags:0];

  [ChromeEarlGrey waitForUIElementToAppearWithMatcher:
                      chrome_test_util::OmniboxContainingText("testupdown")];
}

// Tests that leading image in omnibox changes based on the suggestion
// highlighted.
// TODO(crbug.com/40917341): Test is flaky on both device and simulator.
- (void)DISABLED_testOmniboxLeadingImage {
  // 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.");
  GURL _URL1 = self.testServer->GetURL(kPage1URL);

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

  // Focus omnibox from Web.
  [ChromeEarlGreyUI focusOmnibox];

  // Typing the title of page1.
  [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
      performAction:grey_replaceText(base::SysUTF8ToNSString(kPage1Title))];

  // Wait for suggestions to show.
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:PopupRowWithUrl(_URL1)];

  // The omnibox popup may update multiple times.  Don't downArrow until this
  // is done.
  base::test::ios::SpinRunLoopWithMinDelay(base::Seconds(1));
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"downArrow" flags:0];

  // We expect to have the default leading image.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:
          grey_allOf(OmniboxWithLeadingImageElement(
                         kOmniboxLeadingImageDefaultAccessibilityIdentifier),
                     nil)];

  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"downArrow" flags:0];

  // The popup row is a url suggestion so we expect to have the leading
  // suggestion image .
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:
          grey_allOf(
              OmniboxWithLeadingImageElement(
                  kOmniboxLeadingImageSuggestionImageAccessibilityIdentifier),
              nil)];
}

@end