Skip to content

Commit

Permalink
Refactor: Simplify error handling in GREYElementInteraction grey_hand…
Browse files Browse the repository at this point in the history
…leFailure...
  • Loading branch information
axi0mX committed Aug 26, 2016
1 parent 0cfd6f8 commit 03a851f
Showing 1 changed file with 78 additions and 159 deletions.
237 changes: 78 additions & 159 deletions EarlGrey/Core/GREYElementInteraction.m
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,9 @@ - (instancetype)performAction:(id<GREYAction>)action error:(__strong NSError **)
// If we encountered a failure and are going to raise an exception, raise it right away before
// the main runloop drains any further.
if (actionError && !errorOrNil) {
[strongSelf grey_handleFailureOfAction:action
actionError:actionError
userProvidedOutError:nil];
[strongSelf grey_failInteraction:[NSString stringWithFormat:@"Action '%@'", action.name]
exception:kGREYActionFailedException
error:actionError];
}
} error:&executorError];

Expand All @@ -253,9 +253,13 @@ - (instancetype)performAction:(id<GREYAction>)action error:(__strong NSError **)
NSLocalizedDescriptionKey : description }];
}
if (actionError) {
[self grey_handleFailureOfAction:action
actionError:actionError
userProvidedOutError:errorOrNil];
if (errorOrNil) {
*errorOrNil = actionError;
} else {
[self grey_failInteraction:[NSString stringWithFormat:@"Action '%@'", action.name]
exception:kGREYActionFailedException
error:actionError];
}
}
// Drain once to update idling resources and redraw the screen.
[[GREYUIThreadExecutor sharedInstance] drainOnce];
Expand All @@ -273,9 +277,6 @@ - (instancetype)assert:(id<GREYAssertion>)assertion error:(__strong NSError **)e

@autoreleasepool {
NSError *executorError;
// An error object that holds error due to element not found (if any). It is used only when
// an assertion fails because element was nil. That's when we surface this error.
__block NSError *elementNotFoundError = nil;
__block NSError *assertionError = nil;
__weak __typeof__(self) weakSelf = self;
NSNotificationCenter *defaultNotificationCenter = [NSNotificationCenter defaultCenter];
Expand All @@ -285,8 +286,10 @@ - (instancetype)assert:(id<GREYAssertion>)assertion error:(__strong NSError **)e
__typeof__(self) strongSelf = weakSelf;
NSAssert(strongSelf, @"strongSelf must not be nil");

NSArray *elements = [strongSelf matchedElementsWithTimeout:timeout
error:&elementNotFoundError];
// An error object that holds error due to element not found (if any). It is used only when
// an assertion fails because element was nil. That's when we surface this error.
NSError *matchError = nil;
NSArray *elements = [strongSelf matchedElementsWithTimeout:timeout error:&matchError];
if (elements.count > 1) {
assertionError = [strongSelf grey_errorForMultipleMatchingElements:elements];
} else {
Expand All @@ -311,6 +314,12 @@ - (instancetype)assert:(id<GREYAssertion>)assertion error:(__strong NSError **)e
if (assertionError) {
[assertionUserInfo setObject:assertionError forKey:kGREYAssertionErrorUserInfoKey];
}
if ([assertionError.domain isEqualToString:kGREYInteractionErrorDomain] &&
assertionError.code == kGREYInteractionElementNotFoundErrorCode) {
assertionError = [NSError errorWithDomain:kGREYInteractionErrorDomain
code:kGREYInteractionElementNotFoundErrorCode
userInfo:@{ NSUnderlyingErrorKey : matchError }];
}
// Notification for the assertion being successfully completed on the found element.
[defaultNotificationCenter postNotificationName:kGREYDidPerformAssertionNotification
object:nil
Expand All @@ -319,10 +328,10 @@ - (instancetype)assert:(id<GREYAssertion>)assertion error:(__strong NSError **)e
// If we encountered a failure and are going to raise an exception, raise it right away before
// the main runloop drains any further.
if (assertionError && !errorOrNil) {
[strongSelf grey_handleFailureOfAssertion:assertion
assertionError:assertionError
elementNotFoundError:elementNotFoundError
userProvidedOutError:nil];
NSString *interactionName = [NSString stringWithFormat:@"Assertion '%@'", assertion.name];
[strongSelf grey_failInteraction:interactionName
exception:kGREYAssertionFailedException
error:assertionError];
}
} error:&executorError];

Expand All @@ -337,10 +346,13 @@ - (instancetype)assert:(id<GREYAssertion>)assertion error:(__strong NSError **)e
NSLocalizedDescriptionKey : description }];
}
if (assertionError) {
[self grey_handleFailureOfAssertion:assertion
assertionError:assertionError
elementNotFoundError:elementNotFoundError
userProvidedOutError:errorOrNil];
if (errorOrNil) {
*errorOrNil = assertionError;
} else {
[self grey_failInteraction:[NSString stringWithFormat:@"Assertion '%@'", assertion.name]
exception:kGREYAssertionFailedException
error:assertionError];
}
}
}
return self;
Expand All @@ -367,140 +379,59 @@ - (instancetype)usingSearchAction:(id<GREYAction>)action
#pragma mark - Private

/**
* Handles failure of an @c action.
*
* @param action The action that failed.
* @param actionError Contains the reason for failure.
* @param[out] userProvidedError The out error (or nil) provided by the user.
* @throws NSException to denote the failure of an action, thrown if the @c userProvidedError
* is nil on test failure.
* Handles failure of an @c interaction.
*
* @return Junk boolean value to suppress xcode warning to have "a non-void return
* value to indicate an error occurred"
* @param interactionName Name of the failing action or assertion.
* @param defaultExceptionName Default exception name for this type of interaction.
* @param error NSError object with reason for the failure.
* @throws NSException with the interaction failure is always thrown.
*/
- (BOOL)grey_handleFailureOfAction:(id<GREYAction>)action
actionError:(NSError *)actionError
userProvidedOutError:(__strong NSError **)userProvidedError {
NSParameterAssert(actionError);

// Throw an exception if userProvidedError isn't provided and the action failed.
if (!userProvidedError) {
if ([actionError.domain isEqualToString:kGREYInteractionErrorDomain]) {
NSString *searchAPIInfo = [self grey_searchActionDescription];

// Customize exception based on the error.
switch (actionError.code) {
case kGREYInteractionElementNotFoundErrorCode: {
NSString *reason =
[NSString stringWithFormat:@"Action '%@' was not performed because no UI element "
@"matching %@ was found.", action.name, _elementMatcher];
I_GREYRegisterFailure(kGREYNoMatchingElementException, reason, @"%@Complete Error: %@",
searchAPIInfo, actionError);
return NO;
}
case kGREYInteractionMultipleElementsMatchedErrorCode: {
NSString *reason =
[NSString stringWithFormat:@"Action '%@' was not performed because multiple UI "
@"elements matching %@ were found. Use grey_allOf(...) to "
@"create a more specific matcher.",
action.name, _elementMatcher];
// We print the localized description here to prevent the multiple matchers info from
// being displayed twice - once in the error and once in the userInfo dict.
I_GREYRegisterFailure(kGREYMultipleElementsFoundException, reason,
@"%@Complete Error: %@",
searchAPIInfo, actionError.localizedDescription);
return NO;
}
case kGREYInteractionSystemAlertViewIsDisplayedErrorCode: {
NSString *reason =
[NSString stringWithFormat:@"Action '%@' was not performed because a system alert "
@"view was displayed.", action.name];
I_GREYRegisterFailure(kGREYActionFailedException, reason, @"%@Complete Error: %@",
searchAPIInfo, actionError);
return NO;
}
}
}

// TODO: Add unique failure messages for timeout and other well-known reasons.
NSString *reason = [NSString stringWithFormat:@"Action '%@' failed.", action.name];
I_GREYRegisterFailure(kGREYActionFailedException, reason,
@"Element matcher: %@\nComplete Error: %@", _elementMatcher, actionError);
} else {
*userProvidedError = actionError;
}

return NO;
}

/**
* Handles failure of an @c assertion.
*
* @param assertion The asserion that failed.
* @param assertionError Contains the reason for the failure.
* @param elementNotFoundError If non-nil, contains the underlying reason
* for element not being found.
* @param[out] userProvidedError Error (or @c nil) provided by the user. When @c nil, an exception
* is thrown to halt further execution of the test case.
* @throws NSException to denote an assertion failure, thrown if the @c userProvidedError
* is @c nil on test failure.
*
* @return Junk boolean value to suppress xcode warning to have "a non-void return
* value to indicate an error occurred"
*/
- (BOOL)grey_handleFailureOfAssertion:(id<GREYAssertion>)assertion
assertionError:(NSError *)assertionError
elementNotFoundError:(NSError *)elementNotFoundError
userProvidedOutError:(__strong NSError **)userProvidedError {
NSParameterAssert(assertionError);

if ([assertionError.domain isEqualToString:kGREYInteractionErrorDomain] &&
assertionError.code == kGREYInteractionElementNotFoundErrorCode) {
NSDictionary *userInfo = @{ NSUnderlyingErrorKey : elementNotFoundError };
assertionError = [NSError errorWithDomain:kGREYInteractionErrorDomain
code:kGREYInteractionElementNotFoundErrorCode
userInfo:userInfo];
}
- (void)grey_failInteraction:(NSString *)interactionName
exception:(NSString *)defaultExceptionName
error:(NSError *)error {
NSParameterAssert(interactionName);
NSParameterAssert(defaultExceptionName);
NSParameterAssert(error);

// Throw an exception if userProvidedError isn't provided and the assertion failed.
if (!userProvidedError) {
if ([assertionError.domain isEqualToString:kGREYInteractionErrorDomain]) {
NSString *searchAPIInfo = [self grey_searchActionDescription];

// Customize exception based on the error.
switch (assertionError.code) {
case kGREYInteractionElementNotFoundErrorCode: {
NSString *reason =
[NSString stringWithFormat:@"Assertion '%@' was not performed because no UI element "
@"matching %@ was found.",
[assertion name], _elementMatcher];
I_GREYRegisterFailure(kGREYNoMatchingElementException, reason, @"%@Complete Error: %@",
searchAPIInfo, assertionError);
return NO;
}
case kGREYInteractionMultipleElementsMatchedErrorCode: {
NSString *reason =
[NSString stringWithFormat:@"Assertion '%@' was not performed because multiple UI "
@"elements matching %@ were found. Use grey_allOf(...) to "
@"create a more specific matcher.",
[assertion name], _elementMatcher];
I_GREYRegisterFailure(kGREYMultipleElementsFoundException, reason,
@"%@Complete Error: %@", searchAPIInfo, assertionError);
return NO;
}
if ([error.domain isEqualToString:kGREYInteractionErrorDomain]) {
NSString *searchActionDescription = @"";
if (_searchAction) {
searchActionDescription =
[NSString stringWithFormat:@"Search action: %@. \nSearch action element matcher: %@.\n",
_searchAction, _searchActionElementMatcher];
}
// Customize exception based on the error.
switch (error.code) {
case kGREYInteractionElementNotFoundErrorCode: {
NSString *reason =
[NSString stringWithFormat:@"%@ was not performed because no UI element matching %@ "
@"was found.", interactionName, _elementMatcher];
I_GREYRegisterFailure(kGREYNoMatchingElementException, reason, @"%@Complete Error: %@",
searchActionDescription, error);
}
case kGREYInteractionMultipleElementsMatchedErrorCode: {
NSString *reason =
[NSString stringWithFormat:@"%@ was not performed because multiple UI elements "
@"matching %@ were found. Use grey_allOf(...) to create a "
@"more specific matcher.", interactionName, _elementMatcher];
// We print the localized description here to prevent multiple matchers info from being
// displayed twice - once in the error and once in the userInfo dict.
I_GREYRegisterFailure(kGREYMultipleElementsFoundException, reason, @"%@Complete Error: %@",
searchActionDescription, error.localizedDescription);
}
case kGREYInteractionSystemAlertViewIsDisplayedErrorCode: {
NSString *reason =
[NSString stringWithFormat:@"%@ was not performed because a system alert view was "
@"displayed.", interactionName];
I_GREYRegisterFailure(defaultExceptionName, reason, @"%@Complete Error: %@",
searchActionDescription, error);
}
}

// TODO: Add unique failure messages for timeout and other well-known reason for failure.
NSString *reason = [NSString stringWithFormat:@"Assertion '%@' failed.", assertion.name];
I_GREYRegisterFailure(kGREYAssertionFailedException, reason,
@"Element matcher: %@\nComplete Error: %@",
_elementMatcher, assertionError);
} else {
*userProvidedError = assertionError;
}

return NO;
// TODO: Add unique failure messages for timeout and other well-known reasons for failure.
NSString *reason = [NSString stringWithFormat:@"%@ failed.", interactionName];
I_GREYRegisterFailure(defaultExceptionName, reason, @"Element matcher: %@\nComplete Error: %@",
_elementMatcher, error);
}

/**
Expand All @@ -525,16 +456,4 @@ - (NSError *)grey_errorForMultipleMatchingElements:(NSArray *)matchingElements {
userInfo:@{ NSLocalizedDescriptionKey : description }];
}

/**
* @return A String description of the current search action.
*/
- (NSString *)grey_searchActionDescription {
if (_searchAction) {
return [NSString stringWithFormat:@"Search action: %@. \nSearch action element matcher: %@.\n",
_searchAction, _searchActionElementMatcher];
} else {
return @"";
}
}

@end

0 comments on commit 03a851f

Please sign in to comment.