// Copyright 2016 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/test/earl_grey/chrome_earl_grey_ui.h"
#import "base/ios/ios_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_constants.h"
#import "ios/chrome/browser/ui/popup_menu/popup_menu_constants.h"
#import "ios/chrome/grit/ios_strings.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/scoped_disable_timer_tracking.h"
#import "ios/chrome/test/scoped_eg_synchronization_disabler.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ui/base/l10n/l10n_util.h"
// Redefine EarlGrey macro to use line number and file name taken from the place
// of ChromeEarlGreyUI macro instantiation, rather than local line number
// inside test helper method. Original EarlGrey macro definition also expands to
// EarlGreyImpl instantiation. [self earlGrey] is provided by a superclass and
// returns EarlGreyImpl object created with correct line number and filename.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmacro-redefined"
#define EarlGrey [self earlGrey]
#pragma clang diagnostic pop
using base::test::ios::kWaitForUIElementTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using chrome_test_util::ButtonWithAccessibilityLabelId;
using chrome_test_util::ClearAutofillButton;
using chrome_test_util::ClearBrowsingDataButton;
using chrome_test_util::ClearBrowsingDataView;
using chrome_test_util::ClearSavedPasswordsButton;
using chrome_test_util::ConfirmClearBrowsingDataButton;
using chrome_test_util::SettingsActionButton;
using chrome_test_util::SettingsDestinationButton;
using chrome_test_util::SettingsMenuBackButton;
using chrome_test_util::ToolsMenuView;
namespace {
// Returns a GREYAction to scroll down (swipe up) for a reasonably small amount.
id<GREYAction> ScrollDown() {
// 150 is a reasonable value to ensure all menu items are seen, without too
// much delay. With a larger value, some menu items could be skipped while
// searching. A smaller value increses the area that is searched, but slows
// down the scroll.
CGFloat const kMenuScrollDisplacement = 150;
return grey_scrollInDirection(kGREYDirectionDown, kMenuScrollDisplacement);
}
// Returns a GREYAction to scroll down (swipe up) for a reasonably small amount.
id<GREYAction> PageSheetScrollDown() {
// 500 is a reasonable value to ensure all menu items are seen, and cause the
// page sheet to expand to full screen. With a larger value, some menu items
// could be skipped while searching. A smaller value increses the area that is
// searched, but slows down the scroll. It also causes the page sheet to not
// expand.
CGFloat menu_scroll_displacement = 500;
// But for very small devices (like the SE), this is too big.
UIWindow* currentWindow = chrome_test_util::GetAnyKeyWindow();
if (currentWindow.rootViewController.view.frame.size.height < 600)
menu_scroll_displacement = 250;
return grey_scrollInDirection(kGREYDirectionDown, menu_scroll_displacement);
}
// Returns a GREYAction to scroll right (swipe left) for a reasonably small
// amount.
id<GREYAction> ScrollRight() {
// 150 is a reasonable value to ensure all menu items are seen, without too
// much delay. With a larger value, some menu items could be skipped while
// searching. A smaller value increses the area that is searched, but slows
// down the scroll.
CGFloat const kMenuScrollDisplacement = 150;
return grey_scrollInDirection(kGREYDirectionRight, kMenuScrollDisplacement);
}
bool IsAppCompactWidth() {
UIUserInterfaceSizeClass sizeClass =
chrome_test_util::GetAnyKeyWindow().traitCollection.horizontalSizeClass;
return sizeClass == UIUserInterfaceSizeClassCompact;
}
// Maximum number of times `typeTextInOmnibox:andPressEnter:` will attempt to
// type the given text in the Omnibox. If it still cannot be typed properly
// after this number of attempts, `GREYAssert` is invoked.
const int kMaxNumberOfAttemptsAtTypingTextInOmnibox = 3;
} // namespace
@implementation ChromeEarlGreyUIImpl
- (void)openToolsMenu {
// TODO(crbug.com/41271107): Add logic to ensure the app is in the correct
// state, for example DCHECK if no tabs are displayed.
[ChromeEarlGrey waitForUIElementToAppearWithMatcher:
grey_allOf(chrome_test_util::ToolsMenuButton(),
grey_sufficientlyVisible(), nil)];
[[[EarlGrey
selectElementWithMatcher:grey_allOf(chrome_test_util::ToolsMenuButton(),
grey_sufficientlyVisible(), nil)]
usingSearchAction:grey_swipeSlowInDirection(kGREYDirectionDown)
onElementWithMatcher:chrome_test_util::WebStateScrollViewMatcher()]
performAction:grey_tap()];
// TODO(crbug.com/41271101): Add webViewScrollView matcher so we don't have
// to always find it.
}
- (void)closeToolsMenu {
if ([ChromeEarlGrey isNewOverflowMenuEnabled] &&
[ChromeEarlGrey isCompactWidth]) {
// With the new overflow menu on compact devices, the half sheet covers the
// bottom half of the screen. Swiping down on the sheet will close the menu.
[[EarlGrey selectElementWithMatcher:chrome_test_util::ToolsMenuView()]
performAction:grey_swipeFastInDirection(kGREYDirectionDown)];
// Sometimes the menu can be expanded to full height, so one swipe isn't
// enough to dismiss. If the menu is still visible, swipe one more time to
// guarantee closing.
NSError* error;
[[EarlGrey selectElementWithMatcher:chrome_test_util::ToolsMenuView()]
assertWithMatcher:grey_notVisible()
error:&error];
if (error) {
[[EarlGrey selectElementWithMatcher:chrome_test_util::ToolsMenuView()]
performAction:grey_swipeFastInDirection(kGREYDirectionDown)];
}
} else {
// A scrim covers the whole window and tapping on this scrim dismisses the
// tools menu. The "Tools Menu" button happens to be outside of the bounds
// of the menu and is a convenient place to tap to activate the scrim.
[[EarlGrey selectElementWithMatcher:chrome_test_util::ToolsMenuButton()]
performAction:grey_tap()];
}
}
- (void)openToolsMenuInWindowWithNumber:(int)windowNumber {
[EarlGrey setRootMatcherForSubsequentInteractions:
chrome_test_util::WindowWithNumber(windowNumber)];
// TODO(crbug.com/41271107): Add logic to ensure the app is in the correct
// state, for example DCHECK if no tabs are displayed.
[[[EarlGrey
selectElementWithMatcher:grey_allOf(chrome_test_util::ToolsMenuButton(),
grey_sufficientlyVisible(), nil)]
usingSearchAction:grey_swipeSlowInDirection(kGREYDirectionDown)
onElementWithMatcher:chrome_test_util::
WebStateScrollViewMatcherInWindowWithNumber(
windowNumber)] performAction:grey_tap()];
// TODO(crbug.com/41271101): Add webViewScrollView matcher so we don't have
// to always find it.
}
- (void)openSettingsMenu {
[self openToolsMenu];
if ([ChromeEarlGrey isNewOverflowMenuEnabled]) {
[self tapToolsMenuButton:SettingsDestinationButton()];
} else {
[self tapToolsMenuButton:SettingsActionButton()];
}
}
- (void)openSettingsMenuInWindowWithNumber:(int)windowNumber {
[self openToolsMenuInWindowWithNumber:windowNumber];
if ([ChromeEarlGrey isNewOverflowMenuEnabled]) {
[self tapToolsMenuButton:SettingsDestinationButton()];
} else {
[self tapToolsMenuButton:SettingsActionButton()];
}
}
- (void)openNewTabMenu {
// TODO(crbug.com/41271107): Add logic to ensure the app is in the correct
// state, for example DCHECK if no tabs are displayed.
[[[EarlGrey
selectElementWithMatcher:grey_allOf(chrome_test_util::NewTabButton(),
grey_sufficientlyVisible(), nil)]
usingSearchAction:grey_swipeSlowInDirection(kGREYDirectionDown)
onElementWithMatcher:chrome_test_util::WebStateScrollViewMatcher()]
performAction:grey_longPress()];
// TODO(crbug.com/41271101): Add webViewScrollView matcher so we don't have
// to always find it.
}
- (void)tapToolsMenuButton:(id<GREYMatcher>)buttonMatcher {
ScopedDisableTimerTracking disabler;
id<GREYMatcher> interactableSettingsButton =
grey_allOf(buttonMatcher, grey_interactable(), nil);
id<GREYAction> scrollAction =
[ChromeEarlGrey isNewOverflowMenuEnabled] ? ScrollRight() : ScrollDown();
[[[EarlGrey selectElementWithMatcher:interactableSettingsButton]
usingSearchAction:scrollAction
onElementWithMatcher:ToolsMenuView()] performAction:grey_tap()];
// TODO(crbug.com/347267212): On iOS18 tools menu taps will fail due to an
// apparent EG2 SwiftUI bug. Use XCUIapplication APIs instead here.
[self tapButtonUsingXCUIApplication:buttonMatcher];
}
- (void)tapToolsMenuAction:(id<GREYMatcher>)buttonMatcher {
if (![ChromeEarlGrey isNewOverflowMenuEnabled]) {
[self tapToolsMenuButton:buttonMatcher];
return;
}
ScopedDisableTimerTracking disabler;
id<GREYMatcher> interactableSettingsButton =
grey_allOf(buttonMatcher, grey_interactable(), nil);
[[[EarlGrey selectElementWithMatcher:interactableSettingsButton]
usingSearchAction:PageSheetScrollDown()
onElementWithMatcher:grey_accessibilityID(
kPopupMenuToolsMenuActionListId)]
performAction:grey_tap()];
// TODO(crbug.com/347647806): On iOS18 tools menu taps will fail due to an
// apparent EG2 SwiftUI bug. Use XCUIapplication APIs instead here.
[self tapButtonUsingXCUIApplication:buttonMatcher];
}
- (void)tapSettingsMenuButton:(id<GREYMatcher>)buttonMatcher {
ScopedDisableTimerTracking disabler;
id<GREYMatcher> interactableButtonMatcher =
grey_allOf(buttonMatcher, grey_interactable(), nil);
[[[EarlGrey selectElementWithMatcher:interactableButtonMatcher]
usingSearchAction:ScrollDown()
onElementWithMatcher:chrome_test_util::SettingsCollectionView()]
performAction:grey_tap()];
}
- (void)tapClearBrowsingDataMenuButton:(id<GREYMatcher>)buttonMatcher {
ScopedDisableTimerTracking disabler;
id<GREYMatcher> interactableButtonMatcher =
grey_allOf(buttonMatcher, grey_interactable(), nil);
[[[EarlGrey selectElementWithMatcher:interactableButtonMatcher]
usingSearchAction:ScrollDown()
onElementWithMatcher:ClearBrowsingDataView()] performAction:grey_tap()];
}
- (void)openAndClearBrowsingDataFromHistory {
// Open Clear Browsing Data Button
[[EarlGrey selectElementWithMatcher:chrome_test_util::
HistoryClearBrowsingDataButton()]
performAction:grey_tap()];
[self waitForClearBrowsingDataViewVisible:YES];
[self selectAllBrowsingDataAndClear];
// Include sufficientlyVisible condition for the case of the clear browsing
// dialog, which also has a "Done" button and is displayed over the history
// panel.
id<GREYMatcher> visibleDoneButton = grey_allOf(
chrome_test_util::SettingsDoneButton(), grey_sufficientlyVisible(), nil);
[[EarlGrey selectElementWithMatcher:visibleDoneButton]
performAction:grey_tap()];
}
- (void)clearAllBrowsingData {
// Open the "Clear Browsing Data" view by the privacy view.
[self openSettingsMenu];
[self tapSettingsMenuButton:chrome_test_util::SettingsMenuPrivacyButton()];
[self tapPrivacyMenuButton:chrome_test_util::ClearBrowsingDataCell()];
[self waitForClearBrowsingDataViewVisible:YES];
// Clear all data.
[self selectAllBrowsingDataAndClear];
// Close the "Clear Browsing Data" view.
id<GREYMatcher> visibleDoneButton = grey_allOf(
chrome_test_util::SettingsDoneButton(), grey_sufficientlyVisible(), nil);
[[EarlGrey selectElementWithMatcher:visibleDoneButton]
performAction:grey_tap()];
}
- (void)assertHistoryHasNoEntries {
// Make sure the empty state illustration, title and subtitle are present.
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(
kTableViewIllustratedEmptyViewID)]
assertWithMatcher:grey_notNil()];
id<GREYMatcher> noHistoryTitleMatcher =
grey_allOf(grey_text(l10n_util::GetNSString(IDS_IOS_HISTORY_EMPTY_TITLE)),
grey_sufficientlyVisible(), nil);
[[EarlGrey selectElementWithMatcher:noHistoryTitleMatcher]
assertWithMatcher:grey_notNil()];
id<GREYMatcher> noHistoryMessageMatcher = grey_allOf(
grey_text(l10n_util::GetNSString(IDS_IOS_HISTORY_EMPTY_MESSAGE)),
grey_sufficientlyVisible(), nil);
[[EarlGrey selectElementWithMatcher:noHistoryMessageMatcher]
assertWithMatcher:grey_notNil()];
// Make sure there are no history entry cells.
id<GREYMatcher> historyEntryMatcher =
grey_allOf(grey_kindOfClassName(@"TableViewURLCell"),
grey_sufficientlyVisible(), nil);
[[EarlGrey selectElementWithMatcher:historyEntryMatcher]
assertWithMatcher:grey_nil()];
}
- (void)tapPrivacyMenuButton:(id<GREYMatcher>)buttonMatcher {
ScopedDisableTimerTracking disabler;
id<GREYMatcher> interactableButtonMatcher =
grey_allOf(buttonMatcher, grey_interactable(), nil);
[[[EarlGrey selectElementWithMatcher:interactableButtonMatcher]
usingSearchAction:ScrollDown()
onElementWithMatcher:chrome_test_util::SettingsPrivacyTableView()]
performAction:grey_tap()];
}
- (void)tapPrivacySafeBrowsingMenuButton:(id<GREYMatcher>)buttonMatcher {
ScopedDisableTimerTracking disabler;
id<GREYMatcher> interactableButtonMatcher =
grey_allOf(buttonMatcher, grey_interactable(), nil);
[[[EarlGrey selectElementWithMatcher:interactableButtonMatcher]
usingSearchAction:ScrollDown()
onElementWithMatcher:chrome_test_util::
SettingsPrivacySafeBrowsingTableView()]
performAction:grey_tap()];
}
- (void)tapPriceNotificationsMenuButton:(id<GREYMatcher>)buttonMatcher {
ScopedDisableTimerTracking disabler;
id<GREYMatcher> interactableButtonMatcher =
grey_allOf(buttonMatcher, grey_interactable(), nil);
[[[EarlGrey selectElementWithMatcher:interactableButtonMatcher]
usingSearchAction:ScrollDown()
onElementWithMatcher:chrome_test_util::SettingsNotificationsTableView()]
performAction:grey_tap()];
}
- (void)tapTrackingPriceMenuButton:(id<GREYMatcher>)buttonMatcher {
ScopedDisableTimerTracking disabler;
id<GREYMatcher> interactableButtonMatcher =
grey_allOf(buttonMatcher, grey_interactable(), nil);
[[[EarlGrey selectElementWithMatcher:interactableButtonMatcher]
usingSearchAction:ScrollDown()
onElementWithMatcher:chrome_test_util::SettingsTrackingPriceTableView()]
performAction:grey_tap()];
}
- (void)tapAccountsMenuButton:(id<GREYMatcher>)buttonMatcher {
ScopedDisableTimerTracking disabler;
[[[EarlGrey selectElementWithMatcher:buttonMatcher]
usingSearchAction:ScrollDown()
onElementWithMatcher:chrome_test_util::SettingsAccountsCollectionView()]
performAction:grey_tap()];
}
- (void)focusOmniboxAndType:(NSString*)text {
[self focusOmnibox];
if (text.length) {
[[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
performAction:grey_typeText(text)];
}
}
- (void)focusOmniboxAndReplaceText:(NSString*)text {
[self focusOmnibox];
if (text.length) {
[[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
performAction:grey_replaceText(text)];
}
}
- (void)focusOmnibox {
[[EarlGrey selectElementWithMatcher:chrome_test_util::DefocusedLocationView()]
performAction:grey_tap()];
}
- (void)openNewTab {
[self openToolsMenu];
id<GREYMatcher> newTabButtonMatcher =
grey_accessibilityID(kToolsMenuNewTabId);
[[EarlGrey selectElementWithMatcher:newTabButtonMatcher]
performAction:grey_tap()];
[self waitForAppToIdle];
}
- (void)openNewIncognitoTab {
[self openToolsMenu];
id<GREYMatcher> newIncognitoTabMatcher =
grey_accessibilityID(kToolsMenuNewIncognitoTabId);
[[EarlGrey selectElementWithMatcher:newIncognitoTabMatcher]
performAction:grey_tap()];
[self waitForAppToIdle];
}
- (void)openTabGrid {
// Wait until the button is visible because other UI may still be animating
// and EarlGrey synchronization is disabled below which would prevent waiting.
ConditionBlock condition = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
assertWithMatcher:grey_sufficientlyVisible()
error:&error];
return error == nil;
};
bool tabGridButtonVisible = base::test::ios::WaitUntilConditionOrTimeout(
kWaitForUIElementTimeout, condition);
EG_TEST_HELPER_ASSERT_TRUE(tabGridButtonVisible,
@"Show tab grid button was not visible.");
// TODO(crbug.com/41442441) For an unknown reason synchronization doesn't work
// well with tapping on the tabgrid button, and instead triggers the long
// press gesture recognizer. Disable this here so the test can be re-enabled.
ScopedSynchronizationDisabler disabler;
[[EarlGrey selectElementWithMatcher:chrome_test_util::ShowTabsButton()]
performAction:grey_longPressWithDuration(base::Milliseconds(50))];
}
- (void)reload {
// On iPhone Reload button is a part of tools menu, so open it.
if (IsAppCompactWidth()) {
[self openToolsMenu];
}
[[EarlGrey selectElementWithMatcher:chrome_test_util::ReloadButton()]
performAction:grey_tap()];
}
- (void)openShareMenu {
[[EarlGrey selectElementWithMatcher:chrome_test_util::TabShareButton()]
performAction:grey_tap()];
}
- (void)waitForToolbarVisible:(BOOL)isVisible {
ConditionBlock condition = ^{
NSError* error = nil;
id<GREYMatcher> visibleMatcher = isVisible ? grey_notNil() : grey_nil();
[[EarlGrey selectElementWithMatcher:chrome_test_util::ToolsMenuButton()]
assertWithMatcher:visibleMatcher
error:&error];
return error == nil;
};
NSString* errorMessage =
isVisible ? @"Toolbar was not visible" : @"Toolbar was visible";
bool toolbarVisibility = base::test::ios::WaitUntilConditionOrTimeout(
kWaitForUIElementTimeout, condition);
EG_TEST_HELPER_ASSERT_TRUE(toolbarVisibility, errorMessage);
}
- (void)waitForAppToIdle {
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)openPageInfo {
[self openToolsMenu];
[self tapToolsMenuButton:chrome_test_util::SiteInfoDestinationButton()];
}
- (BOOL)dismissContextMenuIfPresent {
// There is no way to programmatically dismiss the native context menu from
// application side, so instead tap on the context menu container view if it
// exists.
NSError* err = nil;
[[EarlGrey
selectElementWithMatcher:grey_allOf(grey_kindOfClassName(
@"_UIContextMenuContainerView"),
grey_sufficientlyVisible(), nil)]
performAction:grey_tapAtPoint(CGPointMake(0, 0))
error:&err];
return err == nil;
}
- (void)cleanupAfterShowingAlert {
// Workaround for an Earl Grey crash in iOS 15.5 on iPad when traversing the
// view hierarchy with accessibility. Likely due to the system alert view, the
// traversal will crash because some system view cannot provide the correct
// accessibility result. Background and Foreground the app removes the system
// alert view's residues from the view hierarchy.
if (!base::ios::IsRunningOnIOS16OrLater()) {
[[AppLaunchManager sharedManager] backgroundAndForegroundApp];
}
}
- (void)dismissByTappingOnTheWindowOfPopover:(id<GREYMatcher>)matcher {
id<GREYMatcher> classMatcher = grey_kindOfClass([UIWindow class]);
id<GREYMatcher> parentMatcher = grey_descendant(matcher);
id<GREYMatcher> windowMatcher = grey_allOf(classMatcher, parentMatcher, nil);
// Tap on a point outside of the popover.
// The way EarlGrey taps doesn't go through the window hierarchy. Because of
// this, the tap needs to be done in the same window as the popover.
[[EarlGrey selectElementWithMatcher:windowMatcher]
performAction:grey_tapAtPoint(CGPointMake(0, 0))];
// Verify the window is not visible.
[[EarlGrey selectElementWithMatcher:windowMatcher]
assertWithMatcher:grey_notVisible()];
}
#pragma mark - Private
// Clears all browsing data from the device. This method needs to be called when
// the "Clear Browsing Data" panel is opened.
- (void)selectAllBrowsingDataAndClear {
// Check "Saved Passwords" and "Autofill Data" which are unchecked by
// default.
[ChromeEarlGrey
waitForSufficientlyVisibleElementWithMatcher:ClearSavedPasswordsButton()];
[[EarlGrey selectElementWithMatcher:ClearSavedPasswordsButton()]
performAction:grey_tap()];
[[[EarlGrey
selectElementWithMatcher:grey_allOf(ClearAutofillButton(),
grey_sufficientlyVisible(), nil)]
usingSearchAction:grey_swipeSlowInDirection(kGREYDirectionUp)
onElementWithMatcher:ClearBrowsingDataView()] performAction:grey_tap()];
// Set 'Time Range' to 'All Time'.
[[[EarlGrey
selectElementWithMatcher:
grey_allOf(ButtonWithAccessibilityLabelId(
IDS_IOS_CLEAR_BROWSING_DATA_TIME_RANGE_SELECTOR_TITLE),
grey_sufficientlyVisible(), nil)]
usingSearchAction:grey_swipeSlowInDirection(kGREYDirectionDown)
onElementWithMatcher:ClearBrowsingDataView()] performAction:grey_tap()];
[[EarlGrey
selectElementWithMatcher:
ButtonWithAccessibilityLabelId(
IDS_IOS_CLEAR_BROWSING_DATA_TIME_RANGE_OPTION_BEGINNING_OF_TIME)]
performAction:grey_tap()];
[[[EarlGrey selectElementWithMatcher:SettingsMenuBackButton()] atIndex:0]
performAction:grey_tap()];
// Clear data, and confirm.
[[EarlGrey selectElementWithMatcher:ClearBrowsingDataButton()]
performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:ConfirmClearBrowsingDataButton()]
performAction:grey_tap()];
// Wait until activity indicator modal is cleared, meaning clearing browsing
// data has been finished.
[self waitForAppToIdle];
// Recheck "Saved Passwords" and "Autofill Data".
[ChromeEarlGrey
waitForSufficientlyVisibleElementWithMatcher:ClearSavedPasswordsButton()];
[[EarlGrey selectElementWithMatcher:ClearSavedPasswordsButton()]
performAction:grey_tap()];
[[[EarlGrey
selectElementWithMatcher:grey_allOf(ClearAutofillButton(),
grey_sufficientlyVisible(), nil)]
usingSearchAction:grey_swipeSlowInDirection(kGREYDirectionUp)
onElementWithMatcher:ClearBrowsingDataView()] performAction:grey_tap()];
}
// Waits for the clear browsing data view to become visible if `isVisible` is
// YES, otherwise waits for it to disappear. If the condition is not met within
// a timeout, a GREYAssert is induced.
- (void)waitForClearBrowsingDataViewVisible:(BOOL)isVisible {
ConditionBlock condition = ^{
NSError* error = nil;
id<GREYMatcher> visibleMatcher =
isVisible ? grey_sufficientlyVisible() : grey_nil();
[[EarlGrey selectElementWithMatcher:ClearBrowsingDataView()]
assertWithMatcher:visibleMatcher
error:&error];
return error == nil;
};
NSString* errorMessage = isVisible
? @"Clear browsing data view was not visible"
: @"Clear browsing data view was visible";
bool clearBrowsingDataViewVisibility =
base::test::ios::WaitUntilConditionOrTimeout(kWaitForUIElementTimeout,
condition);
EG_TEST_HELPER_ASSERT_TRUE(clearBrowsingDataViewVisibility, errorMessage);
}
- (void)typeTextInOmnibox:(std::string const&)text
andPressEnter:(BOOL)shouldPressEnter {
BOOL textHasBeenTypedProperly = NO;
int numberOfAttemptsPerformed = 0;
while (!textHasBeenTypedProperly &&
numberOfAttemptsPerformed <
kMaxNumberOfAttemptsAtTypingTextInOmnibox) {
[ChromeEarlGreyUI focusOmnibox];
// Type the text.
[[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()]
performAction:grey_replaceText(base::SysUTF8ToNSString(text))];
numberOfAttemptsPerformed++;
// Check that the omnibox contains the typed text.
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(text)]
assertWithMatcher:grey_notNil()
error:&error];
textHasBeenTypedProperly = error == nil;
if (!textHasBeenTypedProperly &&
numberOfAttemptsPerformed < kMaxNumberOfAttemptsAtTypingTextInOmnibox) {
// Text has not been typed properly. Defocusing the omnibox so a new
// attempt is possible next round of loop.
if ([ChromeEarlGrey isIPadIdiom]) {
id<GREYMatcher> typingShield = grey_accessibilityID(@"Typing Shield");
[[EarlGrey selectElementWithMatcher:typingShield]
performAction:grey_tap()];
} else {
[[EarlGrey selectElementWithMatcher:grey_buttonTitle(@"Cancel")]
performAction:grey_tap()];
}
}
}
if (textHasBeenTypedProperly && shouldPressEnter) {
// Press enter to navigate.
// TODO(crbug.com/40916974): Use simulatePhysicalKeyboardEvent until
// replaceText can properly handle \n.
[ChromeEarlGrey simulatePhysicalKeyboardEvent:@"\n" flags:0];
}
// Assert the text has been typed properly.
GREYAssert(textHasBeenTypedProperly,
@"Failed to type '%s' in the Omnibox after %d attempts.",
text.c_str(), numberOfAttemptsPerformed);
}
// This is a temporary workarond to fix broken tests in iO18.
- (void)tapButtonUsingXCUIApplication:(id<GREYMatcher>)buttonMatcher {
if (@available(iOS 18, *)) {
NSError* error;
[[EarlGrey selectElementWithMatcher:buttonMatcher]
assertWithMatcher:grey_notNil()
error:&error];
if ([error.domain isEqual:kGREYInteractionErrorDomain] &&
error.code == kGREYInteractionElementNotFoundErrorCode) {
// If buttonMatcher is gone, that means it wasn't a SwiftUI element (or
// EG2 is fixed).
return;
}
// GREYMatcher's that match: accessibilityID('kToolsMenuBookmarksId'))
NSString* description = [buttonMatcher description];
NSRegularExpression* regex = [NSRegularExpression
regularExpressionWithPattern:@"accessibilityID\\('(.*?)'\\)"
options:NSRegularExpressionCaseInsensitive
error:nil];
NSTextCheckingResult* match =
[regex firstMatchInString:description
options:0
range:NSMakeRange(0, description.length)];
if (match) {
XCUIApplication* app = [[XCUIApplication alloc] init];
NSString* accessibilityID =
[description substringWithRange:[match rangeAtIndex:1]];
[app.buttons[accessibilityID] tap];
return;
}
// GREYMatcher's that match "starts with('kToolsMenuSettingsId')"
regex = [NSRegularExpression
regularExpressionWithPattern:@"starts with\\('(.*?)'\\)"
options:NSRegularExpressionCaseInsensitive
error:nil];
match = [regex firstMatchInString:description
options:0
range:NSMakeRange(0, description.length)];
if (match) {
NSString* prefix =
[description substringWithRange:[match rangeAtIndex:1]];
NSPredicate* predicate =
[NSPredicate predicateWithFormat:@"identifier BEGINSWITH %@", prefix];
XCUIApplication* app = [[XCUIApplication alloc] init];
XCUIElement* button = [app.buttons elementMatchingPredicate:predicate];
XCTAssertTrue(
button.exists,
@"Button with accessibility label starting with '%@' should exist",
prefix);
[button tap];
return;
}
XCTFail("SwiftUI/EG2 workaround missing GREYMatcher equivalent "
"NSRegularExpression.");
}
}
@end