blob: fff3d021080aebdb98dc757fda111e52be25f852 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/testing/earl_grey/base_earl_grey_test_case.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/testing/earl_grey/app_launch_configuration.h"
#import "ios/testing/earl_grey/app_launch_manager.h"
#import "ios/testing/earl_grey/base_earl_grey_test_case_app_interface.h"
#import "ios/testing/earl_grey/coverage_utils.h"
#import "ios/testing/earl_grey/earl_grey_test.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(BaseEarlGreyTestCaseAppInterface)
namespace {
// If true, +setUpForTestCase will be called from -setUp. This flag is used to
// ensure that +setUpForTestCase is called exactly once per unique XCTestCase
// and is reset in +tearDown.
bool g_needs_set_up_for_test_case = true;
} // namespace
@implementation BaseEarlGreyTestCase
+ (void)setUpForTestCase {
}
// Invoked upon starting each test method in a test case.
// Launches the app under test if necessary.
- (void)setUp {
[super setUp];
[[AppLaunchManager sharedManager]
ensureAppLaunchedWithConfiguration:[self appConfigurationForTestCase]];
[self handleSystemAlertIfVisible];
NSString* logFormat = @"*********************************\nStarting test: %@";
[BaseEarlGreyTestCaseAppInterface
logMessage:[NSString stringWithFormat:logFormat, self.name]];
// Calling XCTFail before the application is launched does not assert
// properly, so failing upon detection of overriding +setUp is delayed until
// here. See +setUp below for details on why overriding +setUp causes a
// failure.
[self failIfSetUpIsOverridden];
if (g_needs_set_up_for_test_case) {
g_needs_set_up_for_test_case = false;
[[self class] setUpForTestCase];
}
}
+ (void)tearDown {
[CoverageUtils writeClangCoverageProfile];
[CoverageUtils resetCoverageProfileCounters];
g_needs_set_up_for_test_case = true;
[super tearDown];
}
// Handles system alerts if any are present, closing them to unblock the UI.
- (void)handleSystemAlertIfVisible {
NSError* systemAlertFoundError = nil;
[[EarlGrey selectElementWithMatcher:grey_systemAlertViewShown()]
assertWithMatcher:grey_nil()
error:&systemAlertFoundError];
if (systemAlertFoundError) {
NSError* alertGetTextError = nil;
NSString* alertText =
[self grey_systemAlertTextWithError:&alertGetTextError];
GREYAssertNil(alertGetTextError, @"Error getting alert text.\n%@",
alertGetTextError);
@try {
// TODO(crbug.com/1073542): Style guide does not allow throwing
// exceptions. This call throws an NSInternalInconsistencyException when
// the system alert is unknown in EG2 framework. The exception will be
// handled in @catch. Otherwise the system alert is of a known type,
// accept it.
[self grey_systemAlertType];
DLOG(WARNING) << "Accepting iOS system alert: "
<< base::SysNSStringToUTF8(alertText);
NSError* acceptAlertError = nil;
[self grey_acceptSystemDialogWithError:&acceptAlertError];
GREYAssertNil(acceptAlertError, @"Error accepting system alert.\n%@",
acceptAlertError);
} @catch (NSException* exception) {
GREYAssert(
(exception.name == NSInternalInconsistencyException &&
[exception.reason rangeOfString:@"Invalid System Alert."].location !=
NSNotFound),
@"Unknown error caught when handling unknown system alert: %@",
exception.reason);
// If the unsupported alert is iOS upgrade or carrier settings alert,
// handle it. Otherwise, fail the test.
if ([alertText isEqualToString:@"Software Update"]) {
DLOG(WARNING) << "Denying iOS system alert of Software Update!";
// Software Update alert usually has two consecutive alerts, handle them
// one by one.
NSError* error = nil;
// Choose "Later" for the first alert.
[self tapAlertButtonWithText:@"Later" error:&error];
// If an error with code |GREYSystemAlertCustomButtonNotFound| happens,
// probably the second alert is already there. Try to handle it in
// following steps.
GREYAssert(
error == nil || error.code == GREYSystemAlertCustomButtonNotFound,
@"Error denying first Software Update alert.\n%@", error);
// A second alert promoting to update tonight will appear. Wait for it.
[self grey_waitForAlertVisibility:YES
withTimeout:kSystemAlertVisibilityTimeout];
error = nil;
// Choose "Remind Me Later" for the second alert.
[self tapAlertButtonWithText:@"Remind Me Later" error:&error];
GREYAssertNil(error, @"Error denying second Software Update alert.\n%@",
error);
} else if ([alertText
containsString:@"A new iOS update is now available."]) {
DLOG(WARNING)
<< "Denying iOS system alert of new iOS update is now available!";
// This is another format of Software Update dialog. Need to choose
// "Close".
NSError* error = nil;
[self tapAlertButtonWithText:@"Close" error:&error];
GREYAssertNil(error, @"Error closing Software Update alert.\n%@",
error);
} else if ([alertText isEqualToString:@"Carrier Settings Update"]) {
DLOG(WARNING) << "Denying iOS system alert of Carrier Settings Update!";
NSError* error = nil;
[self tapAlertButtonWithText:@"Not Now" error:&error];
GREYAssertNil(
error, @"Error closing Carrier Settings Update alert.\n%@", error);
} else if ([alertText containsString:@"would like to find and connect to "
@"devices on your local network."]) {
DLOG(WARNING) << "Denying iOS system alert of connecting to local "
"network devices!";
NSError* error = nil;
[self tapAlertButtonWithText:@"OK" error:&error];
GREYAssertNil(error,
@"Error closing connecting to local network devices.\n%@",
error);
} else {
XCTFail("An unsupported system alert is present on device. Failing "
"this test. Alert label: %@",
alertText);
}
}
}
// Ensures no visible alert after handling.
[self grey_waitForAlertVisibility:NO
withTimeout:kSystemAlertVisibilityTimeout];
}
- (AppLaunchConfiguration)appConfigurationForTestCase {
return AppLaunchConfiguration();
}
#pragma mark - Private
// Prevents tests inheriting from this class from putting logic in +setUp.
// +setUp will be called before the application is launched,
// and thus is not suitable for most test case setup. Inheriting tests should
// migrate their +setUp logic to use the equivalent -setUpForTestCase.
- (void)failIfSetUpIsOverridden {
if ([[BaseEarlGreyTestCase class] methodForSelector:@selector(setUp)] !=
[[self class] methodForSelector:@selector(setUp)]) {
XCTFail(@"EG2 test class %@ inheriting from BaseEarlGreyTestCase "
@"should not override +setUp, as it is called before the "
@"test application is launched. Please convert your "
@"+setUp method to +setUpForTestCase.",
NSStringFromClass([self class]));
}
}
// Taps button with |text| in the system alert on screen. If an alert or the
// button doesn't exist, note it in |error| accordingly. In EG1, this method is
// no-op.
- (void)tapAlertButtonWithText:(NSString*)text error:(NSError**)error {
XCUIApplication* springboardApp = [[XCUIApplication alloc]
initWithBundleIdentifier:@"com.apple.springboard"];
XCUIElement* alert = [[springboardApp
descendantsMatchingType:XCUIElementTypeAlert] firstMatch];
if (![alert waitForExistenceWithTimeout:kSystemAlertVisibilityTimeout]) {
*error = [NSError errorWithDomain:kGREYSystemAlertDismissalErrorDomain
code:GREYSystemAlertNotPresent
userInfo:nil];
return;
}
XCUIElement* button = alert.buttons[text];
if (![alert.buttons[text] exists]) {
*error = [NSError errorWithDomain:kGREYSystemAlertDismissalErrorDomain
code:GREYSystemAlertCustomButtonNotFound
userInfo:nil];
return;
}
[button tap];
}
@end