diff --git a/Additions/UIApplication-KIFAdditions.h b/Additions/UIApplication-KIFAdditions.h index f9ec79c13..4ced93751 100644 --- a/Additions/UIApplication-KIFAdditions.h +++ b/Additions/UIApplication-KIFAdditions.h @@ -75,6 +75,11 @@ CF_EXPORT SInt32 KIFRunLoopRunInModeRelativeToAnimationSpeed(CFStringRef mode, C */ - (NSArray *)windowsWithKeyWindow; +/// @discussion The first responders are ordered in the reverse order of @c -windowsWithKeyWindow +/// to return in order of nearest visually. +/// @returns All first responders in the application. +- (NSArray *)firstResponders; + /*! The current Core Animation speed of the keyWindow's CALayer. */ diff --git a/Additions/UIApplication-KIFAdditions.m b/Additions/UIApplication-KIFAdditions.m index b26a4364d..d1fee373d 100644 --- a/Additions/UIApplication-KIFAdditions.m +++ b/Additions/UIApplication-KIFAdditions.m @@ -8,6 +8,7 @@ // which Square, Inc. licenses this file to you. #import "UIApplication-KIFAdditions.h" +#import "UIWindow-KIFAdditions.h" #import "LoadableCategory.h" #import "UIView-KIFAdditions.h" #import "NSError-KIFAdditions.h" @@ -99,6 +100,20 @@ - (UIWindow *)dimmingViewWindow; return [self getWindowForSubviewClass:@"UIDimmingView"]; } +- (NSArray *)firstResponders; +{ + NSMutableArray *responders = [NSMutableArray array]; + + for (UIWindow *window in [[self windowsWithKeyWindow] reverseObjectEnumerator]) { + UIResponder *responder = window.firstResponder; + if (responder) { + [responders addObject:responder]; + } + } + + return [responders copy]; +} + - (UIWindow *)getWindowForSubviewClass:(NSString*)className; { for (UIWindow *window in self.windowsWithKeyWindow) { diff --git a/Classes/KIFUITestActor.h b/Classes/KIFUITestActor.h index f14fa9464..1290fd8a1 100644 --- a/Classes/KIFUITestActor.h +++ b/Classes/KIFUITestActor.h @@ -678,7 +678,7 @@ typedef NS_ENUM(NSUInteger, KIFPullToRefreshTiming) { /*! @abstract Waits until a view or accessibility element is the first responder. @discussion The first responder is found by searching the view hierarchy of the application's - main window and its accessibility label is compared to the given value. If they match, the + windows and its accessibility label is compared to the given value. If they match, the step returns success else it will attempt to wait until they do. @param label The accessibility label of the element to wait for. */ @@ -687,7 +687,7 @@ typedef NS_ENUM(NSUInteger, KIFPullToRefreshTiming) { /*! @abstract Waits until a view or accessibility element is the first responder. @discussion The first responder is found by searching the view hierarchy of the application's - main window and its accessibility label is compared to the given value. If they match, the + windows and its accessibility label is compared to the given value. If they match, the step returns success else it will attempt to wait until they do. @param label The accessibility label of the element to wait for. @param traits The accessibility traits of the element to wait for. Elements that do not include at least these traits are ignored. diff --git a/Classes/KIFUITestActor.m b/Classes/KIFUITestActor.m index 4c0b1ad7c..332438b36 100644 --- a/Classes/KIFUITestActor.m +++ b/Classes/KIFUITestActor.m @@ -220,7 +220,7 @@ - (void)waitForAnimationsToFinishWithTimeout:(NSTimeInterval)timeout stabilizati [self waitForTimeInterval:maximumWaitingTimeInterval relativeToAnimationSpeed:YES]; } } else { - + // Wait for the view to stabilize and give them a chance to start animations before we wait for them. [self waitForTimeInterval:stabilizationTime relativeToAnimationSpeed:YES]; maximumWaitingTimeInterval -= stabilizationTime; @@ -468,24 +468,38 @@ - (void)enterTextIntoCurrentFirstResponder:(NSString *)text fallbackView:(UIView [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock: ^(NSString *characterString,NSRange substringRange,NSRange enclosingRange,BOOL * stop) - { + { if (![KIFTypist enterCharacter:characterString]) { + NSLog(@"KIF: Unable to find keyboard key for %@. Will attempt to insert manually.", characterString); + // Attempt to cheat if we couldn't find the character - UIView * fallback = fallbackView; - if (!fallback) { - UIResponder *firstResponder = [[[UIApplication sharedApplication] keyWindow] firstResponder]; + NSMutableArray *fallbackViews = [NSMutableArray array]; - if ([firstResponder isKindOfClass:[UIView class]]) { - fallback = (UIView *)firstResponder; + if (fallbackView) { + [fallbackViews addObject:fallbackView]; + } else { + [fallbackViews addObjectsFromArray:[[UIApplication sharedApplication] firstResponders]]; + } + + for (id fallback in [fallbackViews copy]) { + if (![fallback isKindOfClass:[UITextField class]] && + ![fallback isKindOfClass:[UITextView class]] && + ![fallback isKindOfClass:[UISearchBar class]]) { + [fallbackViews removeObject:fallback]; } } - if ([fallback isKindOfClass:[UITextField class]] || [fallback isKindOfClass:[UITextView class]] || [fallback isKindOfClass:[UISearchBar class]]) { - NSLog(@"KIF: Unable to find keyboard key for %@. Inserting manually.", characterString); - [(UITextField *)fallback setText:[[(UITextField *)fallback text] stringByAppendingString:characterString]]; - } else { - [self failWithError:[NSError KIFErrorWithFormat:@"Failed to find key for character \"%@\"", characterString] stopTest:YES]; + UITextField *fallbackTextView = fallbackViews.firstObject; + if (fallbackTextView) { + if (fallbackViews.count > 1) { + NSLog(@"KIF: Found multiple possible fallback views for entering text: %@. Will use: %@", fallbackViews, fallbackTextView); + } + + [fallbackTextView setText:[[fallbackTextView text] stringByAppendingString:characterString]]; + return; } + + [self failWithError:[NSError KIFErrorWithFormat:@"Failed to find key for character \"%@\"", characterString] stopTest:YES]; } }]; @@ -551,9 +565,17 @@ - (void)expectView:(UIView *)view toContainText:(NSString *)expectedResult - (void)clearTextFromFirstResponder { @autoreleasepool { - UIView *firstResponder = (id)[[[UIApplication sharedApplication] keyWindow] firstResponder]; - if ([firstResponder isKindOfClass:[UIView class]]) { - [self clearTextFromElement:(UIAccessibilityElement *)firstResponder inView:firstResponder]; + NSArray *firstResponders = [[UIApplication sharedApplication] firstResponders]; + + for (UIResponder *firstResponder in firstResponders) { + if ([firstResponder isKindOfClass:[UIView class]]) { + if (firstResponders.count > 1) { + NSLog(@"KIF: Found multiple first responders while attempting to clear text: %@. Will use: %@.", firstResponders, firstResponder); + } + + [self clearTextFromElement:(UIAccessibilityElement *)firstResponder inView:(UIView *)firstResponder]; + break; + } } } } @@ -976,7 +998,7 @@ - (void)choosePhotoInAlbum:(NSString *)albumName atRow:(NSInteger)row column:(NS // Tap the desired photo in the grid // TODO: This currently only works for the first page of photos. It should scroll appropriately at some point. - UIAccessibilityElement *headerElt = [[UIApplication sharedApplication] accessibilityElementMatchingBlock:^(UIAccessibilityElement *element) { + UIAccessibilityElement *headerElt = [[UIApplication sharedApplication] accessibilityElementMatchingBlock:^(UIAccessibilityElement *element) { return [NSStringFromClass(element.class) isEqual:@"UINavigationItemButtonView"]; }]; UIView* headerView = [UIAccessibilityElement viewContainingAccessibilityElement:headerElt]; @@ -1095,56 +1117,56 @@ - (void)swipeViewWithAccessibilityLabel:(NSString *)label value:(NSString *)valu - (void)swipeAccessibilityElement:(UIAccessibilityElement *)element inView:(UIView *)viewToSwipe inDirection:(KIFSwipeDirection)direction { // The original version of this came from http://groups.google.com/group/kif-framework/browse_thread/thread/df3f47eff9f5ac8c - + const NSUInteger kNumberOfPointsInSwipePath = 20; - + // Within this method, all geometry is done in the coordinate system of the view to swipe. CGRect elementFrame = [self elementFrameForElement:element andView:viewToSwipe]; CGPoint swipeStart = CGPointCenteredInRect(elementFrame); KIFDisplacement swipeDisplacement = [self _displacementForSwipingInDirection:direction]; - + [viewToSwipe dragFromPoint:swipeStart displacement:swipeDisplacement steps:kNumberOfPointsInSwipePath]; } - (void)pullToRefreshViewWithAccessibilityLabel:(NSString *)label { - [self pullToRefreshViewWithAccessibilityLabel:label value:nil pullDownDuration:0 traits:UIAccessibilityTraitNone]; + [self pullToRefreshViewWithAccessibilityLabel:label value:nil pullDownDuration:0 traits:UIAccessibilityTraitNone]; } - (void)pullToRefreshViewWithAccessibilityLabel:(NSString *)label pullDownDuration:(KIFPullToRefreshTiming) pullDownDuration { - [self pullToRefreshViewWithAccessibilityLabel:label value:nil pullDownDuration:pullDownDuration traits:UIAccessibilityTraitNone]; + [self pullToRefreshViewWithAccessibilityLabel:label value:nil pullDownDuration:pullDownDuration traits:UIAccessibilityTraitNone]; } - (void)pullToRefreshViewWithAccessibilityLabel:(NSString *)label value:(NSString *)value { - [self pullToRefreshViewWithAccessibilityLabel:label value:value pullDownDuration:0 traits:UIAccessibilityTraitNone]; + [self pullToRefreshViewWithAccessibilityLabel:label value:value pullDownDuration:0 traits:UIAccessibilityTraitNone]; } - (void)pullToRefreshViewWithAccessibilityLabel:(NSString *)label value:(NSString *)value pullDownDuration:(KIFPullToRefreshTiming) pullDownDuration traits:(UIAccessibilityTraits)traits { - UIView *viewToSwipe = nil; - UIAccessibilityElement *element = nil; + UIView *viewToSwipe = nil; + UIAccessibilityElement *element = nil; - [self waitForAccessibilityElement:&element view:&viewToSwipe withLabel:label value:value traits:traits tappable:YES]; + [self waitForAccessibilityElement:&element view:&viewToSwipe withLabel:label value:value traits:traits tappable:YES]; - [self pullToRefreshAccessibilityElement:element inView:viewToSwipe pullDownDuration:pullDownDuration]; + [self pullToRefreshAccessibilityElement:element inView:viewToSwipe pullDownDuration:pullDownDuration]; } - (void)pullToRefreshAccessibilityElement:(UIAccessibilityElement *)element inView:(UIView *)viewToSwipe pullDownDuration:(KIFPullToRefreshTiming) pullDownDuration { - //Based on swipeAccessibilityElement + //Based on swipeAccessibilityElement - const NSUInteger kNumberOfPointsInSwipePath = pullDownDuration ? pullDownDuration : KIFPullToRefreshInAboutAHalfSecond; + const NSUInteger kNumberOfPointsInSwipePath = pullDownDuration ? pullDownDuration : KIFPullToRefreshInAboutAHalfSecond; // Can handle only the touchable space. CGRect elementFrame = [viewToSwipe convertRect:viewToSwipe.bounds toView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; CGPoint swipeStart = CGPointCenteredInRect(elementFrame); - CGPoint swipeDisplacement = CGPointMake(CGRectGetMidX(elementFrame), CGRectGetMaxY(elementFrame)); + CGPoint swipeDisplacement = CGPointMake(CGRectGetMidX(elementFrame), CGRectGetMaxY(elementFrame)); - [viewToSwipe dragFromPoint:swipeStart displacement:swipeDisplacement steps:kNumberOfPointsInSwipePath]; + [viewToSwipe dragFromPoint:swipeStart displacement:swipeDisplacement steps:kNumberOfPointsInSwipePath]; } - (void)scrollViewWithAccessibilityLabel:(NSString *)label byFractionOfSizeHorizontal:(CGFloat)horizontalFraction vertical:(CGFloat)verticalFraction @@ -1182,13 +1204,24 @@ - (void)scrollAccessibilityElement:(UIAccessibilityElement *)element inView:(UIV - (void)waitForFirstResponderWithAccessibilityLabel:(NSString *)label { [self runBlock:^KIFTestStepResult(NSError **error) { - UIResponder *firstResponder = [[[UIApplication sharedApplication] keyWindow] firstResponder]; - if ([firstResponder isKindOfClass:NSClassFromString(@"UISearchBarTextField")]) { - do { - firstResponder = [(UIView *)firstResponder superview]; - } while (firstResponder && ![firstResponder isKindOfClass:[UISearchBar class]]); + BOOL didMatch = NO; + NSArray *firstResponders = [[UIApplication sharedApplication] firstResponders]; + + for (UIResponder *firstResponder in firstResponders) { + UIResponder *foundResponder = firstResponder; + if ([foundResponder isKindOfClass:NSClassFromString(@"UISearchBarTextField")]) { + do { + foundResponder = [(UIView *)foundResponder superview]; + } while (foundResponder && ![foundResponder isKindOfClass:[UISearchBar class]]); + } + + if (foundResponder.accessibilityLabel == label || [foundResponder.accessibilityLabel isEqualToString:label]) { + didMatch = YES; + break; + } } - KIFTestWaitCondition([[firstResponder accessibilityLabel] isEqualToString:label], error, @"Expected accessibility label for first responder to be '%@', got '%@'", label, [firstResponder accessibilityLabel]); + + KIFTestWaitCondition(didMatch, error, @"Expected to find a first responder with the accessibility label '%@', got: %@", label, firstResponders); return KIFTestStepResultSuccess; }]; @@ -1197,13 +1230,26 @@ - (void)waitForFirstResponderWithAccessibilityLabel:(NSString *)label - (void)waitForFirstResponderWithAccessibilityLabel:(NSString *)label traits:(UIAccessibilityTraits)traits { [self runBlock:^KIFTestStepResult(NSError **error) { - UIResponder *firstResponder = [[[UIApplication sharedApplication] keyWindow] firstResponder]; - - NSString *foundLabel = firstResponder.accessibilityLabel; + BOOL didMatchLabel = NO; + BOOL didMatchTraits = NO; + NSArray *firstResponders = [[UIApplication sharedApplication] firstResponders]; + + for (UIResponder *firstResponder in firstResponders) { + if (firstResponder.accessibilityLabel == label || [firstResponder.accessibilityLabel isEqualToString:label]) { + didMatchLabel = YES; + } + if (firstResponder.accessibilityTraits & traits) { + didMatchTraits = YES; + } + + if (didMatchLabel && didMatchTraits) { + break; + } + } // foundLabel == label checks for the case where both are nil. - KIFTestWaitCondition(foundLabel == label || [foundLabel isEqualToString:label], error, @"Expected accessibility label for first responder to be '%@', got '%@'", label, foundLabel); - KIFTestWaitCondition(firstResponder.accessibilityTraits & traits, error, @"Found first responder with accessibility label, but not traits. First responder: %@", firstResponder); + KIFTestWaitCondition(didMatchLabel, error, @"Expected to find a first responder with the accessibility label '%@', got: %@", label, firstResponders); + KIFTestWaitCondition(didMatchTraits, error, @"Expected to find a first responder with accessibility traits, got: %@", firstResponders); return KIFTestStepResultSuccess; }]; @@ -1392,43 +1438,43 @@ - (void)deactivateAppForDuration:(NSTimeInterval)duration { -(void) tapStepperWithAccessibilityLabel: (NSString *)accessibilityLabel increment: (KIFStepperDirection) stepperDirection { - @autoreleasepool { - UIView *view = nil; - UIAccessibilityElement *element = nil; - [self waitForAccessibilityElement:&element view:&view withLabel:accessibilityLabel value:nil traits:UIAccessibilityTraitNone tappable:YES]; - [self tapStepperWithAccessibilityElement:element increment:stepperDirection inView:view]; - } + @autoreleasepool { + UIView *view = nil; + UIAccessibilityElement *element = nil; + [self waitForAccessibilityElement:&element view:&view withLabel:accessibilityLabel value:nil traits:UIAccessibilityTraitNone tappable:YES]; + [self tapStepperWithAccessibilityElement:element increment:stepperDirection inView:view]; + } } //inspired by http://www.raywenderlich.com/61419/ios-ui-testing-with-kif - (void)tapStepperWithAccessibilityElement:(UIAccessibilityElement *)element increment: (KIFStepperDirection) stepperDirection inView:(UIView *)view { - [self runBlock:^KIFTestStepResult(NSError **error) { + [self runBlock:^KIFTestStepResult(NSError **error) { - KIFTestWaitCondition(view.isUserInteractionActuallyEnabled, error, @"View is not enabled for interaction: %@", view); + KIFTestWaitCondition(view.isUserInteractionActuallyEnabled, error, @"View is not enabled for interaction: %@", view); CGPoint stepperPointToTap = [self tappablePointInElement:element andView:view]; - switch (stepperDirection) - { - case KIFStepperDirectionIncrement: - stepperPointToTap.x += CGRectGetWidth(view.frame) / 4; - break; - case KIFStepperDirectionDecrement: - stepperPointToTap.x -= CGRectGetWidth(view.frame) / 4; - break; - } + switch (stepperDirection) + { + case KIFStepperDirectionIncrement: + stepperPointToTap.x += CGRectGetWidth(view.frame) / 4; + break; + case KIFStepperDirectionDecrement: + stepperPointToTap.x -= CGRectGetWidth(view.frame) / 4; + break; + } - // This is mostly redundant of the test in _accessibilityElementWithLabel: - KIFTestWaitCondition(!isnan(stepperPointToTap.x), error, @"View is not tappable: %@", view); - [view tapAtPoint:stepperPointToTap]; + // This is mostly redundant of the test in _accessibilityElementWithLabel: + KIFTestWaitCondition(!isnan(stepperPointToTap.x), error, @"View is not tappable: %@", view); + [view tapAtPoint:stepperPointToTap]; - KIFTestCondition(![view canBecomeFirstResponder] || [view isDescendantOfFirstResponder], error, @"Failed to make the view into the first responder: %@", view); + KIFTestCondition(![view canBecomeFirstResponder] || [view isDescendantOfFirstResponder], error, @"Failed to make the view into the first responder: %@", view); - return KIFTestStepResultSuccess; - }]; + return KIFTestStepResultSuccess; + }]; - [self waitForAnimationsToFinish]; + [self waitForAnimationsToFinish]; } - (CGRect) elementFrameForElement:(UIAccessibilityElement *)element andView:(UIView *)view diff --git a/Classes/KIFUIViewTestActor.h b/Classes/KIFUIViewTestActor.h index b4979c644..b5cf68cf8 100644 --- a/Classes/KIFUIViewTestActor.h +++ b/Classes/KIFUIViewTestActor.h @@ -195,8 +195,8 @@ extern NSString *const inputFieldTestString; /*! @abstract Waits until a view or accessibility element matching the tester's search predicate is the first responder. - @discussion The first responder is found by searching the view hierarchy of the application's - main window and its accessibility label is compared to the given value. If they match, the + @discussion The first responder is found by searching the view hierarchy of all the application's + windows and its accessibility label is compared to the given value. If they match, the step returns success else it will attempt to wait until they do. */ - (void)waitToBecomeFirstResponder; diff --git a/Classes/KIFUIViewTestActor.m b/Classes/KIFUIViewTestActor.m index 31287b5b5..bf34c172f 100644 --- a/Classes/KIFUIViewTestActor.m +++ b/Classes/KIFUIViewTestActor.m @@ -210,9 +210,17 @@ - (void)waitToBecomeTappable; - (void)waitToBecomeFirstResponder; { [self runBlock:^KIFTestStepResult(NSError **error) { - UIResponder *firstResponder = [[[UIApplication sharedApplication] keyWindow] firstResponder]; + BOOL didMatch = NO; + NSArray *firstResponders = [[UIApplication sharedApplication] firstResponders]; - KIFTestWaitCondition([self.predicate evaluateWithObject:firstResponder], error, @"Expected first responder to match '%@', got '%@'", self.predicate, firstResponder); + for (UIResponder *firstResponder in firstResponders) { + if ([self.predicate evaluateWithObject:firstResponder]) { + didMatch = YES; + break; + } + } + + KIFTestWaitCondition(didMatch, error, @"Expected to find a first responder matching '%@', got: %@", self.predicate.kifPredicateDescription, firstResponders); return KIFTestStepResultSuccess; }]; } diff --git a/IdentifierTests/KIFUITestActor-IdentifierTests.m b/IdentifierTests/KIFUITestActor-IdentifierTests.m index 5a930747b..3c8489d5b 100644 --- a/IdentifierTests/KIFUITestActor-IdentifierTests.m +++ b/IdentifierTests/KIFUITestActor-IdentifierTests.m @@ -10,6 +10,7 @@ #import "KIFUITestActor-IdentifierTests.h" #import "UIAccessibilityElement-KIFAdditions.h" #import "NSError-KIFAdditions.h" +#import "UIApplication-KIFAdditions.h" #import "UIWindow-KIFAdditions.h" @implementation KIFUITestActor (IdentifierTests) @@ -52,47 +53,47 @@ - (UIView *)waitForViewWithAccessibilityIdentifier:(NSString *)accessibilityIden - (void)longPressViewWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier duration:(NSTimeInterval)duration { - @autoreleasepool { - UIView *view = nil; - UIAccessibilityElement *element = nil; - [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; - [self longPressAccessibilityElement:element inView:view duration:duration]; - } + @autoreleasepool { + UIView *view = nil; + UIAccessibilityElement *element = nil; + [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; + [self longPressAccessibilityElement:element inView:view duration:duration]; + } } - (void)enterText:(NSString *)text intoViewWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier { - return [self enterText:text intoViewWithAccessibilityIdentifier:accessibilityIdentifier expectedResult:nil]; + return [self enterText:text intoViewWithAccessibilityIdentifier:accessibilityIdentifier expectedResult:nil]; } - (void)enterText:(NSString *)text intoViewWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier expectedResult:(NSString *)expectedResult { - UIView *view = nil; - UIAccessibilityElement *element = nil; - - [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; + UIView *view = nil; + UIAccessibilityElement *element = nil; + + [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; [self enterText:text intoElement:element inView:view expectedResult:expectedResult]; } - (void)clearTextFromViewWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier { - UIView *view = nil; - UIAccessibilityElement *element = nil; - - [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; - [self clearTextFromElement:element inView:view]; + UIView *view = nil; + UIAccessibilityElement *element = nil; + + [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; + [self clearTextFromElement:element inView:view]; } - (void)clearTextFromAndThenEnterText:(NSString *)text intoViewWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier { - [self clearTextFromViewWithAccessibilityIdentifier:accessibilityIdentifier]; - [self enterText:text intoViewWithAccessibilityIdentifier:accessibilityIdentifier]; + [self clearTextFromViewWithAccessibilityIdentifier:accessibilityIdentifier]; + [self enterText:text intoViewWithAccessibilityIdentifier:accessibilityIdentifier]; } - (void)clearTextFromAndThenEnterText:(NSString *)text intoViewWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier expectedResult:(NSString *)expectedResult { - [self clearTextFromViewWithAccessibilityIdentifier:accessibilityIdentifier]; - [self enterText:text intoViewWithAccessibilityIdentifier:accessibilityIdentifier expectedResult:expectedResult]; + [self clearTextFromViewWithAccessibilityIdentifier:accessibilityIdentifier]; + [self enterText:text intoViewWithAccessibilityIdentifier:accessibilityIdentifier expectedResult:expectedResult]; } - (void)setText:(NSString *)text intoViewWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier @@ -108,78 +109,84 @@ - (void)setText:(NSString *)text intoViewWithAccessibilityIdentifier:(NSString * - (void)setOn:(BOOL)switchIsOn forSwitchWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier { - UIView *view = nil; - UIAccessibilityElement *element = nil; - - [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; - - if (![view isKindOfClass:[UISwitch class]]) { - [self failWithError:[NSError KIFErrorWithFormat:@"View with accessibility identifier \"%@\" is a %@, not a UISwitch", accessibilityIdentifier, NSStringFromClass([view class])] stopTest:YES]; - } - - UISwitch *switchView = (UISwitch *)view; - - // No need to switch it if it's already in the correct position - if (switchView.isOn == switchIsOn) { - return; - } - - [self tapAccessibilityElement:element inView:view]; - - // If we succeeded, stop the test. - if (switchView.isOn == switchIsOn) { - return; - } - - NSLog(@"Faking turning switch %@ with accessibility identifier %@", switchIsOn ? @"ON" : @"OFF", accessibilityIdentifier); - [switchView setOn:switchIsOn animated:YES]; - [switchView sendActionsForControlEvents:UIControlEventValueChanged]; - [self waitForTimeInterval:0.5]; - - // We gave it our best shot. Fail the test. - if (switchView.isOn != switchIsOn) { - [self failWithError:[NSError KIFErrorWithFormat:@"Failed to toggle switch to \"%@\"; instead, it was \"%@\"", switchIsOn ? @"ON" : @"OFF", switchView.on ? @"ON" : @"OFF"] stopTest:YES]; - } - + UIView *view = nil; + UIAccessibilityElement *element = nil; + + [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; + + if (![view isKindOfClass:[UISwitch class]]) { + [self failWithError:[NSError KIFErrorWithFormat:@"View with accessibility identifier \"%@\" is a %@, not a UISwitch", accessibilityIdentifier, NSStringFromClass([view class])] stopTest:YES]; + } + + UISwitch *switchView = (UISwitch *)view; + + // No need to switch it if it's already in the correct position + if (switchView.isOn == switchIsOn) { + return; + } + + [self tapAccessibilityElement:element inView:view]; + + // If we succeeded, stop the test. + if (switchView.isOn == switchIsOn) { + return; + } + + NSLog(@"Faking turning switch %@ with accessibility identifier %@", switchIsOn ? @"ON" : @"OFF", accessibilityIdentifier); + [switchView setOn:switchIsOn animated:YES]; + [switchView sendActionsForControlEvents:UIControlEventValueChanged]; + [self waitForTimeInterval:0.5]; + + // We gave it our best shot. Fail the test. + if (switchView.isOn != switchIsOn) { + [self failWithError:[NSError KIFErrorWithFormat:@"Failed to toggle switch to \"%@\"; instead, it was \"%@\"", switchIsOn ? @"ON" : @"OFF", switchView.on ? @"ON" : @"OFF"] stopTest:YES]; + } + } - (void)setValue:(float)value forSliderWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier { - UISlider *slider = nil; - UIAccessibilityElement *element = nil; - [self waitForAccessibilityElement:&element view:&slider withIdentifier:accessibilityIdentifier tappable:YES]; - - if (![slider isKindOfClass:[UISlider class]]) { - [self failWithError:[NSError KIFErrorWithFormat:@"View with accessibility identifier \"%@\" is a %@, not a UISlider", accessibilityIdentifier, NSStringFromClass([slider class])] stopTest:YES]; - } - [self setValue:value forSlider:slider]; + UISlider *slider = nil; + UIAccessibilityElement *element = nil; + [self waitForAccessibilityElement:&element view:&slider withIdentifier:accessibilityIdentifier tappable:YES]; + + if (![slider isKindOfClass:[UISlider class]]) { + [self failWithError:[NSError KIFErrorWithFormat:@"View with accessibility identifier \"%@\" is a %@, not a UISlider", accessibilityIdentifier, NSStringFromClass([slider class])] stopTest:YES]; + } + [self setValue:value forSlider:slider]; } - (void)waitForFirstResponderWithAccessibilityIdentifier:(NSString *)accessibilityIdentifier { - [self runBlock:^KIFTestStepResult(NSError **error) { - UIResponder *firstResponder = [[[UIApplication sharedApplication] keyWindow] firstResponder]; - if ([firstResponder isKindOfClass:NSClassFromString(@"UISearchBarTextField")]) { - do { - firstResponder = [(UIView *)firstResponder superview]; - } while (firstResponder && ![firstResponder isKindOfClass:[UISearchBar class]]); - } - UIResponder* firstResponderIdentification = nil; - if ([firstResponder conformsToProtocol:@protocol(UIAccessibilityIdentification)]) - { - firstResponderIdentification = (UIResponder*)firstResponder; - } - else - { - [self failWithError:[NSError KIFErrorWithFormat:@"First responder does not conform to UIAccessibilityIdentification %@", NSStringFromClass([firstResponder class])] stopTest:YES]; - - } - KIFTestWaitCondition([[firstResponderIdentification accessibilityIdentifier] isEqualToString:accessibilityIdentifier], - error, @"Expected accessibility identifier for first responder to be '%@', got '%@'", - accessibilityIdentifier, [firstResponderIdentification accessibilityIdentifier]); - - return KIFTestStepResultSuccess; - }]; + [self runBlock:^KIFTestStepResult(NSError **error) { + BOOL didMatch = NO; + NSArray *firstResponders = [[UIApplication sharedApplication] firstResponders]; + + for (UIResponder *firstResponder in firstResponders) { + UIResponder *foundResponder = firstResponder; + if ([foundResponder isKindOfClass:NSClassFromString(@"UISearchBarTextField")]) { + do { + foundResponder = [(UIView *)foundResponder superview]; + } while (foundResponder && ![foundResponder isKindOfClass:[UISearchBar class]]); + } + + NSString *foundIdentifier = nil; + if ([foundResponder conformsToProtocol:@protocol(UIAccessibilityIdentification)]) { + foundIdentifier = [(UIResponder *)foundResponder accessibilityIdentifier]; + } + + if ([foundIdentifier isEqualToString:accessibilityIdentifier]) { + didMatch = YES; + break; + } + } + + KIFTestWaitCondition(didMatch, + error, @"Expected to find a first responder with accessibility identifier '%@', got: %@", + accessibilityIdentifier, firstResponders); + + return KIFTestStepResultSuccess; + }]; } - (BOOL) tryFindingViewWithAccessibilityIdentifier:(NSString *) accessibilityIdentifier @@ -200,31 +207,31 @@ - (void)swipeViewWithAccessibilityIdentifier:(NSString *)identifier inDirection: - (void)pullToRefreshViewWithAccessibilityIdentifier:(NSString *)identifier { - UIView *viewToSwipe = nil; - UIAccessibilityElement *element = nil; + UIView *viewToSwipe = nil; + UIAccessibilityElement *element = nil; - [self waitForAccessibilityElement: &element view:&viewToSwipe withIdentifier:identifier tappable:NO]; + [self waitForAccessibilityElement: &element view:&viewToSwipe withIdentifier:identifier tappable:NO]; - [self pullToRefreshAccessibilityElement:element inView:viewToSwipe pullDownDuration:0]; + [self pullToRefreshAccessibilityElement:element inView:viewToSwipe pullDownDuration:0]; } - (void)pullToRefreshViewWithAccessibilityIdentifier:(NSString *)identifier pullDownDuration:(KIFPullToRefreshTiming) pullDownDuration { - UIView *viewToSwipe = nil; - UIAccessibilityElement *element = nil; + UIView *viewToSwipe = nil; + UIAccessibilityElement *element = nil; - [self waitForAccessibilityElement: &element view:&viewToSwipe withIdentifier:identifier tappable:NO]; + [self waitForAccessibilityElement: &element view:&viewToSwipe withIdentifier:identifier tappable:NO]; - [self pullToRefreshAccessibilityElement:element inView:viewToSwipe pullDownDuration:pullDownDuration]; + [self pullToRefreshAccessibilityElement:element inView:viewToSwipe pullDownDuration:pullDownDuration]; } -(void) tapStepperWithAccessibilityIdentifier: (NSString *)accessibilityIdentifier increment: (KIFStepperDirection) stepperDirection { - @autoreleasepool { - UIView *view = nil; - UIAccessibilityElement *element = nil; - [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; - [self tapStepperWithAccessibilityElement:element increment:stepperDirection inView:view]; - } + @autoreleasepool { + UIView *view = nil; + UIAccessibilityElement *element = nil; + [self waitForAccessibilityElement:&element view:&view withIdentifier:accessibilityIdentifier tappable:YES]; + [self tapStepperWithAccessibilityElement:element increment:stepperDirection inView:view]; + } } @end