From 60f3b1ad9f3a76ac758c0dcbfd46cd7b76485c67 Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Thu, 14 Mar 2019 17:27:20 -0700 Subject: [PATCH] Elements don't become first responder fast enough When tapping an element, there is a check that ensures it becomes the first responder within a half second. This doesn't work for elements such as popover controls however, which only become first responder after the popover view finishes being presented (usually animated). In that case, there is a race condition as to whether the element will finish displaying before the polling interval completes. The fix here is to ensure that we wait for any animations to complete before trying to check that the element that has been tapped became first responder. One exception to this is when interacting with an accessibility element and not a view. In this case, the backing view for the may still be visible on screen, even though the element itself is not. An example of this is web views, where this would cause the test `-[WebViewTests testTappingLinks]` to fail. --- Classes/KIFUITestActor.m | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Classes/KIFUITestActor.m b/Classes/KIFUITestActor.m index 4953ce625..07fea7e59 100644 --- a/Classes/KIFUITestActor.m +++ b/Classes/KIFUITestActor.m @@ -316,8 +316,9 @@ - (void)tapViewWithAccessibilityLabel:(NSString *)label value:(NSString *)value - (void)tapAccessibilityElement:(UIAccessibilityElement *)element inView:(UIView *)view { + BOOL wasAccessibilityElementOnscreen = ((id)element != view) && !CGRectEqualToRect([element accessibilityFrame], CGRectZero); + [self runBlock:^KIFTestStepResult(NSError **error) { - KIFTestWaitCondition(view.isUserInteractionActuallyEnabled, error, @"View is not enabled for interaction: %@", view); CGPoint tappablePointInElement = [self tappablePointInElement:element andView:view]; @@ -336,14 +337,18 @@ - (void)tapAccessibilityElement:(UIAccessibilityElement *)element inView:(UIView return KIFTestStepResultSuccess; }]; + [self waitForAnimationsToFinish]; + // Controls might not synchronously become first-responders. Sometimes custom controls // may need to spin the runloop before reporting as the first responder. [self runBlock:^KIFTestStepResult(NSError *__autoreleasing *error) { - KIFTestWaitCondition(![view canBecomeFirstResponder] || [view isDescendantOfFirstResponder], error, @"Failed to make the view into the first responder: %@", view); + // When we're interacting with an accessibility element, it might go offscreen before we're able to do this check + BOOL isAccessibilityElementOffscreen = ((id)element != view) && CGRectEqualToRect([element accessibilityFrame], CGRectZero); + BOOL accessibilityElementDisappeared = wasAccessibilityElementOnscreen && isAccessibilityElementOffscreen; + + KIFTestWaitCondition(![view canBecomeFirstResponder] || ![view isProbablyTappable] || [view isDescendantOfFirstResponder] || accessibilityElementDisappeared, error, @"Failed to make the view into the first responder: %@", view); return KIFTestStepResultSuccess; } timeout:0.5]; - - [self waitForAnimationsToFinish]; } - (void)tapScreenAtPoint:(CGPoint)screenPoint