| // 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); |
| } |
| |
| 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. |
| - (void)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]) { |
| 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 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. |
| - (void)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)"); |
| } |
| RelaunchConfigurationWithIPHFeature([self appConfigurationForTestCase], |
| @"IPH_iOSLensOverlayEntrypointTip", |
| /*safari_switcher=*/NO); |
| |
| // 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. |
| GREYAssertTrue( |
| [ChromeEarlGrey |
| testUIElementAppearanceWithMatcher:grey_accessibilityID( |
| kBubbleViewArrowViewIdentifier) |
| timeout:base::Seconds(1)], |
| @"Lens overlay entrypoint tip is not shown"); |
| |
| // 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. |
| GREYAssertFalse( |
| [ChromeEarlGrey testUIElementAppearanceWithMatcher: |
| grey_accessibilityID(kBubbleViewArrowViewIdentifier)], |
| @"Lens overlay entrypoint tip is still shown"); |
| } |
| |
| @end |