chromium/ios/chrome/browser/find_in_page/model/find_in_page_egtest_util.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 "ios/chrome/browser/find_in_page/model/find_in_page_egtest_util.h"

#import <sstream>

#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_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_xcui_actions.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/web/public/test/element_selector.h"
#import "net/test/embedded_test_server/embedded_test_server.h"

namespace {

// Returns the test content for different test cases.
std::string FindInPageTestContent() {
  std::ostringstream oss;
  oss << "<div>";
  oss << "Text that repeats: " << kFindInPageTestRepeatingText
      << kFindInPageTestRepeatingText << "</p>";
  oss << "  <p id=\"" << kFindInPageTestShortTextID << "\">"
      << kFindInPageTestShortText << "</p>";
  oss << "  <p>" << kFindInPageTestLongText << "</p>";
  oss << "  <p>Special characters: " << kFindInPageTestSpecialCharactersText
      << "</p>";
  oss << "  <p>Numbers: " << kFindInPageTestNumbersText << "</p>";
  oss << "  <p>Alphanumeric text: " << kFindInPageTestAlphanumericText
      << "</p>";
  oss << "  <p>Non-ASCII text: " << kFindInPageTestNonASCIIText << "</p>";
  oss << "  <p>Text without spanish accent: "
      << kFindInPageTestWithoutSpanishAccentText << "</p>";
  oss << "  <p>Case sensitivity: " << kFindInPageTestLowercaseAndUppercaseText
      << "</p>";
  oss << "  <p dir=\"RTL\">" << kFindInPageTestRTLText << "</p>";
  oss << "  <div>";
  oss << "</div>";
  return oss.str();
}

// Response handler that serves a test page for Find in Page.
std::unique_ptr<net::test_server::HttpResponse> FindInPageTestPageHttpResponse(
    const net::test_server::HttpRequest& request) {
  if (request.relative_url != kFindInPageTestURL) {
    return nullptr;
  }
  std::unique_ptr<net::test_server::BasicHttpResponse> http_response =
      std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HTTP_OK);
  http_response->set_content(
      "<html><head><meta charset=\"UTF-8\"></head><body>" +
      FindInPageTestContent() + "</body></html>");
  return std::move(http_response);
}

// Response handler that serves a test page with a cross-origin iframe for Find
// in Page. `sourceURL` is used as `src` for the iframe.
std::unique_ptr<net::test_server::HttpResponse>
FindInPageTestCrossOriginFramePageHttpResponse(
    const GURL& sourceURL,
    const net::test_server::HttpRequest& request) {
  if (request.relative_url != kFindInPageCrossOriginFrameTestURL) {
    return nullptr;
  }
  std::unique_ptr<net::test_server::BasicHttpResponse> http_response =
      std::make_unique<net::test_server::BasicHttpResponse>();
  http_response->set_code(net::HTTP_OK);
  http_response->set_content(
      "<html><head><meta charset=\"UTF-8\"></head><body>" +
      FindInPageTestContent() + "<iframe src=\"" + sourceURL.spec() +
      "\"></iframe></body></html>");
  return std::move(http_response);
}

// Long presses on `element_id` to trigger context menu.
void LongPressElement(const char* element_id) {
  [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
      performAction:chrome_test_util::LongPressElementForContextMenu(
                        [ElementSelector selectorWithElementID:element_id],
                        true /* menu should appear */)];
}

}  // namespace

const char kFindInPageTestRepeatingText[] = "repeating";
const char kFindInPageTestShortTextID[] = "shortText";
const char kFindInPageTestShortText[] = "ShortQuery";
const char kFindInPageTestLongText[] =
    "This is a particularly long string with a great number of characters";
const char kFindInPageTestSpecialCharactersText[] = "!@#$%^&*()_+";
const char kFindInPageTestNumbersText[] = "1234567890";
const char kFindInPageTestAlphanumericText[] = "f00bar";
const char kFindInPageTestNonASCIIText[] = "大家好🦑";
const char kFindInPageTestWithSpanishAccentText[] = "á";
const char kFindInPageTestWithoutSpanishAccentText[] = "a";
const char kFindInPageTestLowercaseAndUppercaseText[] =
    "ThIs tExT Is bOtH UpPeRcAsE AnD LoWeRcAsE";
const char kFindInPageTestRTLText[] = "He said \"שלם\" (shalom] to me.";

const char kFindInPageTestURL[] = "/findinpage.html";
const char kFindInPageCrossOriginFrameTestURL[] = "/crossorigin.html";
const char kFindInPageComplexPDFTestURL[] = "/complex_document.pdf";

id<GREYMatcher> PasteButton() {
  NSString* a11yLabelPaste = @"Paste";
  return grey_allOf(grey_accessibilityLabel(a11yLabelPaste),
                    chrome_test_util::SystemSelectionCallout(), nil);
}

@implementation FindInPageTestCaseHelper {
  // Second test server for cross-origin iframe tests.
  std::unique_ptr<net::test_server::EmbeddedTestServer> _secondTestServer;
}

- (void)setUpTestServersForWebPageTest {
  // Set up first server to test Find in Page content.
  self.testServer->RegisterRequestHandler(
      base::BindRepeating(&FindInPageTestPageHttpResponse));
  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");

  // Set up second server for cross-origin iframe tests.
  GURL sourceURLForFrame = self.testServer->GetURL(kFindInPageTestURL);
  _secondTestServer = std::make_unique<net::test_server::EmbeddedTestServer>();
  [self secondTestServer]->RegisterRequestHandler(base::BindRepeating(
      &FindInPageTestCrossOriginFramePageHttpResponse, sourceURLForFrame));
  GREYAssertTrue([self secondTestServer]->Start(),
                 @"Second test server serving page with iframe did not start.");
}

- (net::test_server::EmbeddedTestServer*)secondTestServer {
  return _secondTestServer.get();
}

- (void)setUpTestServerForPDFTest {
  // This is sufficient to ensure `ios/testing/data/http_server_files/` file
  // system directory is being served, as this is the default configuration.
  GREYAssertTrue(self.testServer->Start(), @"Server did not start.");
}

- (id<GREYMatcher>)matcherForText:(NSString*)text {
  NSString* prefix = @"hasText";
  GREYMatchesBlock matchesBlock = ^BOOL(id element) {
    return [[element text] isEqualToString:text];
  };

  GREYDescribeToBlock describeToBlock = ^void(id<GREYDescription> description) {
    [description
        appendText:[NSString stringWithFormat:@"%@('%@')", prefix, text]];
  };
  // A matcher for non-SwiftUI elements
  id<GREYMatcher> matcher =
      [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matchesBlock
                                           descriptionBlock:describeToBlock];
  return matcher;
}

// Tests that FIP can be opened with Overflow menu.
- (void)helperTestFindInPageFromOverflowMenu {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page.
    GURL destinationURL = self.testServer->GetURL(kFindInPageTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP with Overflow menu and check it is visible.
    [self.delegate openFindInPageWithOverflowMenu];
    [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                        [self.delegate findInPageInputField]];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that characters appear in the search box and that results UI updates as
// each characters is entered/deleted.
- (void)helperTestFindInPageTextInput {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page.
    GURL destinationURL = self.testServer->GetURL(kFindInPageTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP.
    [self.delegate openFindInPageWithOverflowMenu];
    // Test the result string is empty or "0".
    [self.delegate assertResultStringIsEmptyOrZero];

    [self.delegate replaceFindInPageText:@(kFindInPageTestRepeatingText)];
    // Test the input field contains the text that was just typed.
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self
                              matcherForText:@(kFindInPageTestRepeatingText)]];
    // Test the result UI is updated accordingly.
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];

    [self.delegate
        replaceFindInPageText:
            [NSString stringWithFormat:@"%s%s", kFindInPageTestRepeatingText,
                                       kFindInPageTestRepeatingText]];
    [self.delegate assertResultStringIsResult:1 outOfTotal:1];

    [self.delegate
        replaceFindInPageText:
            [NSString stringWithFormat:@"%s%s%s", kFindInPageTestRepeatingText,
                                       kFindInPageTestRepeatingText,
                                       kFindInPageTestRepeatingText]];
    [self.delegate assertResultStringIsEmptyOrZero];

    [self.delegate clearFindInPageText];
    [self.delegate assertResultStringIsEmptyOrZero];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// 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)helperTestFindInPageSupportsCrossOriginFrame {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate assertResultStringIsEmptyOrZero];

    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];
    // Tests there are two matches: one is in the main frame, the other in the
    // cross-origin iframe.
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];

    [self.delegate advanceToNextResult];
    // Tests that the second match can be navigated to.
    [self.delegate assertResultStringIsResult:2 outOfTotal:2];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// 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)helperTestFindInPageSpecialCharacters {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate assertResultStringIsEmptyOrZero];

    // Tests special characters.
    [self.delegate
        replaceFindInPageText:@(kFindInPageTestSpecialCharactersText)];
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];

    // Tests numbers.
    [self.delegate replaceFindInPageText:@(kFindInPageTestNumbersText)];
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];

    // Tests alphanumeric values.
    [self.delegate replaceFindInPageText:@(kFindInPageTestAlphanumericText)];
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];

    // Tests non-ASCII characters.
    [self.delegate replaceFindInPageText:@(kFindInPageTestNonASCIIText)];
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// 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)helperTestFindInPageCopyPaste {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page.
    GURL destinationURL = self.testServer->GetURL(kFindInPageTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Select and copy text on the web page.
    LongPressElement(kFindInPageTestShortTextID);

    [[EarlGrey
        selectElementWithMatcher:
            grey_allOf(chrome_test_util::SystemSelectionCalloutCopyButton(),
                       grey_sufficientlyVisible(), nil)]
        performAction:grey_tap()];

    // Open FIP.
    [self.delegate openFindInPageWithOverflowMenu];

    // Paste content of pasteboard in the FIP text field.
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        performAction:grey_tap()];
    [[EarlGrey selectElementWithMatcher:PasteButton()]
        performAction:grey_tap()];

    // Tests that the number of results is updated accordingly.
    [self.delegate assertResultStringIsResult:1 outOfTotal:1];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that FIP yields no results for an empty search query.
- (void)helperTestFindInPageEmptySearchQuery {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP.
    [self.delegate openFindInPageWithOverflowMenu];

    // Assert that searching text from the page yields results.
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];
    [self.delegate assertResultStringIsNonZero];

    // Test that the number of results is zero after clearing the FIP text
    // field.
    [self.delegate clearFindInPageText];
    [self.delegate assertResultStringIsEmptyOrZero];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that FIP yields no results for a non-empty query with no matches in the
// page.
- (void)helperTestFindInPageQueryWithNoMatches {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and type text that is not in the page.
    const char* queryWithNoMatches =
        "example query which should not match with the content of the page";
    [ChromeEarlGrey waitForWebStateNotContainingText:queryWithNoMatches];
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(queryWithNoMatches)];
    // Test the result label shows no results.
    [self.delegate assertResultStringIsEmptyOrZero];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// 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`.
- (void)helperTestFindInPageDifferentAccent {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Assert the text without accent is there but the text with accents does
    // not match.
    [ChromeEarlGrey
        waitForWebStateContainingText:kFindInPageTestWithoutSpanishAccentText];
    [ChromeEarlGrey
        waitForWebStateNotContainingText:kFindInPageTestWithSpanishAccentText];

    // Open FIP and assert that text with no accents yields matches.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate
        replaceFindInPageText:@(kFindInPageTestWithoutSpanishAccentText)];
    [self.delegate assertResultStringIsNonZero];

    // Replace the text without spanish accent with the same text with spanish
    // accents and test that there are no more matches.
    [self.delegate
        replaceFindInPageText:@(kFindInPageTestWithSpanishAccentText)];
    [self.delegate assertResultStringIsEmptyOrZero];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Test query persistence in the same tab and in a new normal tab.
- (void)helperTestFindInPageHistoryWithQueryPersistence:(BOOL)queryPersistence {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and test the input field is empty.
    [self.delegate openFindInPageWithOverflowMenu];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:@""]];

    // Type a query and assert it is contained in the input field before closing
    // FIP.
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:@(kFindInPageTestShortText)]];
    [self.delegate closeFindInPageWithDoneButton];

    // Open FIP again and test depending on query persistence.
    [self.delegate openFindInPageWithOverflowMenu];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:queryPersistence
                                                   ? @(kFindInPageTestShortText)
                                                   : @""]];
    [self.delegate closeFindInPageWithDoneButton];

    // Open the same URL in a different non-Incognito tab.
    [ChromeEarlGrey openNewTab];
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP in this new tab and test depending on query persistence.
    [self.delegate openFindInPageWithOverflowMenu];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:queryPersistence
                                                   ? @(kFindInPageTestShortText)
                                                   : @""]];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that there is no query persistence from an non-Incognito to an
// Incognito tab.
- (void)helperTestFindInPageNormalToIncognito {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and type short query.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];
    [self.delegate closeFindInPageWithDoneButton];

    // Load same URL in a new Incognito tab.
    [ChromeEarlGrey openNewIncognitoTab];
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and test the input field is empty.
    [self.delegate openFindInPageWithOverflowMenu];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:@""]];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that switching orientation during a Find session does not throw away
// the query or the current results.
- (void)helperTestFindInPageSwitchOrientation {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP, type short query, move to second match and wait for expected
    // results.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];
    [self.delegate advanceToNextResult];
    [self.delegate assertResultStringIsResult:2 outOfTotal:2];

    // Switch to landscape.
    GREYAssert(
        [EarlGrey rotateDeviceToOrientation:UIDeviceOrientationLandscapeLeft
                                      error:nil],
        @"Could not rotate device to Landscape Left");

    // Test the query is still there will the same result.
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:@(kFindInPageTestShortText)]];
    [self.delegate assertResultStringIsResult:2 outOfTotal:2];

    // Switch back to portrait.
    GREYAssert([EarlGrey rotateDeviceToOrientation:UIDeviceOrientationPortrait
                                             error:nil],
               @"Could not rotate device to Portrait");

    // Test the query is still there will the same result.
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:@(kFindInPageTestShortText)]];
    [self.delegate assertResultStringIsResult:2 outOfTotal:2];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that Next/Previous buttons work and wrap.
- (void)helperTestFindInPageNextPreviousArrows {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and type query with four expected matches.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestRepeatingText)];
    [self.delegate assertResultStringIsResult:1 outOfTotal:4];

    // Test that tapping "Next" button works and wraps.
    [self.delegate advanceToNextResult];
    [self.delegate assertResultStringIsResult:2 outOfTotal:4];
    [self.delegate advanceToNextResult];
    [self.delegate assertResultStringIsResult:3 outOfTotal:4];
    [self.delegate advanceToNextResult];
    [self.delegate assertResultStringIsResult:4 outOfTotal:4];
    [self.delegate advanceToNextResult];
    [self.delegate assertResultStringIsResult:1 outOfTotal:4];

    // Test that tapping "Previous" button also works and wraps.
    [self.delegate advanceToPreviousResult];
    [self.delegate assertResultStringIsResult:4 outOfTotal:4];
    [self.delegate advanceToPreviousResult];
    [self.delegate assertResultStringIsResult:3 outOfTotal:4];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests the various ways to dismiss the keyboard during a Find session.
- (void)helperTestFindInPageDismissKeyboard {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and type short query.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];

    // Tap Done button and test the keyboard is dismissed as a result.
    [self.delegate closeFindInPageWithDoneButton];
    [ChromeEarlGrey waitForKeyboardToDisappear];

    // Open FIP and type short query again.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];

    // Tap an element on the page and test the keyboard is dismissed as a
    // result.
    [[EarlGrey selectElementWithMatcher:chrome_test_util::WebViewMatcher()]
        performAction:chrome_test_util::TapWebElementWithId(
                          kFindInPageTestShortTextID)];
    [ChromeEarlGrey waitForKeyboardToDisappear];
  }
}

// Tests that FIP can find long strings of characters.
- (void)helperTestFindInPageLongString {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and type short query.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestLongText)];

    // Test the number of results is as expected.
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that FIP is not case sensitive.
- (void)helperTestFindInPageNotCaseSensitive {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Assert the page contains string that is both lowercase and uppercase.
    [ChromeEarlGrey
        waitForWebStateContainingText:kFindInPageTestLowercaseAndUppercaseText];

    // Open FIP and type lowercase version of contained text.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate
        replaceFindInPageText:[@(kFindInPageTestLowercaseAndUppercaseText)
                                  lowercaseString]];
    // Test the number of results is as expected.
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];

    // Clear input field and type uppercase version of contained text.
    [self.delegate
        replaceFindInPageText:[@(kFindInPageTestLowercaseAndUppercaseText)
                                  uppercaseString]];
    // Test the number of results is as expected.
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that there is no leak of the FIP search query from Incognito tabs to
// normal tabs.
- (void)helperTestFindInPageIncognitoHistory {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe in new Incognito tab.
    [ChromeEarlGrey openNewIncognitoTab];
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and type short query.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];
    [self.delegate closeFindInPageWithDoneButton];

    // Open a new normal tab and load the same URL.
    [ChromeEarlGrey openNewTab];
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP again and test the input field is empty.
    [self.delegate openFindInPageWithOverflowMenu];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:@""]];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests query persistence when coming back to a normal tab after switching
// temporarily to another tab.
- (void)helperTestFindInPageSwitchingTabsWithQueryPersistence:
    (BOOL)queryPersistence {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe in a second normal tab.
    [ChromeEarlGrey openNewTab];
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and type short query.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];

    // Switching to first tab and then back to second tab.
    [ChromeEarlGrey selectTabAtIndex:0];
    [ChromeEarlGrey selectTabAtIndex:1];

    // Test query persistence.
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:queryPersistence
                                                   ? @(kFindInPageTestShortText)
                                                   : @""]];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that FIP can find RTL text in a web page.
- (void)helperTestFindInPageRTL {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP, type RTL text and test that the results are as expected.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate pasteTextToFindInPage:@(kFindInPageTestRTLText)];
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that Find in Page can find matches in an Incognito tab.
- (void)helperTestFindInPageIncognito {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe in a new Incognito tab.
    [ChromeEarlGrey openNewIncognitoTab];
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP, type text contained in test page and test that the results are
    // as expected.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests accessibility of the Find in Page screen.
- (void)helperTestFindInPageAccessibility {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page with cross-origin iframe.
    GURL destinationURL =
        [self secondTestServer]->GetURL(kFindInPageCrossOriginFrameTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP, type query and check the expected number of results.
    [self.delegate openFindInPageWithOverflowMenu];
    [self.delegate replaceFindInPageText:@(kFindInPageTestShortText)];
    [self.delegate assertResultStringIsResult:1 outOfTotal:2];

    // Test accessibility.
    [ChromeEarlGrey verifyAccessibilityForCurrentScreen];
    [self.delegate closeFindInPageWithDoneButton];
  }
}

// Tests that Native Find in Page works as expected for PDF documents.
- (void)helperTestFindInPagePDF {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServerForPDFTest];

    // Load test PDF document.
    GURL destinationURL = self.testServer->GetURL(kFindInPageComplexPDFTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Open FIP and test that the input field is empty and there are no results.
    [self.delegate openFindInPageWithOverflowMenu];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:[self matcherForText:@""]];
    [self.delegate assertResultStringIsEmptyOrZero];

    // Type text with 18 expected matches and test that results are as
    // expected.
    [self.delegate replaceFindInPageText:@"the F"];
    [self.delegate assertResultStringIsResult:1 outOfTotal:18];

    // Test that the Next button works.
    [self.delegate advanceToNextResult];
    [self.delegate assertResultStringIsResult:2 outOfTotal:18];
    [self.delegate advanceToNextResult];
    [self.delegate assertResultStringIsResult:3 outOfTotal:18];

    // Type more specific query and test that results are as expected.
    [self.delegate replaceFindInPageText:@"the Form"];
    [self.delegate assertResultStringIsResult:1 outOfTotal:6];

    // Test that the Previous button works and wraps.
    [self.delegate advanceToPreviousResult];
    [self.delegate assertResultStringIsResult:6 outOfTotal:6];
    [self.delegate advanceToPreviousResult];
    [self.delegate assertResultStringIsResult:5 outOfTotal:6];

    // Type even more specific query and test that results are as expected.
    [self.delegate replaceFindInPageText:@"the Form 1050"];
    [self.delegate assertResultStringIsEmptyOrZero];

    // Test that the Done button does close Find in Page.
    [self.delegate closeFindInPageWithDoneButton];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:grey_notVisible()];
  }
}

// Tests that FIP exit fullscreen when done.
- (void)helperTestFindInPageExitFullscreen {
  if (@available(iOS 16.1.1, *)) {
    [self setUpTestServersForWebPageTest];

    // Load test page.
    GURL destinationURL = self.testServer->GetURL(kFindInPageComplexPDFTestURL);
    [ChromeEarlGrey loadURL:destinationURL];

    // Ensure the toolbars are not in fullscreen mode by checking if share
    // button is visible.
    [[EarlGrey selectElementWithMatcher:chrome_test_util::TabShareButton()]
        assertWithMatcher:grey_sufficientlyVisible()];

    // Open FIP with Overflow menu and check it is visible and the share button
    // is not visible.
    [self.delegate openFindInPageWithOverflowMenu];
    [ChromeEarlGrey waitForSufficientlyVisibleElementWithMatcher:
                        [self.delegate findInPageInputField]];

    [ChromeEarlGrey waitForUIElementToDisappearWithMatcher:
                        chrome_test_util::TabShareButton()];

    // Close find in page with Done button and ensure the share button is
    // visible again.
    [self.delegate closeFindInPageWithDoneButton];
    [[EarlGrey selectElementWithMatcher:[self.delegate findInPageInputField]]
        assertWithMatcher:grey_notVisible()];
    [[EarlGrey selectElementWithMatcher:chrome_test_util::TabShareButton()]
        assertWithMatcher:grey_sufficientlyVisible()];
  }
}

@end