// 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.h"
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#import "base/apple/foundation_util.h"
#import "base/format_macros.h"
#import "base/json/json_string_value_serializer.h"
#import "base/logging.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case_app_interface.h"
#import "ios/chrome/test/scoped_eg_synchronization_disabler.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#import "ios/testing/earl_grey/system_alert_handler.h"
#import "ios/testing/nserror_util.h"
#import "ios/web/public/test/element_selector.h"
#import "net/base/apple/url_conversions.h"
using base::test::ios::kWaitForActionTimeout;
using base::test::ios::kWaitForJSCompletionTimeout;
using base::test::ios::kWaitForPageLoadTimeout;
using base::test::ios::kWaitForUIElementTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
using chrome_test_util::ActivityViewHeader;
using chrome_test_util::CopyLinkButton;
using chrome_test_util::OpenLinkInIncognitoButton;
using chrome_test_util::OpenLinkInNewTabButton;
using chrome_test_util::OpenLinkInNewWindowButton;
using chrome_test_util::ShareButton;
namespace {
// Accessibility ID of the Activity menu.
NSString* kActivityMenuIdentifier = @"ActivityListView";
NSString* const kWaitForPageToStartLoadingError = @"Page did not start to load";
NSString* const kWaitForPageToFinishLoadingError =
@"Page did not finish loading";
NSString* const kHistoryError = @"Error occurred during history verification.";
NSString* const kWaitForRestoreSessionToFinishError =
@"Session restoration did not finish";
// Helper class to allow EarlGrey to match elements with isAccessible=N.
class ScopedMatchNonAccessibilityElements {
public:
ScopedMatchNonAccessibilityElements() {
original_value_ = GREY_CONFIG_BOOL(kGREYConfigKeyIgnoreIsAccessible);
[[GREYConfiguration sharedConfiguration]
setValue:@YES
forConfigKey:kGREYConfigKeyIgnoreIsAccessible];
}
~ScopedMatchNonAccessibilityElements() {
[[GREYConfiguration sharedConfiguration]
setValue:[NSNumber numberWithBool:original_value_]
forConfigKey:kGREYConfigKeyIgnoreIsAccessible];
}
private:
BOOL original_value_;
};
} // namespace
namespace chrome_test_util {
UIWindow* GetAnyKeyWindow() {
// Only one or zero foreground scene should be available if this is called.
NSSet<UIScene*>* scenes =
[GREY_REMOTE_CLASS_IN_APP(UIApplication) sharedApplication]
.connectedScenes;
int foregroundScenes = 0;
for (UIScene* scene in scenes) {
if (scene.activationState == UISceneActivationStateForegroundInactive ||
scene.activationState == UISceneActivationStateForegroundActive) {
foregroundScenes++;
}
}
DCHECK(foregroundScenes <= 1);
return [ChromeEarlGreyAppInterface keyWindow];
}
} // namespace chrome_test_util
id<GREYAction> grey_longPressWithDuration(base::TimeDelta duration) {
return grey_longPressWithDuration(duration.InSecondsF());
}
@interface ChromeEarlGreyImpl ()
// Waits for session restoration to finish within a timeout, or a GREYAssert is
// induced.
- (void)waitForRestoreSessionToFinish;
@end
@implementation ChromeEarlGreyImpl
#pragma mark - Test Utilities
- (void)startReloading {
[ChromeEarlGreyAppInterface startReloading];
}
- (void)waitForMatcher:(id<GREYMatcher>)matcher {
ConditionBlock condition = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_notNil()
error:&error];
return error == nil;
};
NSString* errorString =
[NSString stringWithFormat:@"Waiting for matcher %@ failed.", matcher];
EG_TEST_HELPER_ASSERT_TRUE(
base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForUIElementTimeout, condition),
errorString);
}
#pragma mark - Device Utilities
- (BOOL)isIPadIdiom {
UIUserInterfaceIdiom idiom =
[[GREY_REMOTE_CLASS_IN_APP(UIDevice) currentDevice] userInterfaceIdiom];
return idiom == UIUserInterfaceIdiomPad;
}
- (BOOL)isIPhoneIdiom {
UIUserInterfaceIdiom idiom =
[[GREY_REMOTE_CLASS_IN_APP(UIDevice) currentDevice] userInterfaceIdiom];
return idiom == UIUserInterfaceIdiomPhone;
}
- (BOOL)isRTL {
return [ChromeEarlGreyAppInterface isRTL];
}
- (BOOL)isCompactWidth {
UIUserInterfaceSizeClass horizontalSpace =
[[chrome_test_util::GetAnyKeyWindow() traitCollection]
horizontalSizeClass];
return horizontalSpace == UIUserInterfaceSizeClassCompact;
}
- (BOOL)isCompactHeight {
UIUserInterfaceSizeClass verticalSpace =
[[chrome_test_util::GetAnyKeyWindow() traitCollection] verticalSizeClass];
return verticalSpace == UIUserInterfaceSizeClassCompact;
}
- (BOOL)isSplitToolbarMode {
return [self isCompactWidth] && ![self isCompactHeight];
}
- (BOOL)isRegularXRegularSizeClass {
UITraitCollection* traitCollection =
[chrome_test_util::GetAnyKeyWindow() traitCollection];
return traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular &&
traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular;
}
- (void)primesStopLogging {
[ChromeEarlGreyAppInterface primesStopLogging];
}
- (void)primesTakeMemorySnapshot:(NSString*)eventName {
[ChromeEarlGreyAppInterface primesTakeMemorySnapshot:eventName];
}
- (BOOL)isBottomOmniboxAvailable {
return self.isIPhoneIdiom;
}
- (BOOL)isCurrentLayoutBottomOmnibox {
return [ChromeEarlGreyAppInterface isCurrentLayoutBottomOmnibox];
}
- (BOOL)isEnhancedSafeBrowsingInfobarEnabled {
return [ChromeEarlGreyAppInterface isEnhancedSafeBrowsingInfobarEnabled];
}
#pragma mark - History Utilities (EG2)
- (void)clearBrowsingHistory {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface clearBrowsingHistory]);
// After clearing browsing history via code, wait for the UI to be done
// with any updates. This includes icons from the new tab page being removed.
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)killWebKitNetworkProcess {
[ChromeEarlGreyAppInterface killWebKitNetworkProcess];
}
- (NSInteger)browsingHistoryEntryCount {
NSError* error = nil;
NSInteger result =
[ChromeEarlGreyAppInterface browsingHistoryEntryCountWithError:&error];
EG_TEST_HELPER_ASSERT_NO_ERROR(error);
return result;
}
- (NSInteger)navigationBackListItemsCount {
return [ChromeEarlGreyAppInterface navigationBackListItemsCount];
}
- (void)removeBrowsingCache {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface removeBrowsingCache]);
}
- (void)saveSessionImmediately {
[ChromeEarlGreyAppInterface saveSessionImmediately];
}
#pragma mark - Navigation Utilities (EG2)
- (void)goBack {
[ChromeEarlGreyAppInterface startGoingBack];
[self waitForPageToFinishLoading];
}
- (void)goForward {
[ChromeEarlGreyAppInterface startGoingForward];
[self waitForPageToFinishLoading];
}
- (void)reload {
[self reloadAndWaitForCompletion:YES];
}
- (void)reloadAndWaitForCompletion:(BOOL)wait {
[ChromeEarlGreyAppInterface startReloading];
if (wait) {
[self waitForPageToFinishLoading];
}
}
- (void)openURLFromExternalApp:(const GURL&)URL {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
[ChromeEarlGreyAppInterface openURLFromExternalApp:spec];
}
- (void)dismissSettings {
[ChromeEarlGreyAppInterface dismissSettings];
}
#pragma mark - Tab Utilities (EG2)
- (void)selectTabAtIndex:(NSUInteger)index {
[ChromeEarlGreyAppInterface selectTabAtIndex:index];
// Tab changes are initiated through `WebStateList`. Need to wait its
// obeservers to complete UI changes at app.
GREYWaitForAppToIdle(@"App failed to idle");
}
- (BOOL)isIncognitoMode {
return [ChromeEarlGreyAppInterface isIncognitoMode];
}
- (void)closeTabAtIndex:(NSUInteger)index {
[ChromeEarlGreyAppInterface closeTabAtIndex:index];
// Tab changes are initiated through `WebStateList`. Need to wait its
// obeservers to complete UI changes at app.
GREYWaitForAppToIdle(@"App failed to idle");
}
- (NSUInteger)mainTabCount {
return [ChromeEarlGreyAppInterface mainTabCount];
}
- (NSUInteger)inactiveTabCount {
return [ChromeEarlGreyAppInterface inactiveTabCount];
}
- (NSUInteger)incognitoTabCount {
return [ChromeEarlGreyAppInterface incognitoTabCount];
}
- (NSUInteger)browserCount {
return [ChromeEarlGreyAppInterface browserCount];
}
- (NSInteger)realizedWebStatesCount {
return [ChromeEarlGreyAppInterface realizedWebStatesCount];
}
- (NSUInteger)evictedMainTabCount {
return [ChromeEarlGreyAppInterface evictedMainTabCount];
}
- (void)evictOtherBrowserTabs {
[ChromeEarlGreyAppInterface evictOtherBrowserTabs];
}
- (void)simulateTabsBackgrounding {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface simulateTabsBackgrounding]);
}
- (void)setCurrentTabsToBeColdStartTabs {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface setCurrentTabsToBeColdStartTabs]);
}
- (void)resetTabUsageRecorder {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface resetTabUsageRecorder]);
}
- (void)openNewTab {
[ChromeEarlGreyAppInterface openNewTab];
[self waitForPageToFinishLoading];
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)simulateExternalAppURLOpeningWithURL:(NSURL*)url {
[ChromeEarlGreyAppInterface simulateExternalAppURLOpeningWithURL:url];
}
- (void)simulateExternalAppURLOpeningAndWaitUntilOpenedWithGURL:(GURL)url {
[ChromeEarlGreyAppInterface
simulateExternalAppURLOpeningWithURL:net::NSURLWithGURL(url)];
// Wait until the navigation is finished.
GREYCondition* finishedLoading = [GREYCondition
conditionWithName:kWaitForPageToStartLoadingError
block:^{
return url == [ChromeEarlGrey webStateVisibleURL];
}];
bool pageLoaded =
[finishedLoading waitWithTimeout:kWaitForPageLoadTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(pageLoaded, kWaitForPageToStartLoadingError);
// Wait until the page is loaded.
[self waitForPageToFinishLoading];
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)simulateAddAccountFromWeb {
[ChromeEarlGreyAppInterface simulateAddAccountFromWeb];
[self waitForPageToFinishLoading];
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)closeCurrentTab {
[ChromeEarlGreyAppInterface closeCurrentTab];
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)pinCurrentTab {
[ChromeEarlGreyAppInterface pinCurrentTab];
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)openNewIncognitoTab {
[ChromeEarlGreyAppInterface openNewIncognitoTab];
[self waitForPageToFinishLoading];
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)closeAllTabsInCurrentMode {
[ChromeEarlGreyAppInterface closeAllTabsInCurrentMode];
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)closeAllNormalTabs {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface closeAllNormalTabs]);
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)closeAllIncognitoTabs {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface closeAllIncognitoTabs]);
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)closeAllTabs {
[ChromeEarlGreyAppInterface closeAllTabs];
// Tab changes are initiated through `WebStateList`. Need to wait its
// obeservers to complete UI changes at app.
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)waitForPageToFinishLoading {
GREYCondition* finishedLoading = [GREYCondition
conditionWithName:kWaitForPageToFinishLoadingError
block:^{
return ![ChromeEarlGreyAppInterface isLoading];
}];
BOOL pageLoaded =
[finishedLoading waitWithTimeout:kWaitForPageLoadTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(pageLoaded, kWaitForPageToFinishLoadingError);
}
- (void)sceneOpenURL:(const GURL&)URL {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
[ChromeEarlGreyAppInterface sceneOpenURL:spec];
}
- (void)loadURL:(const GURL&)URL waitForCompletion:(BOOL)wait {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
[ChromeEarlGreyAppInterface startLoadingURL:spec];
if (wait) {
[self waitForWebStateVisible];
[self waitForPageToFinishLoading];
// Loading URL (especially the first time) can trigger alerts.
[SystemAlertHandler handleSystemAlertIfVisible];
}
}
- (void)loadURL:(const GURL&)URL {
return [self loadURL:URL waitForCompletion:YES];
}
- (BOOL)isLoading {
return [ChromeEarlGreyAppInterface isLoading];
}
- (void)waitForSufficientlyVisibleElementWithMatcher:(id<GREYMatcher>)matcher {
NSString* errorDescription = [NSString
stringWithFormat:
@"Failed waiting for element with matcher %@ to become visible",
matcher];
GREYCondition* waitForElement = [GREYCondition
conditionWithName:errorDescription
block:^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher]
assertWithMatcher:grey_sufficientlyVisible()
error:&error];
return error == nil;
}];
bool matchedElement =
[waitForElement waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(matchedElement, errorDescription);
}
- (void)waitForNotSufficientlyVisibleElementWithMatcher:
(id<GREYMatcher>)matcher {
NSString* errorDescription = [NSString
stringWithFormat:
@"Failed waiting for element with matcher %@ to become not visible",
matcher];
GREYCondition* waitForElement = [GREYCondition
conditionWithName:errorDescription
block:^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher]
assertWithMatcher:grey_sufficientlyVisible()
error:&error];
return error != nil;
}];
bool matchedElement =
[waitForElement waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(matchedElement, errorDescription);
}
- (void)waitForUIElementToAppearWithMatcher:(id<GREYMatcher>)matcher {
[self waitForUIElementToAppearWithMatcher:matcher
timeout:kWaitForUIElementTimeout];
}
- (BOOL)testUIElementAppearanceWithMatcher:(id<GREYMatcher>)matcher {
return [self testUIElementAppearanceWithMatcher:matcher
timeout:kWaitForUIElementTimeout];
}
- (void)waitForUIElementToAppearWithMatcher:(id<GREYMatcher>)matcher
timeout:(base::TimeDelta)timeout {
NSString* errorDescription = [NSString
stringWithFormat:@"Failed waiting for element with matcher %@ to appear",
matcher];
bool matched = [self testUIElementAppearanceWithMatcher:matcher
timeout:timeout];
EG_TEST_HELPER_ASSERT_TRUE(matched, errorDescription);
}
- (BOOL)testUIElementAppearanceWithMatcher:(id<GREYMatcher>)matcher
timeout:(base::TimeDelta)timeout {
ConditionBlock condition = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_notNil()
error:&error];
return error == nil;
};
return WaitUntilConditionOrTimeout(timeout, condition);
}
- (void)waitForUIElementToDisappearWithMatcher:(id<GREYMatcher>)matcher {
[self waitForUIElementToDisappearWithMatcher:matcher
timeout:kWaitForUIElementTimeout];
}
- (void)waitForUIElementToDisappearWithMatcher:(id<GREYMatcher>)matcher
timeout:(base::TimeDelta)timeout {
NSString* errorDescription = [NSString
stringWithFormat:
@"Failed waiting for element with matcher %@ to disappear", matcher];
ConditionBlock condition = ^{
NSError* error = nil;
[[EarlGrey selectElementWithMatcher:matcher] assertWithMatcher:grey_nil()
error:&error];
return error == nil;
};
bool matched = WaitUntilConditionOrTimeout(timeout, condition);
EG_TEST_HELPER_ASSERT_TRUE(matched, errorDescription);
}
- (NSString*)currentTabTitle {
return [ChromeEarlGreyAppInterface currentTabTitle];
}
- (NSString*)nextTabTitle {
return [ChromeEarlGreyAppInterface nextTabTitle];
}
- (NSString*)currentTabID {
return [ChromeEarlGreyAppInterface currentTabID];
}
- (NSString*)nextTabID {
return [ChromeEarlGreyAppInterface nextTabID];
}
- (void)waitForAndTapButton:(id<GREYMatcher>)button {
NSString* errorDescription =
[NSString stringWithFormat:@"Waiting to tap on button %@", button];
// Perform a tap with a timeout. Occasionally EG doesn't sync up properly to
// the animations of tab switcher, so it is necessary to poll here.
// TODO(crbug.com/40672916): Fix the underlying issue in EarlGrey and remove
// this workaround.
GREYCondition* tapButton =
[GREYCondition conditionWithName:errorDescription
block:^BOOL {
NSError* error;
[[EarlGrey selectElementWithMatcher:button]
performAction:grey_tap()
error:&error];
return error == nil;
}];
// Wait for the tap.
BOOL hasClicked =
[tapButton waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(hasClicked, errorDescription);
}
- (void)showTabSwitcher {
[ChromeEarlGrey waitForAndTapButton:chrome_test_util::ShowTabsButton()];
}
#pragma mark - Cookie Utilities (EG2)
- (NSDictionary*)cookies {
NSString* const kGetCookiesScript =
@"document.cookie ? document.cookie.split(/;\\s*/) : [];";
base::Value result = [self evaluateJavaScript:kGetCookiesScript];
if (!result.is_list()) {
EG_TEST_HELPER_ASSERT_TRUE(
false,
@"The script response is not iterable. Cookies can not be retrieved.");
return nil;
}
NSMutableDictionary* cookies = [NSMutableDictionary dictionary];
for (const auto& option : result.GetList()) {
if (option.is_string()) {
NSString* nameValuePair = base::SysUTF8ToNSString(option.GetString());
NSMutableArray* cookieNameValue =
[[nameValuePair componentsSeparatedByString:@"="] mutableCopy];
// For cookies with multiple parameters it may be valid to have multiple
// occurrences of the delimiter.
EG_TEST_HELPER_ASSERT_TRUE((2 <= cookieNameValue.count),
@"Cookie has invalid format.");
NSString* cookieName = cookieNameValue[0];
[cookieNameValue removeObjectAtIndex:0];
NSString* cookieValue = [cookieNameValue componentsJoinedByString:@"="];
cookies[cookieName] = cookieValue;
}
}
return cookies;
}
#pragma mark - WebState Utilities (EG2)
- (void)tapWebStateElementWithID:(NSString*)elementID {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface tapWebStateElementWithID:elementID]);
}
- (void)tapWebStateElementInIFrameWithID:(const std::string&)elementID {
NSString* NSElementID = base::SysUTF8ToNSString(elementID);
EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface
tapWebStateElementInIFrameWithID:NSElementID]);
}
- (void)waitForWebStateContainingElement:(ElementSelector*)selector {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface waitForWebStateContainingElement:selector]);
}
- (void)waitForWebStateNotContainingElement:(ElementSelector*)selector {
EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface
waitForWebStateNotContainingElement:selector]);
}
- (void)waitForMainTabCount:(NSUInteger)count {
__block NSUInteger actualCount = [ChromeEarlGreyAppInterface mainTabCount];
NSString* conditionName = [NSString
stringWithFormat:@"Waiting for main tab count to become %" PRIuNS, count];
// Allow the UI to become idle, in case any tabs are being opened or closed.
GREYWaitForAppToIdle(@"App failed to idle");
GREYCondition* tabCountCheck = [GREYCondition
conditionWithName:conditionName
block:^{
actualCount = [ChromeEarlGreyAppInterface mainTabCount];
return actualCount == count;
}];
bool tabCountEqual =
[tabCountCheck waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
NSString* errorString = [NSString
stringWithFormat:@"Failed waiting for main tab count to become %" PRIuNS
"; actual count: %" PRIuNS,
count, actualCount];
EG_TEST_HELPER_ASSERT_TRUE(tabCountEqual, errorString);
}
- (void)waitForInactiveTabCount:(NSUInteger)count {
NSString* errorString = [NSString
stringWithFormat:
@"Failed waiting for inactive tab count to become %" PRIuNS, count];
// Allow the UI to become idle, in case any tabs are being opened or closed.
GREYWaitForAppToIdle(@"App failed to idle");
GREYCondition* tabCountCheck = [GREYCondition
conditionWithName:errorString
block:^{
return
[ChromeEarlGreyAppInterface inactiveTabCount] == count;
}];
bool tabCountEqual =
[tabCountCheck waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(tabCountEqual, errorString);
}
- (void)waitForIncognitoTabCount:(NSUInteger)count {
NSString* errorString = [NSString
stringWithFormat:
@"Failed waiting for incognito tab count to become %" PRIuNS, count];
// Allow the UI to become idle, in case any tabs are being opened or closed.
GREYWaitForAppToIdle(@"App failed to idle");
GREYCondition* tabCountCheck = [GREYCondition
conditionWithName:errorString
block:^{
return
[ChromeEarlGreyAppInterface incognitoTabCount] == count;
}];
bool tabCountEqual =
[tabCountCheck waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(tabCountEqual, errorString);
}
- (NSUInteger)indexOfActiveNormalTab {
return [ChromeEarlGreyAppInterface indexOfActiveNormalTab];
}
- (void)waitForRestoreSessionToFinish {
GREYCondition* finishedRestoreSession = [GREYCondition
conditionWithName:kWaitForRestoreSessionToFinishError
block:^{
return !
[ChromeEarlGreyAppInterface isRestoreSessionInProgress];
}];
bool restoreSessionCompleted = [finishedRestoreSession
waitWithTimeout:kWaitForPageLoadTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(restoreSessionCompleted,
kWaitForRestoreSessionToFinishError);
}
- (void)submitWebStateFormWithID:(const std::string&)UTF8FormID {
NSString* formID = base::SysUTF8ToNSString(UTF8FormID);
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface submitWebStateFormWithID:formID]);
}
- (BOOL)webStateContainsElement:(ElementSelector*)selector {
return [ChromeEarlGreyAppInterface webStateContainsElement:selector];
}
- (void)waitForWebStateVisible {
NSString* errorString =
[NSString stringWithFormat:@"Failed waiting for web state to be visible"];
GREYCondition* waitForWebState = [GREYCondition
conditionWithName:errorString
block:^{
NSError* error;
[[EarlGrey selectElementWithMatcher:chrome_test_util::
WebViewMatcher()]
assertWithMatcher:grey_notNil()
error:&error];
return error == nil;
}];
bool containsWebState =
[waitForWebState waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(containsWebState, errorString);
}
- (void)waitForWebStateContainingText:(const std::string&)UTF8Text {
[self waitForWebStateContainingText:UTF8Text timeout:kWaitForPageLoadTimeout];
}
- (void)waitForWebStateFrameContainingText:(const std::string&)UTF8Text {
NSString* text = base::SysUTF8ToNSString(UTF8Text);
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface waitForWebStateContainingTextInIFrame:text]);
}
- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
timeout:(base::TimeDelta)timeout {
NSString* text = base::SysUTF8ToNSString(UTF8Text);
NSString* errorString = [NSString
stringWithFormat:@"Failed waiting for web state containing %@", text];
GREYCondition* waitForText = [GREYCondition
conditionWithName:errorString
block:^{
return
[ChromeEarlGreyAppInterface webStateContainsText:text];
}];
bool containsText = [waitForText waitWithTimeout:timeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(containsText, errorString);
}
- (void)waitForWebStateNotContainingText:(const std::string&)UTF8Text {
NSString* text = base::SysUTF8ToNSString(UTF8Text);
NSString* errorString = [NSString
stringWithFormat:@"Failed waiting for web state not containing %@", text];
GREYCondition* waitForText = [GREYCondition
conditionWithName:errorString
block:^{
return !
[ChromeEarlGreyAppInterface webStateContainsText:text];
}];
bool containsText =
[waitForText waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(containsText, errorString);
}
- (void)waitForWebStateContainingBlockedImageElementWithID:
(const std::string&)UTF8ImageID {
NSString* imageID = base::SysUTF8ToNSString(UTF8ImageID);
EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface
waitForWebStateContainingBlockedImage:imageID]);
}
- (void)waitForWebStateContainingLoadedImageElementWithID:
(const std::string&)UTF8ImageID {
NSString* imageID = base::SysUTF8ToNSString(UTF8ImageID);
EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface
waitForWebStateContainingLoadedImage:imageID]);
}
- (void)waitForWebStateZoomScale:(CGFloat)scale {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface waitForWebStateZoomScale:scale]);
}
- (GURL)webStateVisibleURL {
return GURL(
base::SysNSStringToUTF8([ChromeEarlGreyAppInterface webStateVisibleURL]));
}
- (GURL)webStateLastCommittedURL {
return GURL(base::SysNSStringToUTF8(
[ChromeEarlGreyAppInterface webStateLastCommittedURL]));
}
- (void)purgeCachedWebViewPages {
[ChromeEarlGreyAppInterface purgeCachedWebViewPages];
[self waitForRestoreSessionToFinish];
[self waitForPageToFinishLoading];
}
- (BOOL)webStateWebViewUsesContentInset {
return [ChromeEarlGreyAppInterface webStateWebViewUsesContentInset];
}
- (CGSize)webStateWebViewSize {
return [ChromeEarlGreyAppInterface webStateWebViewSize];
}
- (void)stopAllWebStatesLoading {
[ChromeEarlGreyAppInterface stopAllWebStatesLoading];
// Wait for any UI change.
GREYWaitForAppToIdle(
@"Failed to wait app to idle after stopping all WebStates");
}
- (void)clearAllWebStateBrowsingData:(AppLaunchConfiguration)config {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface clearAllWebStateBrowsingData]);
// The app must be relaunched to rebuild internal //ios/web state after
// clearing browsing data with `[ChromeEarlGreyAppInterface
// clearAllWebStateBrowsingData]`.
config.relaunch_policy = ForceRelaunchByKilling;
[[AppLaunchManager sharedManager] ensureAppLaunchedWithConfiguration:config];
}
#pragma mark - Sync Utilities (EG2)
// Whether or not the fake sync server has been setup.
- (BOOL)isFakeSyncServerSetUp {
return [ChromeEarlGreyAppInterface isFakeSyncServerSetUp];
}
- (void)disconnectFakeSyncServerNetwork {
[ChromeEarlGreyAppInterface disconnectFakeSyncServerNetwork];
}
- (void)connectFakeSyncServerNetwork {
[ChromeEarlGreyAppInterface connectFakeSyncServerNetwork];
}
- (void)
addUserDemographicsToSyncServerWithBirthYear:(int)rawBirthYear
gender:
(metrics::UserDemographicsProto::
Gender)gender {
[ChromeEarlGreyAppInterface
addUserDemographicsToSyncServerWithBirthYear:rawBirthYear
gender:gender];
}
- (void)clearAutofillProfileWithGUID:(const std::string&)UTF8GUID {
NSString* GUID = base::SysUTF8ToNSString(UTF8GUID);
[ChromeEarlGreyAppInterface clearAutofillProfileWithGUID:GUID];
}
- (void)addAutofillProfileToFakeSyncServerWithGUID:(const std::string&)UTF8GUID
autofillProfileName:
(const std::string&)UTF8FullName {
NSString* GUID = base::SysUTF8ToNSString(UTF8GUID);
NSString* fullName = base::SysUTF8ToNSString(UTF8FullName);
[ChromeEarlGreyAppInterface
addAutofillProfileToFakeSyncServerWithGUID:GUID
autofillProfileName:fullName];
}
- (BOOL)isAutofillProfilePresentWithGUID:(const std::string&)UTF8GUID
autofillProfileName:(const std::string&)UTF8FullName {
NSString* GUID = base::SysUTF8ToNSString(UTF8GUID);
NSString* fullName = base::SysUTF8ToNSString(UTF8FullName);
return [ChromeEarlGreyAppInterface isAutofillProfilePresentWithGUID:GUID
autofillProfileName:fullName];
}
- (void)setUpFakeSyncServer {
[ChromeEarlGreyAppInterface setUpFakeSyncServer];
}
- (void)tearDownFakeSyncServer {
[ChromeEarlGreyAppInterface tearDownFakeSyncServer];
}
- (void)clearFakeSyncServerData {
[ChromeEarlGreyAppInterface clearFakeSyncServerData];
}
- (void)flushFakeSyncServerToDisk {
[ChromeEarlGreyAppInterface flushFakeSyncServerToDisk];
}
- (int)numberOfSyncEntitiesWithType:(syncer::DataType)type {
return [ChromeEarlGreyAppInterface numberOfSyncEntitiesWithType:type];
}
- (void)addFakeSyncServerBookmarkWithURL:(const GURL&)URL
title:(const std::string&)UTF8Title {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
NSString* title = base::SysUTF8ToNSString(UTF8Title);
[ChromeEarlGreyAppInterface addFakeSyncServerBookmarkWithURL:spec
title:title];
}
- (void)addFakeSyncServerDeviceInfo:(NSString*)deviceName
lastUpdatedTimestamp:(base::Time)lastUpdatedTimestamp {
[ChromeEarlGreyAppInterface addFakeSyncServerDeviceInfo:deviceName
lastUpdatedTimestamp:lastUpdatedTimestamp];
}
- (void)addFakeSyncServerLegacyBookmarkWithURL:(const GURL&)URL
title:(const std::string&)UTF8Title
originator_client_item_id:
(const std::string&)UTF8OriginatorClientItemId {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
NSString* title = base::SysUTF8ToNSString(UTF8Title);
NSString* originator_client_item_id =
base::SysUTF8ToNSString(UTF8OriginatorClientItemId);
[ChromeEarlGreyAppInterface
addFakeSyncServerLegacyBookmarkWithURL:spec
title:title
originator_client_item_id:originator_client_item_id];
}
- (void)addFakeSyncServerHistoryVisit:(const GURL&)URL {
[ChromeEarlGreyAppInterface
addFakeSyncServerHistoryVisit:net::NSURLWithGURL(URL)];
}
- (void)addHistoryServiceTypedURL:(const GURL&)URL {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
[ChromeEarlGreyAppInterface addHistoryServiceTypedURL:spec];
}
- (void)addHistoryServiceTypedURL:(const GURL&)URL
visitTimestamp:(base::Time)visitTimestamp {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
[ChromeEarlGreyAppInterface addHistoryServiceTypedURL:spec
visitTimestamp:visitTimestamp];
}
- (void)deleteHistoryServiceTypedURL:(const GURL&)URL {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
[ChromeEarlGreyAppInterface deleteHistoryServiceTypedURL:spec];
}
- (void)waitForHistoryURL:(const GURL&)URL
expectPresent:(BOOL)expectPresent
timeout:(base::TimeDelta)timeout {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
GREYCondition* waitForURL = [GREYCondition
conditionWithName:kHistoryError
block:^{
return [ChromeEarlGreyAppInterface isURL:spec
presentOnClient:expectPresent];
}];
bool success = [waitForURL waitWithTimeout:timeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(success, kHistoryError);
}
- (void)waitForSyncInvalidationFields {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface waitForSyncInvalidationFields]);
}
- (void)triggerSyncCycleForType:(syncer::DataType)type {
[ChromeEarlGreyAppInterface triggerSyncCycleForType:type];
}
- (void)deleteAutofillProfileFromFakeSyncServerWithGUID:
(const std::string&)UTF8GUID {
NSString* GUID = base::SysUTF8ToNSString(UTF8GUID);
[ChromeEarlGreyAppInterface
deleteAutofillProfileFromFakeSyncServerWithGUID:GUID];
}
- (void)waitForSyncEngineInitialized:(BOOL)isInitialized
syncTimeout:(base::TimeDelta)timeout {
EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface
waitForSyncEngineInitialized:isInitialized
syncTimeout:timeout]);
}
- (void)waitForSyncFeatureEnabled:(BOOL)isEnabled
syncTimeout:(base::TimeDelta)timeout {
EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface
waitForSyncFeatureEnabled:isEnabled
syncTimeout:timeout]);
}
- (void)waitForSyncTransportStateActiveWithTimeout:(base::TimeDelta)timeout {
EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface
waitForSyncTransportStateActiveWithTimeout:timeout]);
}
- (const std::string)syncCacheGUID {
NSString* cacheGUID = [ChromeEarlGreyAppInterface syncCacheGUID];
return base::SysNSStringToUTF8(cacheGUID);
}
- (void)verifySyncServerSessionURLs:(NSArray<NSString*>*)URLs {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface verifySessionsOnSyncServerWithSpecs:URLs]);
}
- (void)waitForSyncServerEntitiesWithType:(syncer::DataType)type
name:(const std::string&)UTF8Name
count:(size_t)count
timeout:(base::TimeDelta)timeout {
NSString* errorString = [NSString
stringWithFormat:@"Expected %zu entities of the %d type.", count, type];
NSString* name = base::SysUTF8ToNSString(UTF8Name);
GREYCondition* verifyEntities = [GREYCondition
conditionWithName:errorString
block:^{
NSError* error = [ChromeEarlGreyAppInterface
verifyNumberOfSyncEntitiesWithType:type
name:name
count:count];
return !error;
}];
bool success = [verifyEntities waitWithTimeout:timeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(success, errorString);
}
- (void)waitForSyncServerHistoryURLs:(NSArray<NSURL*>*)URLs
timeout:(base::TimeDelta)timeout {
NSString* errorString =
[NSString stringWithFormat:@"Expected %zu URLs.", [URLs count]];
__block NSError* blockError = nil;
GREYCondition* verifyURLs =
[GREYCondition conditionWithName:errorString
block:^{
blockError = [ChromeEarlGreyAppInterface
verifyHistoryOnSyncServerWithURLs:URLs];
return !blockError;
}];
bool success = [verifyURLs waitWithTimeout:timeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_NO_ERROR(blockError);
EG_TEST_HELPER_ASSERT_TRUE(success, errorString);
}
- (BOOL)isSyncHistoryDataTypeSelected {
return [ChromeEarlGreyAppInterface isSyncHistoryDataTypeSelected];
}
- (void)addSyncPassphrase:(NSString*)syncPassphrase {
[ChromeEarlGreyAppInterface addSyncPassphrase:syncPassphrase];
}
#pragma mark - Window utilities (EG2)
- (CGRect)screenPositionOfScreenWithNumber:(int)windowNumber {
return [ChromeEarlGreyAppInterface
screenPositionOfScreenWithNumber:windowNumber];
}
- (NSUInteger)windowCount [[nodiscard]] {
return [ChromeEarlGreyAppInterface windowCount];
}
- (NSUInteger)foregroundWindowCount [[nodiscard]] {
return [ChromeEarlGreyAppInterface foregroundWindowCount];
}
- (void)closeAllExtraWindows {
if ([self windowCount] <= 1) {
return;
}
[ChromeEarlGreyAppInterface closeAllExtraWindows];
// Tab changes are initiated through `WebStateList`. Need to wait its
// observers to complete UI changes at app. Wait until window count is
// officially 1 in the app, otherwise we may start a new test while the
// removed window is still partly registered.
[self waitForForegroundWindowCount:1];
}
- (void)waitForForegroundWindowCount:(NSUInteger)count {
__block NSUInteger actualCount =
[ChromeEarlGreyAppInterface foregroundWindowCount];
NSString* conditionName = [NSString
stringWithFormat:@"Waiting for window count to become %" PRIuNS, count];
// Allow the UI to become idle, in case any tabs are being opened or closed.
GREYWaitForAppToIdle(@"App failed to idle");
GREYCondition* browserCountCheck = [GREYCondition
conditionWithName:conditionName
block:^{
actualCount =
[ChromeEarlGreyAppInterface foregroundWindowCount];
return actualCount == count;
}];
bool browserCountEqual =
[browserCountCheck waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
NSString* errorString = [NSString
stringWithFormat:@"Failed waiting for window count to become %" PRIuNS
"; actual count: %" PRIuNS,
count, actualCount];
EG_TEST_HELPER_ASSERT_TRUE(browserCountEqual, errorString);
}
- (void)openNewWindow {
EG_TEST_HELPER_ASSERT_NO_ERROR([ChromeEarlGreyAppInterface openNewWindow]);
}
- (void)openNewTabInWindowWithNumber:(int)windowNumber {
[ChromeEarlGreyAppInterface openNewTabInWindowWithNumber:windowNumber];
[self waitForPageToFinishLoadingInWindowWithNumber:windowNumber];
GREYWaitForAppToIdle(@"App failed to idle");
}
- (void)closeWindowWithNumber:(int)windowNumber {
[ChromeEarlGreyAppInterface closeWindowWithNumber:windowNumber];
}
- (void)changeWindowWithNumber:(int)windowNumber
toNewNumber:(int)newWindowNumber {
[ChromeEarlGreyAppInterface changeWindowWithNumber:windowNumber
toNewNumber:newWindowNumber];
}
- (void)waitForPageToFinishLoadingInWindowWithNumber:(int)windowNumber {
GREYCondition* finishedLoading = [GREYCondition
conditionWithName:kWaitForPageToFinishLoadingError
block:^{
return ![ChromeEarlGreyAppInterface
isLoadingInWindowWithNumber:windowNumber];
}];
BOOL pageLoaded =
[finishedLoading waitWithTimeout:kWaitForPageLoadTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(pageLoaded, kWaitForPageToFinishLoadingError);
}
- (void)loadURL:(const GURL&)URL
inWindowWithNumber:(int)windowNumber
waitForCompletion:(BOOL)wait {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
[ChromeEarlGreyAppInterface startLoadingURL:spec
inWindowWithNumber:windowNumber];
if (wait) {
[self waitForPageToFinishLoadingInWindowWithNumber:windowNumber];
// Loading URL (especially the first time) can trigger alerts.
[SystemAlertHandler handleSystemAlertIfVisible];
}
}
- (void)loadURL:(const GURL&)URL inWindowWithNumber:(int)windowNumber {
return [self loadURL:URL
inWindowWithNumber:windowNumber
waitForCompletion:YES];
}
- (BOOL)isLoadingInWindowWithNumber:(int)windowNumber {
return [ChromeEarlGreyAppInterface isLoadingInWindowWithNumber:windowNumber];
}
- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
inWindowWithNumber:(int)windowNumber {
[self waitForWebStateContainingText:UTF8Text
timeout:kWaitForPageLoadTimeout
inWindowWithNumber:windowNumber];
}
- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
timeout:(base::TimeDelta)timeout
inWindowWithNumber:(int)windowNumber {
NSString* text = base::SysUTF8ToNSString(UTF8Text);
NSString* errorString =
[NSString stringWithFormat:@"Failed waiting for web state containing %@ "
@"in window with number %d",
text, windowNumber];
GREYCondition* waitForText =
[GREYCondition conditionWithName:errorString
block:^{
return [ChromeEarlGreyAppInterface
webStateContainsText:text
inWindowWithNumber:windowNumber];
}];
bool containsText = [waitForText waitWithTimeout:timeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(containsText, errorString);
}
- (void)waitForMainTabCount:(NSUInteger)count
inWindowWithNumber:(int)windowNumber {
__block NSUInteger actualCount =
[ChromeEarlGreyAppInterface mainTabCountInWindowWithNumber:windowNumber];
NSString* conditionName = [NSString
stringWithFormat:@"Waiting for main tab count to become %" PRIuNS
" from %" PRIuNS " in window with number %d",
count, actualCount, windowNumber];
// Allow the UI to become idle, in case any tabs are being opened or closed.
GREYWaitForAppToIdle(@"App failed to idle");
GREYCondition* tabCountCheck = [GREYCondition
conditionWithName:conditionName
block:^{
actualCount = [ChromeEarlGreyAppInterface
mainTabCountInWindowWithNumber:windowNumber];
return actualCount == count;
}];
bool tabCountEqual =
[tabCountCheck waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
NSString* errorString = [NSString
stringWithFormat:@"Failed waiting for main tab count to become %" PRIuNS
" in window with number %d"
"; actual count: %" PRIuNS,
count, windowNumber, actualCount];
EG_TEST_HELPER_ASSERT_TRUE(tabCountEqual, errorString);
}
- (void)waitForIncognitoTabCount:(NSUInteger)count
inWindowWithNumber:(int)windowNumber {
__block NSUInteger actualCount = [ChromeEarlGreyAppInterface
incognitoTabCountInWindowWithNumber:windowNumber];
NSString* conditionName =
[NSString stringWithFormat:
@"Failed waiting for incognito tab count to become %" PRIuNS
" from %" PRIuNS " in window with number %d",
count, actualCount, windowNumber];
// Allow the UI to become idle, in case any tabs are being opened or closed.
GREYWaitForAppToIdle(@"App failed to idle");
GREYCondition* tabCountCheck = [GREYCondition
conditionWithName:conditionName
block:^{
actualCount = [ChromeEarlGreyAppInterface
incognitoTabCountInWindowWithNumber:windowNumber];
return actualCount == count;
}];
bool tabCountEqual =
[tabCountCheck waitWithTimeout:kWaitForUIElementTimeout.InSecondsF()];
NSString* errorString =
[NSString stringWithFormat:
@"Failed waiting for incognito tab count to become %" PRIuNS
" in window with number %d"
"; actual count: %" PRIuNS,
count, windowNumber, actualCount];
EG_TEST_HELPER_ASSERT_TRUE(tabCountEqual, errorString);
}
- (void)waitUntilReadyWindowWithNumber:(int)windowNumber {
[ChromeEarlGrey waitForUIElementToAppearWithMatcher:
chrome_test_util::MatchInWindowWithNumber(
windowNumber, chrome_test_util::FakeOmnibox())];
}
#pragma mark - SignIn Utilities (EG2)
- (void)signOutAndClearIdentities {
__block BOOL isSignoutFinished = NO;
[ChromeEarlGreyAppInterface signOutAndClearIdentitiesWithCompletion:^{
isSignoutFinished = YES;
}];
GREYCondition* signOutFinished = [GREYCondition
conditionWithName:@"Sign-out done, and identities & browsing data cleared"
block:^{
// Spin run loop to ensure observers are notified when
// webstate loading stops.
base::test::ios::SpinRunLoopWithMinDelay(
base::Milliseconds(100));
return isSignoutFinished;
}];
bool success = [signOutFinished
waitWithTimeout:base::test::ios::kWaitForClearBrowsingDataTimeout
.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(
success, @"Failed waiting for sign-out & cleaning completion");
}
#pragma mark - Bookmarks Utilities (EG2)
- (void)addBookmarkWithSyncPassphrase:(NSString*)syncPassphrase {
[ChromeEarlGreyAppInterface addBookmarkWithSyncPassphrase:syncPassphrase];
}
#pragma mark - Javascript Utilities (EG2)
- (void)waitForJavaScriptCondition:(NSString*)javaScriptCondition {
auto verifyBlock = ^BOOL {
base::Value value = [ChromeEarlGrey evaluateJavaScript:javaScriptCondition];
DCHECK(value.is_bool());
return value.GetBool();
};
base::TimeDelta timeout = base::test::ios::kWaitForActionTimeout;
NSString* conditionName = [NSString
stringWithFormat:@"Wait for JS condition: %@", javaScriptCondition];
GREYCondition* condition = [GREYCondition conditionWithName:conditionName
block:verifyBlock];
NSString* errorString =
[NSString stringWithFormat:@"Failed waiting for condition '%@'",
javaScriptCondition];
EG_TEST_HELPER_ASSERT_TRUE([condition waitWithTimeout:timeout.InSecondsF()],
errorString);
}
- (JavaScriptExecutionResult*)evaluateJavaScriptWithPotentialError:
(NSString*)javaScript {
return [ChromeEarlGreyAppInterface executeJavaScript:javaScript];
}
- (base::Value)evaluateJavaScript:(NSString*)javaScript {
JavaScriptExecutionResult* result =
[ChromeEarlGreyAppInterface executeJavaScript:javaScript];
EG_TEST_HELPER_ASSERT_TRUE(
result.success, @"An error was produced during the script's execution");
std::string jsonRepresentation = base::SysNSStringToUTF8(result.result);
JSONStringValueDeserializer deserializer(jsonRepresentation);
int errorCode;
std::string errorMessage;
auto jsonValue = deserializer.Deserialize(&errorCode, &errorMessage);
NSString* message = [NSString
stringWithFormat:
@"JSON parsing failed: code=%d, message=%@, jsonRepresentation=%@",
errorCode, base::SysUTF8ToNSString(errorMessage),
base::SysUTF8ToNSString(jsonRepresentation)];
EG_TEST_HELPER_ASSERT_TRUE(jsonValue, message);
return jsonValue ? std::move(*jsonValue) : base::Value();
}
- (void)evaluateJavaScriptForSideEffect:(NSString*)javaScript {
JavaScriptExecutionResult* result =
[ChromeEarlGreyAppInterface executeJavaScript:javaScript];
EG_TEST_HELPER_ASSERT_TRUE(
result.success, @"An error was produced during the script's execution");
}
- (NSString*)mobileUserAgentString {
return [ChromeEarlGreyAppInterface mobileUserAgentString];
}
#pragma mark - URL Utilities (EG2)
- (NSString*)displayTitleForURL:(const GURL&)URL {
NSString* spec = base::SysUTF8ToNSString(URL.spec());
return [ChromeEarlGreyAppInterface displayTitleForURL:spec];
}
#pragma mark - Accessibility Utilities (EG2)
- (void)verifyAccessibilityForCurrentScreen {
EG_TEST_HELPER_ASSERT_NO_ERROR(
[ChromeEarlGreyAppInterface verifyAccessibilityForCurrentScreen]);
}
#pragma mark - Check features (EG2)
- (BOOL)isVariationEnabled:(int)variationID {
return [ChromeEarlGreyAppInterface isVariationEnabled:variationID];
}
- (BOOL)isTriggerVariationEnabled:(int)variationID {
return [ChromeEarlGreyAppInterface isTriggerVariationEnabled:variationID];
}
- (BOOL)isUKMEnabled {
return [ChromeEarlGreyAppInterface isUKMEnabled];
}
- (BOOL)isTestFeatureEnabled {
return [ChromeEarlGreyAppInterface isTestFeatureEnabled];
}
- (BOOL)isDemographicMetricsReportingEnabled {
return [ChromeEarlGreyAppInterface isDemographicMetricsReportingEnabled];
}
- (BOOL)appHasLaunchSwitch:(const std::string&)launchSwitch {
return [ChromeEarlGreyAppInterface
appHasLaunchSwitch:base::SysUTF8ToNSString(launchSwitch)];
}
- (BOOL)isCustomWebKitLoadedIfRequested {
return [ChromeEarlGreyAppInterface isCustomWebKitLoadedIfRequested];
}
- (BOOL)isMobileModeByDefault {
return [ChromeEarlGreyAppInterface isMobileModeByDefault];
}
- (BOOL)areMultipleWindowsSupported {
return [ChromeEarlGreyAppInterface areMultipleWindowsSupported];
}
- (BOOL)isNewOverflowMenuEnabled {
return [ChromeEarlGreyAppInterface isNewOverflowMenuEnabled];
}
// Returns whether the UseLensToSearchForImage feature is enabled;
- (BOOL)isUseLensToSearchForImageEnabled {
return [ChromeEarlGreyAppInterface isUseLensToSearchForImageEnabled];
}
- (BOOL)isWebChannelsEnabled {
return [ChromeEarlGreyAppInterface isWebChannelsEnabled];
}
- (BOOL)isTabGroupSyncEnabled {
return [ChromeEarlGreyAppInterface isTabGroupSyncEnabled];
}
- (BOOL)isUnfocusedOmniboxAtBottom {
return !self.isIPadIdiom && self.isSplitToolbarMode &&
[self localStateBooleanPref:prefs::kBottomOmnibox];
}
#pragma mark - ContentSettings
- (ContentSetting)popupPrefValue {
return [ChromeEarlGreyAppInterface popupPrefValue];
}
- (void)setPopupPrefValue:(ContentSetting)value {
return [ChromeEarlGreyAppInterface setPopupPrefValue:value];
}
- (void)resetDesktopContentSetting {
[ChromeEarlGreyAppInterface resetDesktopContentSetting];
}
- (void)setContentSetting:(ContentSetting)setting
forContentSettingsType:(ContentSettingsType)type {
[ChromeEarlGreyAppInterface setContentSetting:setting
forContentSettingsType:type];
}
#pragma mark - Keyboard utilities
- (NSInteger)registeredKeyCommandCount {
return [ChromeEarlGreyAppInterface registeredKeyCommandCount];
}
- (void)simulatePhysicalKeyboardEvent:(NSString*)input
flags:(UIKeyModifierFlags)flags {
[ChromeEarlGreyAppInterface simulatePhysicalKeyboardEvent:input flags:flags];
}
- (void)waitForKeyboardToAppear {
GREYCondition* waitForKeyboard = [GREYCondition
conditionWithName:@"Wait for keyboard to appear"
block:^BOOL {
return [EarlGrey isKeyboardShownWithError:nil];
}];
bool success =
[waitForKeyboard waitWithTimeout:kWaitForActionTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(success, @"Keyboard didn't appear");
}
- (void)waitForKeyboardToDisappear {
GREYCondition* waitForKeyboard = [GREYCondition
conditionWithName:@"Wait for keyboard to disappear"
block:^BOOL {
return ![EarlGrey isKeyboardShownWithError:nil];
}];
bool success =
[waitForKeyboard waitWithTimeout:kWaitForActionTimeout.InSecondsF()];
EG_TEST_HELPER_ASSERT_TRUE(success, @"Keyboard didn't dismiss");
}
#pragma mark - Default Utilities (EG2)
- (void)setUserDefaultsObject:(id)value forKey:(NSString*)defaultName {
[ChromeEarlGreyAppInterface setUserDefaultsObject:value forKey:defaultName];
}
- (void)removeUserDefaultsObjectForKey:(NSString*)key {
[ChromeEarlGreyAppInterface removeUserDefaultsObjectForKey:key];
}
- (id)userDefaultsObjectForKey:(NSString*)key {
return [ChromeEarlGreyAppInterface userDefaultsObjectForKey:key];
}
#pragma mark - Pref Utilities (EG2)
- (void)commitPendingUserPrefsWrite {
[ChromeEarlGreyAppInterface commitPendingUserPrefsWrite];
}
// Returns a base::Value representation of the requested pref.
- (std::unique_ptr<base::Value>)localStatePrefValue:
(const std::string&)prefName {
std::string jsonRepresentation =
base::SysNSStringToUTF8([ChromeEarlGreyAppInterface
localStatePrefValue:base::SysUTF8ToNSString(prefName)]);
JSONStringValueDeserializer deserializer(jsonRepresentation);
return deserializer.Deserialize(/*error_code=*/nullptr,
/*error_message=*/nullptr);
}
- (bool)localStateBooleanPref:(const std::string&)prefName {
std::unique_ptr<base::Value> value = [self localStatePrefValue:prefName];
BOOL success = value && value->is_bool();
EG_TEST_HELPER_ASSERT_TRUE(success, @"Expected bool");
return success ? value->GetBool() : false;
}
- (int)localStateIntegerPref:(const std::string&)prefName {
std::unique_ptr<base::Value> value = [self localStatePrefValue:prefName];
BOOL success = value && value->is_int();
EG_TEST_HELPER_ASSERT_TRUE(success, @"Expected int");
return success ? value->GetInt() : 0;
}
- (std::string)localStateStringPref:(const std::string&)prefName {
std::unique_ptr<base::Value> value = [self localStatePrefValue:prefName];
BOOL success = value && value->is_string();
EG_TEST_HELPER_ASSERT_TRUE(success, @"Expected string");
return success ? value->GetString() : "";
}
- (void)setIntegerValue:(int)value
forLocalStatePref:(const std::string&)prefName {
[ChromeEarlGreyAppInterface
setIntegerValue:value
forLocalStatePref:base::SysUTF8ToNSString(prefName)];
}
- (void)setTimeValue:(base::Time)value
forLocalStatePref:(const std::string&)UTF8PrefName {
NSString* prefName = base::SysUTF8ToNSString(UTF8PrefName);
return [ChromeEarlGreyAppInterface setTimeValue:value
forLocalStatePref:prefName];
}
- (void)setTimeValue:(base::Time)value
forUserPref:(const std::string&)UTF8PrefName {
NSString* prefName = base::SysUTF8ToNSString(UTF8PrefName);
return [ChromeEarlGreyAppInterface setTimeValue:value forUserPref:prefName];
}
- (void)setStringValue:(const std::string&)UTF8Value
forLocalStatePref:(const std::string&)UTF8PrefName {
NSString* value = base::SysUTF8ToNSString(UTF8Value);
NSString* prefName = base::SysUTF8ToNSString(UTF8PrefName);
return [ChromeEarlGreyAppInterface setStringValue:value
forLocalStatePref:prefName];
}
- (void)setBoolValue:(BOOL)value
forLocalStatePref:(const std::string&)UTF8PrefName {
NSString* prefName = base::SysUTF8ToNSString(UTF8PrefName);
return [ChromeEarlGreyAppInterface setBoolValue:value
forLocalStatePref:prefName];
}
// Returns a base::Value representation of the requested pref.
- (std::unique_ptr<base::Value>)userPrefValue:(const std::string&)prefName {
std::string jsonRepresentation =
base::SysNSStringToUTF8([ChromeEarlGreyAppInterface
userPrefValue:base::SysUTF8ToNSString(prefName)]);
JSONStringValueDeserializer deserializer(jsonRepresentation);
return deserializer.Deserialize(/*error_code=*/nullptr,
/*error_message=*/nullptr);
}
- (bool)userBooleanPref:(const std::string&)prefName {
std::unique_ptr<base::Value> value = [self userPrefValue:prefName];
BOOL success = value && value->is_bool();
EG_TEST_HELPER_ASSERT_TRUE(success, @"Expected bool");
return success ? value->GetBool() : false;
}
- (int)userIntegerPref:(const std::string&)prefName {
std::unique_ptr<base::Value> value = [self userPrefValue:prefName];
BOOL success = value && value->is_int();
EG_TEST_HELPER_ASSERT_TRUE(success, @"Expected int");
return success ? value->GetInt() : 0;
}
- (std::string)userStringPref:(const std::string&)prefName {
std::unique_ptr<base::Value> value = [self userPrefValue:prefName];
BOOL success = value && value->is_string();
EG_TEST_HELPER_ASSERT_TRUE(success, @"Expected string");
return success ? value->GetString() : "";
}
- (void)setBoolValue:(BOOL)value forUserPref:(const std::string&)UTF8PrefName {
NSString* prefName = base::SysUTF8ToNSString(UTF8PrefName);
return [ChromeEarlGreyAppInterface setBoolValue:value forUserPref:prefName];
}
- (void)setStringValue:(NSString*)value
forUserPref:(const std::string&)UTF8PrefName {
NSString* prefName = base::SysUTF8ToNSString(UTF8PrefName);
return [ChromeEarlGreyAppInterface setStringValue:value forUserPref:prefName];
}
- (void)setIntegerValue:(int)value
forUserPref:(const std::string&)UTF8PrefName {
NSString* prefName = base::SysUTF8ToNSString(UTF8PrefName);
return [ChromeEarlGreyAppInterface setIntegerValue:value
forUserPref:prefName];
}
- (bool)prefWithNameIsDefaultValue:(const std::string&)prefName {
return [ChromeEarlGreyAppInterface
prefWithNameIsDefaultValue:base::SysUTF8ToNSString(prefName)];
}
- (void)clearUserPrefWithName:(const std::string&)prefName {
[ChromeEarlGreyAppInterface
clearUserPrefWithName:base::SysUTF8ToNSString(prefName)];
}
- (void)resetBrowsingDataPrefs {
return [ChromeEarlGreyAppInterface resetBrowsingDataPrefs];
}
- (void)resetDataForLocalStatePref:(const std::string&)prefName {
return [ChromeEarlGreyAppInterface
resetDataForLocalStatePref:base::SysUTF8ToNSString(prefName)];
}
#pragma mark - Pasteboard Utilities (EG2)
- (void)verifyStringCopied:(NSString*)text {
ConditionBlock condition = ^{
NSArray<NSString*>* pasteboardStrings =
[ChromeEarlGreyAppInterface pasteboardStrings];
for (NSString* paste in pasteboardStrings) {
if ([paste containsString:text]) {
return true;
}
}
return false;
};
GREYAssert(base::test::ios::WaitUntilConditionOrTimeout(kWaitForActionTimeout,
condition),
@"Waiting for '%@' to be copied to pasteboard.", text);
}
- (BOOL)pasteboardHasImages {
return [ChromeEarlGreyAppInterface pasteboardHasImages];
}
- (GURL)pasteboardURL {
NSString* absoluteString = [ChromeEarlGreyAppInterface pasteboardURLSpec];
return absoluteString ? GURL(base::SysNSStringToUTF8(absoluteString))
: GURL();
}
- (void)clearPasteboard {
[ChromeEarlGreyAppInterface clearPasteboard];
}
- (void)copyTextToPasteboard:(NSString*)text {
[ChromeEarlGreyAppInterface copyTextToPasteboard:text];
}
#pragma mark - Context Menus Utilities (EG2)
- (void)verifyCopyLinkActionWithText:(NSString*)text {
[ChromeEarlGreyAppInterface clearPasteboardURLs];
[[EarlGrey selectElementWithMatcher:CopyLinkButton()]
performAction:grey_tap()];
[self verifyStringCopied:text];
}
- (void)verifyOpenInNewTabActionWithURL:(const std::string&)URL {
// Check tab count prior to execution.
NSUInteger oldRegularTabCount = [ChromeEarlGreyAppInterface mainTabCount];
NSUInteger oldIncognitoTabCount =
[ChromeEarlGreyAppInterface incognitoTabCount];
[[EarlGrey selectElementWithMatcher:OpenLinkInNewTabButton()]
performAction:grey_tap()];
[self waitForMainTabCount:oldRegularTabCount + 1];
[self waitForIncognitoTabCount:oldIncognitoTabCount];
[[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(URL)]
assertWithMatcher:grey_notNil()];
}
- (void)verifyOpenInNewWindowActionWithContent:(const std::string&)content {
[ChromeEarlGrey waitForForegroundWindowCount:1];
[[EarlGrey selectElementWithMatcher:OpenLinkInNewWindowButton()]
performAction:grey_tap()];
[ChromeEarlGrey waitForForegroundWindowCount:2];
[ChromeEarlGrey waitForWebStateContainingText:content inWindowWithNumber:1];
}
- (void)verifyOpenInIncognitoActionWithURL:(const std::string&)URL {
// Check tab count prior to execution.
NSUInteger oldRegularTabCount = [ChromeEarlGreyAppInterface mainTabCount];
NSUInteger oldIncognitoTabCount =
[ChromeEarlGreyAppInterface incognitoTabCount];
[[EarlGrey selectElementWithMatcher:OpenLinkInIncognitoButton()]
performAction:grey_tap()];
[self waitForIncognitoTabCount:oldIncognitoTabCount + 1];
[self waitForMainTabCount:oldRegularTabCount];
[[EarlGrey selectElementWithMatcher:chrome_test_util::OmniboxText(URL)]
assertWithMatcher:grey_notNil()];
}
- (void)verifyShareActionWithURL:(const GURL&)URL
pageTitle:(NSString*)pageTitle {
[[EarlGrey selectElementWithMatcher:ShareButton()] performAction:grey_tap()];
NSString* hostString = base::SysUTF8ToNSString(URL.host());
if (@available(iOS 17, *)) {
XCUIApplication* currentApplication = [[XCUIApplication alloc] init];
BOOL hostStringPresent = [currentApplication.otherElements[hostString]
waitForExistenceWithTimeout:2];
BOOL pageTitlePresent = [currentApplication.otherElements[pageTitle]
waitForExistenceWithTimeout:2];
GREYAssert(hostStringPresent || pageTitlePresent,
@"Either hostString %d or pageTitle %d was not present",
hostStringPresent, pageTitlePresent);
} else {
#if TARGET_IPHONE_SIMULATOR
// The activity view share sheet blocks EarlGrey's synchronization on
// the simulators. Ref:
// github.com/google/EarlGrey/blob/master/docs/features.md#visibility-checks
ScopedSynchronizationDisabler disabler;
#endif
// On iOS 16, LPLinkView and LPTextView are marked isAccessible=N.
ScopedMatchNonAccessibilityElements enabler;
// Page title is added asynchronously, so wait for its appearance.
[self waitForMatcher:grey_allOf(ActivityViewHeader(hostString, pageTitle),
grey_sufficientlyVisible(), nil)];
}
// Dismiss the Activity View by tapping outside its bounds.
[[EarlGrey selectElementWithMatcher:grey_keyWindow()]
performAction:grey_tap()];
}
#pragma mark - Unified consent utilities
- (void)setURLKeyedAnonymizedDataCollectionEnabled:(BOOL)enabled {
return [ChromeEarlGreyAppInterface
setURLKeyedAnonymizedDataCollectionEnabled:enabled];
}
#pragma mark - Watcher utilities
- (void)watchForButtonsWithLabels:(NSArray<NSString*>*)labels
timeout:(base::TimeDelta)timeout {
[ChromeEarlGreyAppInterface watchForButtonsWithLabels:labels timeout:timeout];
}
- (BOOL)watcherDetectedButtonWithLabel:(NSString*)label {
return [ChromeEarlGreyAppInterface watcherDetectedButtonWithLabel:label];
}
- (void)stopWatcher {
[ChromeEarlGreyAppInterface stopWatcher];
}
#pragma mark - Default Browser Promo Utilities
- (void)clearDefaultBrowserPromoData {
[ChromeEarlGreyAppInterface clearDefaultBrowserPromoData];
}
- (void)copyURLToPasteBoard {
[ChromeEarlGreyAppInterface copyURLToPasteBoard];
}
#pragma mark - ActivitySheet utilities
- (void)verifyActivitySheetVisible {
if (@available(iOS 17.0, *)) {
NSError* error = nil;
GREYAssert([EarlGrey activitySheetPresentWithError:&error],
@"Activity sheet not visible");
} else {
[[EarlGrey
selectElementWithMatcher:grey_accessibilityID(kActivityMenuIdentifier)]
assertWithMatcher:grey_sufficientlyVisible()];
}
}
- (void)verifyActivitySheetNotVisible {
if (@available(iOS 17.0, *)) {
NSError* error = nil;
// Note that -activitySheetAbsentWithError's return value is incorrect, so
// only check the error.
[EarlGrey activitySheetAbsentWithError:&error];
EG_TEST_HELPER_ASSERT_NO_ERROR(error);
} else {
[[EarlGrey
selectElementWithMatcher:grey_accessibilityID(kActivityMenuIdentifier)]
assertWithMatcher:grey_nil()];
}
}
- (void)verifyTextNotVisibleInActivitySheetWithID:(NSString*)text {
if (@available(iOS 17, *)) {
NSError* error = nil;
GREYAssert([EarlGrey activitySheetPresentWithError:&error],
@"Activity sheet not visible");
XCUIApplication* currentApplication = [[XCUIApplication alloc] init];
XCUIElement* activitySheet =
currentApplication.otherElements[@"ActivityListView"];
XCUIElementQuery* activityTexts =
[activitySheet descendantsMatchingType:XCUIElementTypeStaticText];
XCUIElement* staticText =
[activityTexts elementMatchingType:XCUIElementTypeStaticText
identifier:text];
GREYAssert(!staticText.exists, @"staticText %@ visible", text);
} else {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(text)]
assertWithMatcher:grey_notVisible()];
}
}
- (void)verifyTextVisibleInActivitySheetWithID:(NSString*)text {
if (@available(iOS 17, *)) {
NSError* error = nil;
GREYAssert([EarlGrey activitySheetPresentWithError:&error],
@"Activity sheet not visible");
XCUIApplication* currentApplication = [[XCUIApplication alloc] init];
XCUIElement* activitySheet =
currentApplication.otherElements[@"ActivityListView"];
XCUIElementQuery* activityTexts =
[activitySheet descendantsMatchingType:XCUIElementTypeStaticText];
XCUIElement* staticText =
[activityTexts elementMatchingType:XCUIElementTypeStaticText
identifier:text];
GREYAssert(staticText.exists, @"staticText %@ not visible", text);
} else {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(text)]
assertWithMatcher:grey_sufficientlyVisible()];
}
}
- (void)tapButtonInActivitySheetWithID:(NSString*)buttonLabel {
if (@available(iOS 17, *)) {
NSError* error = nil;
GREYAssertTrue([EarlGrey tapButtonInActivitySheetWithId:buttonLabel
error:&error],
@"Button %@ not present in activity sheet", buttonLabel);
EG_TEST_HELPER_ASSERT_NO_ERROR(error);
} else {
[[EarlGrey selectElementWithMatcher:grey_accessibilityLabel(buttonLabel)]
performAction:grey_tap()];
}
}
- (void)closeActivitySheet {
if ([ChromeEarlGrey isIPadIdiom]) {
// Tap the share button to dismiss the popover.
[[EarlGrey selectElementWithMatcher:chrome_test_util::TabShareButton()]
performAction:grey_tap()];
} else {
if (@available(iOS 17, *)) {
[EarlGrey closeActivitySheetWithError:nil];
} else {
NSString* dismissLabel = @"Close";
[[EarlGrey
selectElementWithMatcher:
grey_allOf(
chrome_test_util::ButtonWithAccessibilityLabel(dismissLabel),
grey_not(
grey_accessibilityTrait(UIAccessibilityTraitNotEnabled)),
grey_interactable(), nullptr)] performAction:grey_tap()];
}
}
}
#pragma mark - First Run Utilities
- (void)writeFirstRunSentinel {
[ChromeEarlGreyAppInterface writeFirstRunSentinel];
}
- (void)removeFirstRunSentinel {
[ChromeEarlGreyAppInterface removeFirstRunSentinel];
}
- (bool)hasFirstRunSentinel {
return [ChromeEarlGreyAppInterface hasFirstRunSentinel];
}
- (void)requestTipsNotification:(TipsNotificationType)type {
return [ChromeEarlGreyAppInterface requestTipsNotification:type];
}
@end