| // Copyright 2024 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/strings/stringprintf.h" |
| #import "components/feature_engagement/public/feature_constants.h" |
| #import "components/omnibox/browser/omnibox_pref_names.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_actions.h" |
| #import "ios/chrome/test/earl_grey/chrome_earl_grey.h" |
| #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h" |
| #import "ios/chrome/test/earl_grey/chrome_matchers.h" |
| #import "ios/chrome/test/earl_grey/chrome_test_case.h" |
| #import "ios/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 "net/test/embedded_test_server/request_handler_util.h" |
| |
| namespace { |
| |
| // The page height of test pages. This must be big enough to triger fullscreen. |
| const int kPageHeightEM = 200; |
| |
| // Provides test page long enough to allow fullscreen. |
| std::unique_ptr<net::test_server::HttpResponse> GetLongResponseForFullscreen( |
| const net::test_server::HttpRequest& request) { |
| auto result = std::make_unique<net::test_server::BasicHttpResponse>(); |
| result->set_code(net::HTTP_OK); |
| result->set_content(base::StringPrintf( |
| "<p style='height:%dem'>test1</p><p>test2</p>", kPageHeightEM)); |
| return result; |
| } |
| |
| // Returns the Contextual Panel's entrypoint view GREY matcher. |
| id<GREYMatcher> ContextualPanelEntrypointImageViewMatcher() { |
| return grey_allOf( |
| grey_accessibilityID(@"ContextualPanelEntrypointImageViewAXID"), |
| grey_interactable(), nil); |
| } |
| |
| } // namespace |
| |
| @interface ContextualPanelTestCase : ChromeTestCase |
| @end |
| |
| @implementation ContextualPanelTestCase |
| |
| - (void)setUp { |
| [super setUp]; |
| [ChromeEarlGrey |
| resetDataForLocalStatePref:omnibox::kIsOmniboxInBottomPosition]; |
| |
| self.testServer->RegisterRequestHandler(base::BindRepeating( |
| &net::test_server::HandlePrefixedRequest, "/long-fullscreen", |
| base::BindRepeating(&GetLongResponseForFullscreen))); |
| |
| bool started = self.testServer->Start(); |
| GREYAssertTrue(started, @"Test server failed to start."); |
| } |
| |
| - (void)tearDownHelper { |
| [super tearDownHelper]; |
| [ChromeEarlGrey |
| resetDataForLocalStatePref:omnibox::kIsOmniboxInBottomPosition]; |
| } |
| |
| - (AppLaunchConfiguration)appConfigurationForTestCase { |
| AppLaunchConfiguration config; |
| |
| config.relaunch_policy = ForceRelaunchByCleanShutdown; |
| |
| if ([self isRunningTest:@selector(testOpenContextualPanelFromNormalIPH)]) { |
| config.features_enabled_and_params.push_back( |
| {kContextualPanel, {{{"entrypoint-rich-iph", "false"}}}}); |
| } else { |
| config.features_enabled_and_params.push_back({kContextualPanel, {}}); |
| } |
| |
| config.features_enabled_and_params.push_back( |
| {kContextualPanelForceShowEntrypoint, {}}); |
| |
| if ([self isRunningTest:@selector(testOpenContextualPanelFromNormalIPH)] || |
| [self isRunningTest:@selector(testOpenContextualPanelFromRichIPH)] || |
| [self isRunningTest:@selector(testOrientationChangeDismissesIPH)]) { |
| config.iph_feature_enabled = |
| feature_engagement::kIPHiOSContextualPanelSampleModelFeature.name; |
| } |
| |
| return config; |
| } |
| |
| // Tests that the contextual panel opens correctly. |
| - (void)testOpenContextualPanel { |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| [ChromeEarlGrey waitForUIElementToAppearWithMatcher: |
| ContextualPanelEntrypointImageViewMatcher()]; |
| |
| [[EarlGrey |
| selectElementWithMatcher:ContextualPanelEntrypointImageViewMatcher()] |
| performAction:grey_tap()]; |
| |
| // Check that the contextual panel opened up. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Close panel |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelCloseButtonAXID")] |
| performAction:grey_tap()]; |
| } |
| |
| // Tests that the contextual panel opens correctly when tapping the rich IPH. |
| - (void)testOpenContextualPanelFromRichIPH { |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| // Check that the IPH has appeared. |
| [ChromeEarlGrey |
| waitForUIElementToAppearWithMatcher:grey_accessibilityID( |
| @"BubbleViewLabelIdentifier")]; |
| |
| [[EarlGrey selectElementWithMatcher:grey_accessibilityID( |
| @"BubbleViewLabelIdentifier")] |
| performAction:grey_tap()]; |
| |
| // Check that the contextual panel opened up. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Close panel |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelCloseButtonAXID")] |
| performAction:grey_tap()]; |
| } |
| |
| // Tests that the contextual panel opens correctly when tapping the normal IPH. |
| - (void)testOpenContextualPanelFromNormalIPH { |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| // Check that the IPH has appeared. |
| [ChromeEarlGrey |
| waitForUIElementToAppearWithMatcher:grey_accessibilityID( |
| @"BubbleViewLabelIdentifier")]; |
| |
| [[EarlGrey selectElementWithMatcher:grey_accessibilityID( |
| @"BubbleViewLabelIdentifier")] |
| performAction:grey_tap()]; |
| |
| // Check that the contextual panel opened up. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Close panel |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelCloseButtonAXID")] |
| performAction:grey_tap()]; |
| } |
| |
| // Test that the Contextual Panel can still be closed after rotating to |
| // landscape. |
| - (void)testContextualPanelLandscape { |
| // This test is not relevant on iPads as iPads aren't compact height in |
| // landscape. |
| if ([ChromeEarlGrey isIPadIdiom]) { |
| EARL_GREY_TEST_SKIPPED(@"Skipped for iPad"); |
| } |
| |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| [ChromeEarlGrey waitForUIElementToAppearWithMatcher: |
| ContextualPanelEntrypointImageViewMatcher()]; |
| |
| [[EarlGrey |
| selectElementWithMatcher:ContextualPanelEntrypointImageViewMatcher()] |
| performAction:grey_tap()]; |
| |
| // Check that the contextual panel opened up. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Switch to landscape. |
| GREYAssert( |
| [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft |
| error:nil], |
| @"Could not rotate device to Landscape Left"); |
| |
| // Make sure that panel can still be closed. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelCloseButtonAXID")] |
| performAction:grey_tap()]; |
| } |
| |
| // Tests that closing the last tab with the panel open doesn't crash. |
| - (void)testCloseLastTabWithPanelOpen { |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| [ChromeEarlGrey waitForUIElementToAppearWithMatcher: |
| ContextualPanelEntrypointImageViewMatcher()]; |
| |
| [[EarlGrey |
| selectElementWithMatcher:ContextualPanelEntrypointImageViewMatcher()] |
| performAction:grey_tap()]; |
| |
| // Check that the contextual panel opened up. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Close the tab. |
| [ChromeEarlGrey closeTabAtIndex:0]; |
| } |
| |
| // Tests that closing the last tab before the large entrypoint callback is run |
| // doesn't crash. |
| - (void)testCloseLastTabBeforeLargeEntrypointAppears { |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| // Close the tab. |
| [ChromeEarlGrey closeTabAtIndex:0]; |
| } |
| |
| // Tests that the contextual panel transitions neatly between iOS sheet |
| // controller (full iPad layout) and the panel's custom sheet component (other |
| // window open/iPhone-style layout). |
| - (void)testContexutalPaneliPadMultiwindow { |
| if (@available(iOS 19.0, *)) { |
| // TODO(crbug.com/427699033): Re-enable test on iOS 26. |
| // Fails because it assumes a window will be compact after creating a new |
| // window. |
| EARL_GREY_TEST_DISABLED(@"Test disabled on iOS 26."); |
| } |
| |
| if (![ChromeEarlGrey areMultipleWindowsSupported]) { |
| EARL_GREY_TEST_DISABLED(@"Multiple windows can't be opened."); |
| } |
| |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| [ChromeEarlGrey waitForUIElementToAppearWithMatcher: |
| ContextualPanelEntrypointImageViewMatcher()]; |
| |
| [[EarlGrey |
| selectElementWithMatcher:ContextualPanelEntrypointImageViewMatcher()] |
| performAction:grey_tap()]; |
| |
| // Check that the contextual panel opened up. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Open a second window. |
| [ChromeEarlGrey openNewWindow]; |
| [ChromeEarlGrey waitUntilReadyWindowWithNumber:1]; |
| [ChromeEarlGrey waitForForegroundWindowCount:2]; |
| |
| // Check that the panel is still visible in the first window |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelCloseButtonAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Close the second window. |
| [ChromeEarlGrey closeWindowWithNumber:1]; |
| [ChromeEarlGrey waitForForegroundWindowCount:1]; |
| |
| // Check that the panel is still visible in the first window |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelCloseButtonAXID")] |
| performAction:grey_tap()]; |
| } |
| |
| // Tests that fullscreen is disabled when the omnibox switches to bottom |
| // position. |
| - (void)testBottomOmniboxDisablesFullscreen { |
| if (![ChromeEarlGrey isBottomOmniboxAvailable]) { |
| EARL_GREY_TEST_SKIPPED(@"Test requires bottom omnibox"); |
| } |
| |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/long-fullscreen")]; |
| |
| [ChromeEarlGrey waitForUIElementToAppearWithMatcher: |
| ContextualPanelEntrypointImageViewMatcher()]; |
| |
| [[EarlGrey |
| selectElementWithMatcher:ContextualPanelEntrypointImageViewMatcher()] |
| performAction:grey_tap()]; |
| |
| // Check that the contextual panel opened up. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Scroll up in the webpage to enter fullscreen |
| [[EarlGrey |
| selectElementWithMatcher:chrome_test_util::WebStateScrollViewMatcher()] |
| performAction:grey_swipeFastInDirection(kGREYDirectionUp)]; |
| [ChromeEarlGreyUI waitForToolbarVisible:NO]; |
| |
| // Enable bottom omnibox. |
| [ChromeEarlGrey setBoolValue:YES |
| forLocalStatePref:omnibox::kIsOmniboxInBottomPosition]; |
| [ChromeEarlGreyUI waitForToolbarVisible:YES]; |
| |
| // Make sure that panel can still be closed. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelCloseButtonAXID")] |
| performAction:grey_tap()]; |
| } |
| |
| // Test that the Contextual Panel entrypoint's large chip can be dismissed via |
| // swipe. |
| - (void)testContextualPanelEntrypointLargeChipDismissable { |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| // Wait for large chip entrypoint to appear. |
| [ChromeEarlGrey |
| waitForSufficientlyVisibleElementWithMatcher: |
| grey_accessibilityID(@"ContextualPanelEntrypointLabelAXID")]; |
| |
| // Side swipe on the entrypoint. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID( |
| @"ContextualPanelEntrypointLabelAXID")] |
| performAction:grey_swipeSlowInDirectionWithStartPoint(kGREYDirectionLeft, |
| 0.9, 0.5)]; |
| |
| // Check that the entrypoint is now back to default size. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID( |
| @"ContextualPanelEntrypointLabelAXID")] |
| assertWithMatcher:grey_notVisible()]; |
| } |
| |
| // Test that the Contextual Panel entrypoint IPH is dismissed when the device |
| // orientation changes. |
| - (void)testOrientationChangeDismissesIPH { |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/defaultresponse")]; |
| |
| // Check that the IPH has appeared. |
| [ChromeEarlGrey |
| waitForUIElementToAppearWithMatcher:grey_accessibilityID( |
| @"BubbleViewLabelIdentifier")]; |
| |
| // Switch to landscape. |
| GREYAssert( |
| [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft |
| error:nil], |
| @"Could not rotate device to Landscape Left"); |
| |
| // Check that the IPH has disappeared. |
| [ChromeEarlGrey |
| waitForUIElementToDisappearWithMatcher:grey_accessibilityID( |
| @"BubbleViewLabelIdentifier")]; |
| } |
| |
| // Tests that opening the keyboard on iPhone closes the panel. On iPad, the |
| // panel is presented modally, so the panel wouldn't close. |
| - (void)testKeyboardOpenClosesPanelOniPhone { |
| if ([ChromeEarlGrey isIPadIdiom]) { |
| EARL_GREY_TEST_SKIPPED(@"Test conditions don't happen on iPad."); |
| } |
| |
| // Open a page wth a text field. |
| [ChromeEarlGrey loadURL:self.testServer->GetURL("/simple_login_form.html")]; |
| |
| [ChromeEarlGrey waitForUIElementToAppearWithMatcher: |
| ContextualPanelEntrypointImageViewMatcher()]; |
| |
| [[EarlGrey |
| selectElementWithMatcher:ContextualPanelEntrypointImageViewMatcher()] |
| performAction:grey_tap()]; |
| |
| // Check that the contextual panel opened up. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_sufficientlyVisible()]; |
| |
| // Open the keyboard by tapping a text field. |
| [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()] |
| performAction:chrome_test_util::TapWebElementWithId("un")]; |
| |
| // Check that the contextual panel is closed. |
| [[EarlGrey |
| selectElementWithMatcher:grey_accessibilityID(@"PanelContentViewAXID")] |
| assertWithMatcher:grey_nil()]; |
| } |
| |
| @end |