chromium/ios/chrome/browser/find_in_page/model/find_in_page_egtest.mm

// 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/test/ios/wait_util.h"
#import "ios/chrome/browser/find_bar/ui_bundled/find_bar_constants.h"
#import "ios/chrome/browser/find_in_page/model/find_in_page_app_interface.h"
#import "ios/chrome/browser/find_in_page/model/find_in_page_egtest_util.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_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"

namespace {

// Constants to identify the Find navigator UI components in the view hierarchy.
constexpr char kFindInPageDoneButtonID[] = "find.doneButton";
constexpr char kFindInPageSearchFieldID[] = "find.searchField";
constexpr char kFindInPageResultLabelID[] = "find.resultLabel";
constexpr char kFindInPageNextButtonID[] = "find.nextButton";
constexpr char kFindInPagePreviousButtonID[] = "find.previousButton";
}  // namespace

// Tests for Native Find in Page. This tests the variant of Native Find in Page
// with a Find interaction i.e. with the system UI or Find navigator. Many tests
// use the `secondTestServer` to ensure what is being tested also works with
// cross-origin iframes.
@interface FindInPageTestCase
    : ChromeTestCase <FindInPageTestCaseHelperDelegate>

@end

@implementation FindInPageTestCase {
  FindInPageTestCaseHelper* _helper;
}

- (void)setUp {
  [super setUp];

  // Clear saved search term.
  [FindInPageAppInterface clearSearchTerm];

  // Creating helper.
  _helper = [[FindInPageTestCaseHelper alloc] init];
  _helper.testServer = self.testServer;
  _helper.delegate = self;
}

#pragma mark - FindInPageTestCaseHelperDelegate

- (void)openFindInPageWithOverflowMenu {
  [ChromeEarlGrey waitForKeyboardToDisappear];
  [ChromeEarlGreyUI openToolsMenu];

  id<GREYMatcher> tableViewMatcher =
      [ChromeEarlGrey isNewOverflowMenuEnabled]
          ? grey_accessibilityID(kPopupMenuToolsMenuActionListId)
          : grey_accessibilityID(kPopupMenuToolsMenuTableViewId);
  [[[EarlGrey
      selectElementWithMatcher:grey_allOf(
                                   grey_accessibilityID(kToolsMenuFindInPageId),
                                   grey_sufficientlyVisible(), nil)]
         usingSearchAction:grey_scrollInDirection(kGREYDirectionDown, 250)
      onElementWithMatcher:tableViewMatcher] performAction:grey_tap()];
  [ChromeEarlGrey
      waitForSufficientlyVisibleElementWithMatcher:[self findInPageInputField]];
  [ChromeEarlGreyUI waitForAppToIdle];
}

- (void)closeFindInPageWithDoneButton {
  [[EarlGrey
      selectElementWithMatcher:grey_accessibilityID(@(kFindInPageDoneButtonID))]
      performAction:grey_tap()];
}

- (void)replaceFindInPageText:(NSString*)text {
  [[EarlGrey selectElementWithMatcher:[self findInPageInputField]]
      performAction:grey_replaceText(text)];
}

- (void)pasteTextToFindInPage:(NSString*)text {
  [ChromeEarlGrey copyTextToPasteboard:text];
  [[EarlGrey selectElementWithMatcher:[self findInPageInputField]]
      performAction:grey_tap()];
  [[EarlGrey selectElementWithMatcher:PasteButton()] performAction:grey_tap()];
}

- (void)clearFindInPageText {
  [[EarlGrey selectElementWithMatcher:[self findInPageInputField]]
      performAction:grey_replaceText(@"")];
}

- (id<GREYMatcher>)findInPageInputField {
  return grey_accessibilityID(@(kFindInPageSearchFieldID));
}

- (void)assertResultStringIsResult:(int)resultIndex
                        outOfTotal:(int)resultCount {
  // Returns "<current> of <total>" search results label (e.g "1 of 5").
  NSString* expectedResultsString =
      [NSString stringWithFormat:@"%d of %d", resultIndex, resultCount];

  ConditionBlock condition = ^{
    NSError* error = nil;
    [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                            @(kFindInPageResultLabelID))]
        assertWithMatcher:chrome_test_util::StaticTextWithAccessibilityLabel(
                              expectedResultsString)
                    error:&error];
    return (error == nil);
  };
  GREYAssert(
      base::test::ios::WaitUntilConditionOrTimeout(base::Seconds(1), condition),
      @"Timeout waiting for correct Find in Page results string to appear");
}

- (void)assertResultStringIsEmptyOrZero {
  ConditionBlock condition = ^{
    NSError* error = nil;
    [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                            @(kFindInPageResultLabelID))]
        assertWithMatcher:
            grey_anyOf(chrome_test_util::StaticTextWithAccessibilityLabel(@"0"),
                       grey_notVisible(), nil)
                    error:&error];
    return (error == nil);
  };
  GREYAssert(
      base::test::ios::WaitUntilConditionOrTimeout(base::Seconds(1), condition),
      @"Timeout waiting for correct Find in Page results string to appear");
}

- (void)assertResultStringIsNonZero {
  ConditionBlock condition = ^{
    NSError* error = nil;
    [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                            @(kFindInPageResultLabelID))]
        assertWithMatcher:
            grey_not(grey_anyOf(
                chrome_test_util::StaticTextWithAccessibilityLabel(@"0"),
                grey_notVisible(), nil))
                    error:&error];
    return (error == nil);
  };
  GREYAssert(
      base::test::ios::WaitUntilConditionOrTimeout(base::Seconds(1), condition),
      @"Timeout waiting for correct Find in Page results string to appear");
}

- (void)advanceToNextResult {
  [[EarlGrey
      selectElementWithMatcher:grey_accessibilityID(@(kFindInPageNextButtonID))]
      performAction:grey_tap()];
}

- (void)advanceToPreviousResult {
  [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
                                          @(kFindInPagePreviousButtonID))]
      performAction:grey_tap()];
}

#pragma mark - Tests

// Tests that FIP can be opened with Overflow menu.
- (void)testFindInPageFromOverflowMenu {
  [_helper helperTestFindInPageFromOverflowMenu];
}

// Tests that characters appear in the search box and that results UI updates as
// each characters is entered/deleted.
- (void)testFindInPageTextInput {
  [_helper helperTestFindInPageTextInput];
}

// Tests that the number of results for a query accounts for all the matches
// across frames, here with a main frame and a cross-origin iframe.
- (void)testFindInPageSupportsCrossOriginFrame {
  [_helper helperTestFindInPageSupportsCrossOriginFrame];
}

// Tests that FIP can find different types of characters: special characters,
// number, strings with both letters and numbers as well as non-ASCII
// characters.
- (void)testFindInPageSpecialCharacters {
  [_helper helperTestFindInPageSpecialCharacters];
}

// Tests that text can be copied from the web page and pasted into the FIP input
// field and that the results UI updates accordingly.
- (void)testFindInPageCopyPaste {
  // TODO(crbug.com/360362288): Flaky on iOS 18 simulators.
#if TARGET_OS_SIMULATOR
  if (@available(iOS 18, *)) {
    EARL_GREY_TEST_DISABLED(@"Flaky on iOS 18 simulators.");
  }
#endif
  [_helper helperTestFindInPageCopyPaste];
}

// Tests that FIP yields no results for an empty search query.
- (void)testFindInPageEmptySearchQuery {
  [_helper helperTestFindInPageEmptySearchQuery];
}

// Tests that FIP yields no results for a non-empty query with no matches in the
// page.
- (void)testFindInPageQueryWithNoMatches {
  [_helper helperTestFindInPageQueryWithNoMatches];
}

// Tests that FIP yields no matches for a text with spanish accents e.g. 'รก' if
// the web page contains the same text without spanish accents e.g. 'a'. This
// test assumes removing accents from `kFindInPageTestWithSpanishAccentText`
// yields `kFindInPageTestWithoutSpanishAccentText`.
// TODO(crbug.com/40926974): Test is flaky on device. Re-enable the test.
#if !TARGET_OS_SIMULATOR
#define MAYBE_testFindInPageDifferentAccent FLAKY_testFindInPageDifferentAccent
#else
#define MAYBE_testFindInPageDifferentAccent testFindInPageDifferentAccent
#endif
- (void)MAYBE_testFindInPageDifferentAccent {
  [_helper helperTestFindInPageDifferentAccent];
}

// Test that there is no query persistence with this variant of Native Find in
// Page i.e. with Find interaction.
- (void)testFindInPageHistory {
  [_helper helperTestFindInPageHistoryWithQueryPersistence:NO];
}

// Tests that there is no query persistence from an non-Incognito to an
// Incognito tab.
- (void)testFindInPageNormalToIncognito {
  [_helper helperTestFindInPageNormalToIncognito];
}

// Tests that Next/Previous buttons work and wrap.
- (void)testFindInPageNextPreviousArrows {
  [_helper helperTestFindInPageNextPreviousArrows];
}

// Tests the various ways to dismiss the keyboard during a Find session.
// TODO(crbug.com/40283787): Test fails on downstream bots.
- (void)DISABLED_testFindInPageDismissKeyboard {
  [_helper helperTestFindInPageDismissKeyboard];
}

// Tests that FIP can find long strings of characters.
- (void)testFindInPageLongString {
  [_helper helperTestFindInPageLongString];
}

// Tests that FIP is not case sensitive.
- (void)testFindInPageNotCaseSensitive {
  [_helper helperTestFindInPageNotCaseSensitive];
}

// Tests that there is no leak of the FIP search query from Incognito tabs to
// normal tabs.
- (void)testFindInPageIncognitoHistory {
  [_helper helperTestFindInPageIncognitoHistory];
}

// Tests that there is no query persistence when coming back to a normal tab
// after switching temporarily to another tab.
// TODO(crbug.com/40940589): Re-enable this test.
- (void)FLAKY_testFindInPageSwitchingTabs {
  // TODO(crbug.com/40922941): Failing on iOS17 iPhone.
  if (@available(iOS 17.0, *)) {
    if (![ChromeEarlGrey isIPadIdiom]) {
      XCTSkip(@"Failing on iOS17 iPhone");
    }
  }

  [_helper helperTestFindInPageSwitchingTabsWithQueryPersistence:NO];
}

// Tests that FIP can find RTL text in a web page.
- (void)testFindInPageRTL {
  [_helper helperTestFindInPageRTL];
}

// Tests that Find in Page can find matches in an Incognito tab.
- (void)testFindInPageIncognito {
  [_helper helperTestFindInPageIncognito];
}

// Tests accessibility of the Find in Page screen.
- (void)testFindInPageAccessibility {
  [_helper helperTestFindInPageAccessibility];
}

// Tests that Native Find in Page works as expected for PDF documents.
- (void)testFindInPagePDF {
// TODO(crbug.com/40926974): Failing on devices.
#if !TARGET_IPHONE_SIMULATOR
  XCTSkip(@"Failing on device");
#endif

  [_helper helperTestFindInPagePDF];
}

// Tests that FIP exit fullscreen when done.
- (void)testWhenFullscreenIsDisable {
  AppLaunchConfiguration config = self.appConfigurationForTestCase;
  config.features_enabled.push_back(kDisableFullscreenScrolling);
  // Relaunch the app to take the configuration into account.
  [[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];

  [_helper helperTestFindInPageExitFullscreen];
}

@end