910e62b5创建于 1月15日历史提交
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "base/ios/ios_util.h"
#import "base/strings/stringprintf.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "components/feature_engagement/public/feature_constants.h"
#import "components/omnibox/browser/omnibox_pref_names.h"
#import "ios/chrome/browser/bubble/ui_bundled/bubble_constants.h"
#import "ios/chrome/browser/bubble/ui_bundled/gesture_iph/gesture_in_product_help_view_egtest_utils.h"
#import "ios/chrome/browser/intelligence/features/features.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/public/features/features.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/test_switches.h"
#import "ios/chrome/test/scoped_eg_synchronization_disabler.h"
#import "ios/testing/earl_grey/base_earl_grey_test_case_app_interface.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "net/test/embedded_test_server/embedded_test_server.h"
#import "url/gurl.h"

namespace {

using ::chrome_test_util::BackButton;
using ::chrome_test_util::ForwardButton;

// Bottom toolbar of of the tab view.
id<GREYMatcher> BottomToolbar() {
  return grey_kindOfClassName(@"SecondaryToolbarView");
}

// Open split screen. Should only be invoked for iPad.
void OpenSplitScreen() {
  if (![ChromeEarlGrey areMultipleWindowsSupported]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad without multiwindow support.");
  }
  [ChromeEarlGrey openNewWindow];
  [ChromeEarlGrey waitForForegroundWindowCount:2];
  [EarlGrey setRootMatcherForSubsequentInteractions:chrome_test_util::
                                                        WindowWithNumber(0)];
}

// Reload the current page from omnibox.
void ReloadFromOmnibox() {
  [ChromeEarlGreyUI focusOmnibox];
  [ChromeEarlGrey simulatePhysicalKeyboardEvent:@"\n" flags:0];
  [ChromeEarlGrey waitForPageToFinishLoading];
}

}  // namespace

@interface BubblePresenterTestCase : ChromeTestCase
@end

@implementation BubblePresenterTestCase

// Open a random url from omnibox. `isAfterNewAppLaunch` is used for deciding
// whether the step of tapping the fake omnibox is needed.
- (void)openURLFromOmniboxWithIsAfterNewAppLaunch:(BOOL)isAfterNewAppLaunch {
  if (isAfterNewAppLaunch) {
    [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()]
        performAction:grey_tap()];
    [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                        chrome_test_util::Omnibox()];
  }

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

#pragma mark - Tests

- (void)setUp {
  [super setUp];
  MakeFirstRunRecent();
  [ChromeEarlGrey
      resetDataForLocalStatePref:omnibox::kIsOmniboxInBottomPosition];
}

- (void)tearDownHelper {
  [ChromeEarlGrey closeAllExtraWindows];
  [BaseEarlGreyTestCaseAppInterface enableFastAnimation];
  ResetFirstRunRecency();
  [super tearDownHelper];
}

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

  // Enable lens overlay flag to test the IPH.
  if ([self
          isRunningTest:@selector
          (testLensOverlayEntrypointTipDismissedWhenOmniboxPositionChanged)]) {
    config.features_enabled.push_back(kEnableLensOverlay);
    config.features_disabled.push_back(kPageActionMenu);
    config.iph_feature_enabled = "IPH_iOSLensOverlayEntrypointTip";
  }

  return config;
}

// Tests that the pull-to-refresh IPH is attempted when user taps the omnibox
// to reload the same page, and disappears after the user navigates away.
// TODO(crbug.com/440549642): This test is flaky.
- (void)
    FLAKY_testPullToRefreshIPHAfterReloadFromOmniboxAndDisappearsAfterNavigation {
  if ([ChromeEarlGrey isIPadIdiom]) {
    if (@available(iOS 19.0, *)) {
      // TODO(crbug.com/427699033): Re-enable test on iOS 26.
      // Test uses "split screen" (multiwindow) to force compact width.
      EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26.");
    }
  }
  RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature",
                         /*safari_switcher=*/YES);
  if ([ChromeEarlGrey isIPadIdiom]) {
    OpenSplitScreen();
  }
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1 inWindowWithNumber:0];
  [ChromeEarlGrey loadURL:destinationUrl2 inWindowWithNumber:0];
  ReloadFromOmnibox();
  AssertGestureIPHVisibleWithDismissAction(
      @"Pull to refresh IPH did not appear after reloading from omnibox.", ^{
        [[EarlGrey selectElementWithMatcher:BackButton()]
            performAction:grey_tap()];
      });
  AssertGestureIPHInvisible(
      @"Pull to refresh IPH still showed after user navigates to another page");
}

// Tests that the pull-to-refresh IPH is attempted when user reloads the page
// using context menu.
- (void)testPullToRefreshIPHAfterReloadFromContextMenuAndDisappearsOnSwitchTab {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad.");
  }
  RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature",
                         /*safari_switcher=*/YES);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1 inWindowWithNumber:0];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:destinationUrl2 inWindowWithNumber:0];
  // Reload using context menu.
  [ChromeEarlGreyUI reload];
  [ChromeEarlGrey waitForPageToFinishLoading];
  AssertGestureIPHVisibleWithDismissAction(
      @"Pull to refresh IPH did not appear after reloading from context menu.",
      ^{
        // Side swipe on the toolbar.
        [[EarlGrey selectElementWithMatcher:BottomToolbar()]
            performAction:grey_swipeSlowInDirection(kGREYDirectionRight)];
      });
  AssertGestureIPHInvisible(
      @"Pull to refresh IPH did not dismiss after changing tab.");
}

// Tests that the pull-to-refresh IPH is NOT attempted when page loading fails.
// TODO(crbug.com/427699033): This is also failing on older iOS versions
// when building with Xcode 26.
// TODO(crbug.com/463351924): Test fails on device.
#if TARGET_OS_SIMULATOR
#define MAYBE_testPullToRefreshIPHShouldDisappearOnEnteringTabGrid \
  testPullToRefreshIPHShouldDisappearOnEnteringTabGrid
#else
#define MAYBE_testPullToRefreshIPHShouldDisappearOnEnteringTabGrid \
  DISABLED_testPullToRefreshIPHShouldDisappearOnEnteringTabGrid
#endif
- (void)MAYBE_testPullToRefreshIPHShouldDisappearOnEnteringTabGrid {
  if ([ChromeEarlGrey isIPadIdiom]) {
    // TODO(crbug.com/427699033): Re-enable test when fixed with Xcode 26.
    // Test uses "split screen" (multiwindow) to force compact width.
    EARL_GREY_TEST_DISABLED(@"Test disabled when building with Xcode 26.");
  }
  RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature",
                         /*safari_switcher=*/YES);
  if ([ChromeEarlGrey isIPadIdiom]) {
    OpenSplitScreen();
  }
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl = self.testServer->GetURL("/pony.html");
  [ChromeEarlGrey loadURL:destinationUrl inWindowWithNumber:0];
  ReloadFromOmnibox();
  AssertGestureIPHVisibleWithDismissAction(
      @"Pull to refresh IPH did not appear after reloading from omnibox.", ^{
        [ChromeEarlGreyUI openTabGrid];
      });
  AssertGestureIPHInvisible(
      @"Pull to refresh IPH still visible after going to tab grid.");
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  AssertGestureIPHInvisible(@"Pull to refresh IPH still visible after going to "
                            @"tab grid and coming back.");
  ExpectHistogramEmittedForIPHDismissal(
      IPHDismissalReasonType::kTappedOutsideIPHAndAnchorView);
}

// Tests that the pull-to-refresh IPH is NOT attempted when page loading fails.
- (void)testPullToRefreshIPHShouldNotShowOnPageLoadFail {
  if ([ChromeEarlGrey isIPadIdiom]) {
    // TODO(crbug.com/427699033): Re-enable test on iOS 26.
    // Test uses "split screen" (multiwindow) to force compact width.
    EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26.");
  }
  RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature",
                         /*safari_switcher=*/YES);
  if ([ChromeEarlGrey isIPadIdiom]) {
    OpenSplitScreen();
  }
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl = self.testServer->GetURL("/pony.html");
  [ChromeEarlGrey loadURL:destinationUrl inWindowWithNumber:0];
  // Cut off server.
  GREYAssertTrue(self.testServer->ShutdownAndWaitUntilComplete(),
                 @"Server did not shut down.");
  ReloadFromOmnibox();
  AssertGestureIPHInvisible(
      @"Pull to refresh IPH still appeared despite loading fails.");
}

// Tests that the pull-to-refresh IPH is atttempted when user taps the omnibox
// to reload the same page, and disappears after the user navigates away.
// TODO(crbug.com/459498160): This test is flaky.
- (void)FLAKY_testPullToRefreshIPHShouldNotShowOnRegularXRegular {
  if (![ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPhone.");
  }
  RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature",
                         /*safari_switcher=*/YES);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  [ChromeEarlGrey loadURL:self.testServer->GetURL("/pony.html")];
  ReloadFromOmnibox();
  AssertGestureIPHInvisible(
      @"Pull to refresh IPH showed on regular x regular size class.");
}

// Tests that the swipe back/forward IPH is attempted on navigation, and
// disappears when user leaves the page.
- (void)testSwipeBackForwardIPHShowsOnNavigationAndHidesOnNavigation {
  RelaunchWithIPHFeature(@"IPH_iOSSwipeBackForward", /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey loadURL:destinationUrl2];
  [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
  AssertGestureIPHVisibleWithDismissAction(
      @"Swipe back/forward IPH did not appear after tapping back button.", ^{
        [[EarlGrey selectElementWithMatcher:ForwardButton()]
            performAction:grey_tap()];
      });
  AssertGestureIPHInvisible(
      @"Swipe back/forward IPH still appeared after user left the page.");
  ExpectHistogramEmittedForIPHDismissal(
      IPHDismissalReasonType::kTappedOutsideIPHAndAnchorView);
}

// Tests that the pull-to-refresh IPH would be dismissed with the reason
// `kSwipedAsInstructedByGestureIPH` when the user pulls down on the IPH.
- (void)DISABLED_testPullToRefreshPerformAction {
  if ([ChromeEarlGrey isIPadIdiom]) {
    if (@available(iOS 19.0, *)) {
      // TODO(crbug.com/427699033): Re-enable test on iOS 26.
      // Test uses "split screen" (multiwindow) to force compact width.
      EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26.");
    }
  }
  RelaunchWithIPHFeature(@"IPH_iOSPullToRefreshFeature",
                         /*safari_switcher=*/YES);
  if ([ChromeEarlGrey isIPadIdiom]) {
    OpenSplitScreen();
  }
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  // Trigger pull-to-refresh IPH.
  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1 inWindowWithNumber:0];
  [ChromeEarlGrey loadURL:destinationUrl2 inWindowWithNumber:0];
  ReloadFromOmnibox();
  AssertGestureIPHVisibleWithDismissAction(
      @"Pull to refresh IPH did not appear after reloading from omnibox.", ^{
        // Swipe down.
        SwipeIPHInDirection(kGREYDirectionDown, /*edge_swipe=*/NO);
      });
  AssertGestureIPHInvisible(
      @"Pull to refresh IPH should be dismissed after swiping down.");
  ExpectHistogramEmittedForIPHDismissal(
      IPHDismissalReasonType::kSwipedAsInstructedByGestureIPH);
}

// Tests that bi-directional swipe IPH shows when both forward and backward are
// navigatable, but only one-directional swipe shows when the user can only
// navigate back OR forward. The bi-directional swipe IPH takes longer to
// timeout.
- (void)testSwipeBackForwardIPHDirections {
  // Single direction swipe IPH takes 9s, while bi-direction swipe IPH takes
  // 12s; use a fixed wait time between the two to distinguish between the two
  // kinds of swipe IPHs.
  const base::TimeDelta waitTime = base::Seconds(11);
  RelaunchWithIPHFeature(@"IPH_iOSSwipeBackForward", /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey loadURL:destinationUrl2];

  // Go back to destination URL 1.
  [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
  // Wait while animation runs.
  {
    ScopedSynchronizationDisabler sync_disabler;
    base::test::ios::SpinRunLoopWithMinDelay(waitTime);
  }
  AssertGestureIPHVisibleWithDismissAction(
      @"Bi-directional swipe back/forward IPH should still be visible.", nil);
  RelaunchWithIPHFeature(@"IPH_iOSSwipeBackForward", /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];
  // Go forward to destination URL 2.
  [[EarlGrey selectElementWithMatcher:ForwardButton()]
      performAction:grey_tap()];
  // Wait while animation runs.
  {
    ScopedSynchronizationDisabler sync_disabler;
    base::test::ios::SpinRunLoopWithMinDelay(waitTime);
  }
  AssertGestureIPHInvisible(
      @"One directional swipe back/forward IPH should not be visible.");
}

// Tests that opening a new tab hides the swipe back/forward IPH.
- (void)testSwipeBackForwardIPHHidesOnNewTabOpening {
  RelaunchWithIPHFeature(@"IPH_iOSSwipeBackForward", /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey loadURL:destinationUrl2];
  [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
  AssertGestureIPHVisibleWithDismissAction(
      @"Swipe back/forward IPH did not appear after tapping back button.", ^{
        [ChromeEarlGrey openNewTab];
      });
  AssertGestureIPHInvisible(
      @"Swipe back/forward IPH still appeared after user opens a new tab.");
  ExpectHistogramEmittedForIPHDismissal(
      IPHDismissalReasonType::kTappedOutsideIPHAndAnchorView);
}

// Tests that the back/forward swipe IPH would be dismissed with the reason
// `kSwipedAsInstructedByGestureIPH` when the user swipes the page in the
// correct direction.
- (void)testSwipeBackForwardPerformAction {
  RelaunchWithIPHFeature(@"IPH_iOSSwipeBackForward", /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey loadURL:destinationUrl2];
  [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
  AssertGestureIPHVisibleWithDismissAction(
      @"Swipe back/forward IPH did not appear after going back.", ^{
        SwipeIPHInDirection(kGREYDirectionLeft, /*edge_swipe=*/YES);
      });
  AssertGestureIPHInvisible(@"Swipe back/forward IPH should be dismissed after "
                            @"swiping in the right direction.");
  ExpectHistogramEmittedForIPHDismissal(
      IPHDismissalReasonType::kSwipedAsInstructedByGestureIPH);
}

// Tests that the swipe back/forward IPH would NOT show if the page load fails.
- (void)testSwipeBackForwardDoesNotShowWhenPageFails {
  RelaunchWithIPHFeature(@"IPH_iOSSwipeBackForward", /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey loadURL:destinationUrl2];
  [ChromeEarlGrey purgeCachedWebViewPages];

  // Cut off server and go back to destination URL 1.
  GREYAssertTrue(self.testServer->ShutdownAndWaitUntilComplete(),
                 @"Server did not shut down.");
  [[EarlGrey selectElementWithMatcher:BackButton()] performAction:grey_tap()];
  AssertGestureIPHInvisible(
      @"Swipe back/forward IPH should not be visible when page fails to load.");
}

// Tests that the toolbar swipe IPH would be shown when and only when the user
// taps an adjacent tab.
- (void)testThatTappingAdjacentTabTriggersToolbarSwipeIPH {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad (IPH is iPhone only)");
  }
  RelaunchWithIPHFeature(@"IPH_iOSSwipeToolbarToChangeTab",
                         /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  // Make sure three tabs are created.
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 = self.testServer->GetURL("/destination.html");
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:destinationUrl2];
  [ChromeEarlGrey openNewTab];
  // Switch to non-adjacent tab.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  AssertGestureIPHInvisible(@"Toolbar swipe IPH should not be visible when the "
                            @"user switches to an non-adjacent tab.");
  // Switch to adjacent tab.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(1)]
      performAction:grey_tap()];
  AssertGestureIPHVisibleWithDismissAction(
      @"Toolbar swipe IPH should be visible when the user switches to an "
      @"adjacent tab.",
      nil);
}

// Tests that the toolbar swipe IPH would be dismissed with the reason
// `kTappedClose` when the user taps "dismiss" on the IPH.
- (void)testShowToolbarSwipeIPHAndTapDismissButton {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad (IPH is iPhone only)");
  }
  RelaunchWithIPHFeature(@"IPH_iOSSwipeToolbarToChangeTab",
                         /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  // Make sure two tabs are created.
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:destinationUrl2];
  // Switch to adjacent tab.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  // This is needed because of a race condition with animating the closure of
  // the tab grid.
  WaitForTabGridDisappearance();
  AssertGestureIPHVisibleWithDismissAction(
      @"Toolbar swipe IPH should be visible when the user switches to an "
      @"adjacent tab.",
      ^{
        TapDismissButton();
      });
  AssertGestureIPHInvisible(
      @"IPH still displaying after the user taps the \"dismiss\" button.");
  ExpectHistogramEmittedForIPHDismissal(IPHDismissalReasonType::kTappedClose);
}

// Tests that the toolbar swipe IPH would be dismissed with the reason
// `kSwipedAsInstructedByGestureIPH` when the user swipes the toolbar in the
// correct direction.
- (void)testShowToolbarSwipeIPHAndPerformAction {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad (IPH is iPhone only)");
  }
  RelaunchWithIPHFeature(@"IPH_iOSSwipeToolbarToChangeTab",
                         /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  // Make sure two tabs are created.
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 =
      self.testServer->GetURL("/chromium_logo_page.html");
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:destinationUrl2];
  // Switch to adjacent tab.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  AssertGestureIPHVisibleWithDismissAction(
      @"Toolbar swipe IPH should be visible when the user switches to an "
      @"adjacent tab.",
      ^{
        // Swipe the toolbar in the wrong direction after it appears.
        [ChromeEarlGrey
            waitForUIElementToAppearWithMatcher:grey_allOf(BottomToolbar(),
                                                           grey_interactable(),
                                                           nil)];
        [[EarlGrey selectElementWithMatcher:BottomToolbar()]
            performAction:grey_swipeSlowInDirection(kGREYDirectionRight)];
      });
  AssertGestureIPHVisibleWithDismissAction(
      @"Toolbar swipe IPH should NOT be dismissed after swipe in the wrong "
      @"direction.",
      ^{
        [ChromeEarlGrey
            waitForUIElementToAppearWithMatcher:grey_allOf(BottomToolbar(),
                                                           grey_interactable(),
                                                           nil)];
        // Swipe the toolbar in the right direction.
        [[EarlGrey selectElementWithMatcher:BottomToolbar()]
            performAction:grey_swipeSlowInDirection(kGREYDirectionLeft)];
      });
  AssertGestureIPHInvisible(@"Toolbar swipe IPH should be dismissed after "
                            @"swipe in the right direction.");
  ExpectHistogramEmittedForIPHDismissal(
      IPHDismissalReasonType::kSwipedAsInstructedByGestureIPH);
}

// Tests that the toolbar swipe IPH would NOT be shown if the user has switched
// pages.
- (void)testThatToolbarSwipeIPHDoesNotShowAfterPageChange {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad (IPH is iPhone only)");
  }
  RelaunchWithIPHFeature(@"IPH_iOSSwipeToolbarToChangeTab",
                         /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  // Make sure two tabs are created.
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 = self.testServer->GetURL("/destination.html");
  // Load two pages each in incognito and regular.
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:destinationUrl2];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey openNewIncognitoTab];
  [ChromeEarlGrey loadURL:destinationUrl2];
  // Switch to the "adjacent to active" tab on regular.
  [ChromeEarlGrey showTabSwitcher];
  [[EarlGrey
      selectElementWithMatcher:chrome_test_util::TabGridOpenTabsPanelButton()]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  AssertGestureIPHInvisible(
      @"Toolbar swipe IPH should not be visible when the "
      @"user switches to an adjacent tab after changing page.");
}

// Tests that the toolbar swipe IPH would be dismissed with the reason
// `kTappedOutsideIPHAndAnchorView` when the user leaves the page using other
// means.
- (void)testShowToolbarSwipeIPHAndLeavePage {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad (IPH is iPhone only)");
  }
  RelaunchWithIPHFeature(@"IPH_iOSSwipeToolbarToChangeTab",
                         /*safari_switcher=*/NO);
  [BaseEarlGreyTestCaseAppInterface disableFastAnimation];

  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  // Make sure two tabs are created.
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  const GURL destinationUrl2 = self.testServer->GetURL("/destination.html");
  const GURL destinationUrl3 =
      self.testServer->GetURL("/chromium_logo_page.html");
  // Load two pages so that the user can tap back button.
  [ChromeEarlGrey loadURL:destinationUrl1];
  [ChromeEarlGrey loadURL:destinationUrl2];
  [ChromeEarlGrey openNewTab];
  [ChromeEarlGrey loadURL:destinationUrl3];
  // Switch to adjacent tab.
  [ChromeEarlGreyUI openTabGrid];
  [[EarlGrey selectElementWithMatcher:chrome_test_util::TabGridCellAtIndex(0)]
      performAction:grey_tap()];
  // Checks visibility and tap "back" button to dismiss the IPH.
  AssertGestureIPHVisibleWithDismissAction(
      @"Toolbar swipe IPH should be visible when the user switches to an "
      @"adjacent tab.",
      ^{
        [ChromeEarlGrey
            waitForUIElementToAppearWithMatcher:grey_allOf(BackButton(),
                                                           grey_interactable(),
                                                           nil)];
        [[EarlGrey selectElementWithMatcher:BackButton()]
            performAction:grey_tap()];
      });
  AssertGestureIPHInvisible(
      @"Toolbar swipe IPH should be dismissed after leaving the page.");
  ExpectHistogramEmittedForIPHDismissal(
      IPHDismissalReasonType::kTappedOutsideIPHAndAnchorView);
}

// Tests that the Lens overlay entrypoint tip is dismissed when Omnibox position
// is changed.
- (void)testLensOverlayEntrypointTipDismissedWhenOmniboxPositionChanged {
  if ([ChromeEarlGrey isIPadIdiom]) {
    EARL_GREY_TEST_SKIPPED(@"Skipped for iPad (IPH is iPhone only)");
  }

  // Load a random page.
  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
  const GURL destinationUrl1 = self.testServer->GetURL("/pony.html");
  [ChromeEarlGrey loadURL:destinationUrl1];

  // Verify Lens overlay entrypoint tip is shown.
  [ChromeEarlGrey
      waitForUIElementToAppearWithMatcher:grey_accessibilityID(
                                              kBubbleViewArrowViewIdentifier)];

  // Move Omnibox to the bottom.
  [ChromeEarlGrey setBoolValue:YES
             forLocalStatePref:omnibox::kIsOmniboxInBottomPosition];
  [ChromeEarlGreyUI waitForToolbarVisible:YES];

  // Verify Lens overlay entrypoint tip is hidden after Omnibox position
  // changed.
  [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:
                      grey_accessibilityID(kBubbleViewArrowViewIdentifier)];
}

@end