From a6672dc3defe0bbcce7dc765b344664b3dee7072 Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Sun, 22 Sep 2024 22:05:12 -0700 Subject: [PATCH 1/5] Configure CI to run on Xcode 16 --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0086c1bb..a9410b55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,6 @@ jobs: - name: Validate podspec run: pod lib lint - build_macos14: runs-on: macos-14 strategy: @@ -23,7 +22,9 @@ jobs: - { xcode_version: '14.3.1', simulator: 'name=iPad Air (5th generation),OS=16.4', run_extra_validations: 'false' } - { xcode_version: '14.3.1', simulator: 'name=iPhone 14,OS=16.4', run_extra_validations: 'false' } - { xcode_version: '15.4', simulator: 'name=iPad Air (5th generation),OS=17.5', run_extra_validations: 'false' } - - { xcode_version: '15.4', simulator: 'name=iPhone 15,OS=17.5', run_extra_validations: 'true' } + - { xcode_version: '15.4', simulator: 'name=iPhone 15,OS=17.5', run_extra_validations: 'false' } + - { xcode_version: '16.0', simulator: 'name=iPad Air (5th generation),OS=18.0', run_extra_validations: 'false' } + - { xcode_version: '16.0', simulator: 'name=iPhone 16,OS=18.0', run_extra_validations: 'true' } steps: - name: Checkout Project uses: actions/checkout@v1 From 111c52dc45d0f609c12f28dc1a57d5f4f2c5f1d9 Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Wed, 25 Sep 2024 22:29:02 -0700 Subject: [PATCH 2/5] Ensure that the UITextEffectsWindow also gets layer speed multipliers applied The UITextEffectsWindow directly uses a private initializer, bypassing the public initializer. The `[UIWindow init]` implementation will call through to this private designated initializer, so overriding both is unnecessary. I've added an assertion to the swizzle helpers to prevent this from silently failing in the future if the private APIs change. --- Sources/KIF/Additions/NSObject+KIFSwizzle.m | 3 +++ Sources/KIF/Additions/UIWindow+KIFSwizzle.m | 20 ++++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Sources/KIF/Additions/NSObject+KIFSwizzle.m b/Sources/KIF/Additions/NSObject+KIFSwizzle.m index dfcbd978..62d900da 100644 --- a/Sources/KIF/Additions/NSObject+KIFSwizzle.m +++ b/Sources/KIF/Additions/NSObject+KIFSwizzle.m @@ -17,6 +17,9 @@ + (void)swizzleSEL:(SEL)originalSEL withSEL:(SEL)swizzledSEL Method originalMethod = class_getInstanceMethod(class, originalSEL); Method swizzledMethod = class_getInstanceMethod(class, swizzledSEL); + NSAssert(originalMethod != nil, @"The original method for selector '%@' couldn't be found", NSStringFromSelector(originalSEL)); + NSAssert(swizzledMethod != nil, @"The swizzled method for selector '%@' couldn't be found", NSStringFromSelector(swizzledSEL)); + method_exchangeImplementations(originalMethod, swizzledMethod); } diff --git a/Sources/KIF/Additions/UIWindow+KIFSwizzle.m b/Sources/KIF/Additions/UIWindow+KIFSwizzle.m index 9273fae6..919012b5 100644 --- a/Sources/KIF/Additions/UIWindow+KIFSwizzle.m +++ b/Sources/KIF/Additions/UIWindow+KIFSwizzle.m @@ -9,23 +9,31 @@ #import "UIApplication-KIFAdditions.h" #import "NSObject+KIFSwizzle.h" +@interface UIWindow () + +- (instancetype)_initWithFrame:(CGRect)rect debugName:(NSString *)debugName windowScene:(UIWindowScene *)windowScene API_AVAILABLE(ios(13)); + +@end + + @implementation UIWindow (KIFSwizzle) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - [self swizzleSEL:@selector(init) withSEL:@selector(swizzle_init)]; - [self swizzleSEL:@selector(becomeKeyWindow) withSEL:@selector(swizzle_becomeKeyWindow)]; - if (@available(iOS 13.0, *)) { - [self swizzleSEL:@selector(initWithWindowScene:) withSEL:@selector(swizzle_initWithWindowScene:)]; + if (@available(iOS 13, *)) { + [self swizzleSEL:@selector(_initWithFrame:debugName:windowScene:) withSEL:@selector(swizzle__initWithFrame:debugName:windowScene:)]; + } else { + [self swizzleSEL:@selector(init) withSEL:@selector(swizzle_init)]; } + [self swizzleSEL:@selector(becomeKeyWindow) withSEL:@selector(swizzle_becomeKeyWindow)]; }); } -- (instancetype)swizzle_initWithWindowScene:(UIWindowScene *)scene API_AVAILABLE(ios(13)) +- (instancetype)swizzle__initWithFrame:(CGRect)rect debugName:(NSString *)debugName windowScene:(UIWindowScene *)windowScene API_AVAILABLE(ios(13)) { - UIWindow *window = [self swizzle_initWithWindowScene:scene]; + UIWindow *window = [self swizzle__initWithFrame:rect debugName:debugName windowScene:windowScene]; window.layer.speed = [UIApplication sharedApplication].animationSpeed; return window; From e263dac0c7141cb71a5e3ac1a53e122ad762287e Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Thu, 26 Sep 2024 00:48:46 -0700 Subject: [PATCH 3/5] Improve detection of in progress animations There were cases where we were not identifying animations that were still in progress. For example, if I turn on the simulator slow animations setting, we don't wait a sufficient amount of time for animations to complete. This was consistently showing up when running -[TypingTests testEnteringTextIntoFirstResponder]`. As this test was failing on Xcode 16 in CI, it seems like this change is needed to get KIF working reliably. --- Sources/KIF/Additions/CALayer-KIFAdditions.m | 26 +++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Sources/KIF/Additions/CALayer-KIFAdditions.m b/Sources/KIF/Additions/CALayer-KIFAdditions.m index ddc88308..9ea6b2f3 100644 --- a/Sources/KIF/Additions/CALayer-KIFAdditions.m +++ b/Sources/KIF/Additions/CALayer-KIFAdditions.m @@ -41,12 +41,32 @@ - (BOOL)hasAnimations [layer.animationKeys enumerateObjectsUsingBlock:^(NSString *animationKey, NSUInteger idx, BOOL *innerStop) { CAAnimation *animation = [layer animationForKey:animationKey]; - double beginTime = [animation beginTime]; + double completionTime = [animation KIF_completionTime]; - // Ignore long running animations (> 1 minute duration) - if (currentTime >= beginTime && completionTime < currentTime + 60 && currentTime < completionTime) { + // Ignore long running animations (> 1 minute duration), as we don't want to wait on them + if (completionTime > currentTime + 60) { + return; + } + + // If an animation is set to be removed on completion, it must still be in progress if we enumerated it + // This is the default behavior for animations, so we should often hit this codepath. + if ([animation isRemovedOnCompletion]) { result = YES; + } else if ([animation.delegate isKindOfClass:NSClassFromString(@"UIViewAnimationState")]) { + // Use a private property on the private class to determine if the animation state has completed + BOOL animationDidStopSent = [[(NSObject *)animation.delegate valueForKey:@"_animationDidStopSent"] boolValue]; + + if (!animationDidStopSent) { + result = YES; + } + } else if (currentTime > completionTime) { + // Otherwise, use the completion time to determine if the animation has been completed. + // This doesn't seem to always be exactly right however. + result = YES; + } + + if (result) { *innerStop = YES; *stop = YES; } From cbf90e4b71a4953d94f82fdc76f74b2a98fd2165 Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Thu, 26 Sep 2024 00:54:14 -0700 Subject: [PATCH 4/5] Set .25s min for animation stabilization wait time, regardless of layer speed When animations are set to 100x speed, the text field popover control is presented without animations. This causes issues in the call to waitForAnimations, because the presentation still happens after some delay. This is a crude hack to prevent significantly sped up animations from failing to tap on popovers. --- Sources/KIF/Classes/KIFUITestActor.m | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/KIF/Classes/KIFUITestActor.m b/Sources/KIF/Classes/KIFUITestActor.m index cbabd652..6a1f8ea1 100644 --- a/Sources/KIF/Classes/KIFUITestActor.m +++ b/Sources/KIF/Classes/KIFUITestActor.m @@ -230,9 +230,8 @@ - (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]; + [self waitForTimeInterval:MAX(stabilizationTime / [UIApplication sharedApplication].animationSpeed, 0.25) relativeToAnimationSpeed:NO]; maximumWaitingTimeInterval -= stabilizationTime; NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate]; From 25645e87c27728e4c36fde42f05ca5063d718819 Mon Sep 17 00:00:00 2001 From: Justin Martin Date: Thu, 26 Sep 2024 01:00:18 -0700 Subject: [PATCH 5/5] Speed up many tests by decreasing longPress durations from 2s to 1s --- Tests/AccessibilityIdentifierTests.m | 6 +++--- Tests/AccessibilityIdentifierTests_ViewTestActor.m | 4 ++-- Tests/LongPressTests.m | 6 +++--- Tests/LongPressTests_ViewTestActor.m | 6 +++--- Tests/TypingTests.m | 6 +++--- Tests/TypingTests_ViewTestActor.m | 4 ++-- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Tests/AccessibilityIdentifierTests.m b/Tests/AccessibilityIdentifierTests.m index b3ff0432..b94becbf 100644 --- a/Tests/AccessibilityIdentifierTests.m +++ b/Tests/AccessibilityIdentifierTests.m @@ -49,14 +49,14 @@ - (void)testWaitingForAbscenceOfViewWithAccessibilityIdentifier - (void)testLongPressingViewWithAccessibilityIdentifier { [tester tapViewWithAccessibilityIdentifier:@"idGreeting"]; - [tester longPressViewWithAccessibilityIdentifier:@"idGreeting" duration:2]; + [tester longPressViewWithAccessibilityIdentifier:@"idGreeting" duration:1]; [tester tapViewWithAccessibilityLabel:@"Select All"]; } - (void)testEnteringTextIntoViewWithAccessibilityIdentifier { [tester tapViewWithAccessibilityIdentifier:@"idGreeting"]; - [tester longPressViewWithAccessibilityIdentifier:@"idGreeting" duration:2]; + [tester longPressViewWithAccessibilityIdentifier:@"idGreeting" duration:1]; [tester waitForAnimationsToFinish]; [tester tapViewWithAccessibilityLabel:@"Select All"]; [tester tapViewWithAccessibilityLabel:@"Cut"]; @@ -77,7 +77,7 @@ - (void)testClearingAndEnteringTextIntoViewWithAccessibilityLabel - (void)testSettingTextIntoViewWithAccessibilityIdentifier { UIView *greetingView = [tester waitForViewWithAccessibilityIdentifier:@"idGreeting"]; - [tester longPressViewWithAccessibilityIdentifier:@"idGreeting" duration:2]; + [tester longPressViewWithAccessibilityIdentifier:@"idGreeting" duration:1]; [tester setText:@"Yo" intoViewWithAccessibilityIdentifier:@"idGreeting"]; [tester expectView:greetingView toContainText:@"Yo"]; [tester setText:@"Hello" intoViewWithAccessibilityIdentifier:@"idGreeting"]; diff --git a/Tests/AccessibilityIdentifierTests_ViewTestActor.m b/Tests/AccessibilityIdentifierTests_ViewTestActor.m index ce96ff3a..240253bf 100644 --- a/Tests/AccessibilityIdentifierTests_ViewTestActor.m +++ b/Tests/AccessibilityIdentifierTests_ViewTestActor.m @@ -49,14 +49,14 @@ - (void)testWaitingForAbscenceOfViewWithAccessibilityIdentifier - (void)testLongPressingViewWithAccessibilityIdentifier { [[viewTester usingIdentifier:@"idGreeting"] tap]; - [[viewTester usingIdentifier:@"idGreeting"] longPressWithDuration:2]; + [[viewTester usingIdentifier:@"idGreeting"] longPressWithDuration:1]; [[viewTester usingLabel:@"Select All"] tap]; } - (void)testEnteringTextIntoViewWithAccessibilityIdentifier { [[viewTester usingIdentifier:@"idGreeting"] tap]; - [[viewTester usingIdentifier:@"idGreeting"] longPressWithDuration:2]; + [[viewTester usingIdentifier:@"idGreeting"] longPressWithDuration:1]; [[viewTester usingLabel:@"Select All"] tap]; [[viewTester usingLabel:@"Cut"] tap]; [[viewTester usingIdentifier:@"idGreeting"] enterText:@"Yo"]; diff --git a/Tests/LongPressTests.m b/Tests/LongPressTests.m index d8e3f8d6..08f8449e 100644 --- a/Tests/LongPressTests.m +++ b/Tests/LongPressTests.m @@ -28,21 +28,21 @@ - (void)afterEach - (void)testLongPressingViewWithAccessibilityLabel { [tester tapViewWithAccessibilityLabel:@"Greeting"]; - [tester longPressViewWithAccessibilityLabel:@"Greeting" duration:2]; + [tester longPressViewWithAccessibilityLabel:@"Greeting" duration:1]; [tester tapViewWithAccessibilityLabel:@"Select All"]; } - (void)testLongPressingViewViewWithTraits { [tester tapViewWithAccessibilityLabel:@"Greeting"]; - [tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:2]; + [tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:1]; [tester tapViewWithAccessibilityLabel:@"Select All"]; } - (void)testLongPressingViewViewWithValue { [tester tapViewWithAccessibilityLabel:@"Greeting"]; - [tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" traits:UIAccessibilityTraitUpdatesFrequently duration:2]; + [tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" traits:UIAccessibilityTraitUpdatesFrequently duration:1]; [tester tapViewWithAccessibilityLabel:@"Select All"]; } diff --git a/Tests/LongPressTests_ViewTestActor.m b/Tests/LongPressTests_ViewTestActor.m index d472cd38..33461478 100644 --- a/Tests/LongPressTests_ViewTestActor.m +++ b/Tests/LongPressTests_ViewTestActor.m @@ -28,21 +28,21 @@ - (void)afterEach - (void)testLongPressingViewWithAccessibilityLabel { [[viewTester usingLabel:@"Greeting"] tap]; - [[viewTester usingLabel:@"Greeting"] longPressWithDuration:2]; + [[viewTester usingLabel:@"Greeting"] longPressWithDuration:1]; [[viewTester usingLabel:@"Select All"] tap]; } - (void)testLongPressingViewViewWithTraits { [[viewTester usingLabel:@"Greeting"] tap]; - [[[viewTester usingLabel:@"Greeting"] usingValue:@"Hello"] longPressWithDuration:2]; + [[[viewTester usingLabel:@"Greeting"] usingValue:@"Hello"] longPressWithDuration:1]; [[viewTester usingLabel:@"Select All"] tap]; } - (void)testLongPressingViewViewWithValue { [[viewTester usingLabel:@"Greeting"] tap]; - [[[[viewTester usingLabel:@"Greeting"] usingValue:@"Hello"] usingTraits:UIAccessibilityTraitUpdatesFrequently] longPressWithDuration:2]; + [[[[viewTester usingLabel:@"Greeting"] usingValue:@"Hello"] usingTraits:UIAccessibilityTraitUpdatesFrequently] longPressWithDuration:1]; [[viewTester usingLabel:@"Select All"] tap]; } diff --git a/Tests/TypingTests.m b/Tests/TypingTests.m index 2b9f4b25..d1681c02 100644 --- a/Tests/TypingTests.m +++ b/Tests/TypingTests.m @@ -39,7 +39,7 @@ - (void)testMissingFirstResponder - (void)testEnteringTextIntoFirstResponder { [tester tapViewWithAccessibilityLabel:@"Greeting"]; - [tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:2]; + [tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:1]; [tester tapViewWithAccessibilityLabel:@"Select All"]; [tester enterTextIntoCurrentFirstResponder:@"Yo"]; [tester waitForViewWithAccessibilityLabel:@"Greeting" value:@"Yo" traits:UIAccessibilityTraitNone]; @@ -53,7 +53,7 @@ - (void)testFailingToEnterTextIntoFirstResponder - (void)testEnteringTextIntoViewWithAccessibilityLabel { [tester tapViewWithAccessibilityLabel:@"Greeting"]; - [tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:2]; + [tester longPressViewWithAccessibilityLabel:@"Greeting" value:@"Hello" duration:1]; [tester tapViewWithAccessibilityLabel:@"Select All"]; [tester tapViewWithAccessibilityLabel:@"Cut"]; [tester enterText:@"Yo" intoViewWithAccessibilityLabel:@"Greeting"]; @@ -110,7 +110,7 @@ - (void)testClearingALongTextField - (void)testSettingTextIntoViewWithAccessibilityLabel { UIView *greetingView = [tester waitForViewWithAccessibilityLabel:@"Greeting"]; - [tester longPressViewWithAccessibilityLabel:@"Greeting" duration:2]; + [tester longPressViewWithAccessibilityLabel:@"Greeting" duration:1]; [tester setText:@"Yo" intoViewWithAccessibilityLabel:@"Greeting"]; [tester expectView:greetingView toContainText:@"Yo"]; [tester setText:@"Hello" intoViewWithAccessibilityLabel:@"Greeting"]; diff --git a/Tests/TypingTests_ViewTestActor.m b/Tests/TypingTests_ViewTestActor.m index a907ecda..e5ef5f8b 100644 --- a/Tests/TypingTests_ViewTestActor.m +++ b/Tests/TypingTests_ViewTestActor.m @@ -40,7 +40,7 @@ - (void)testMissingFirstResponder - (void)testEnteringTextIntoFirstResponder { [tester tapViewWithAccessibilityLabel:@"Greeting"]; - [[[viewTester usingLabel:@"Greeting"] usingValue:@"Hello"] longPressWithDuration:2]; + [[[viewTester usingLabel:@"Greeting"] usingValue:@"Hello"] longPressWithDuration:1]; [[viewTester usingLabel:@"Select All"] tap]; [[viewTester usingFirstResponder] enterText:@"Yo"]; [[[viewTester usingLabel:@"Greeting"] usingValue:@"Yo"] waitForView]; @@ -54,7 +54,7 @@ - (void)testFailingToEnterTextIntoFirstResponder - (void)testEnteringTextIntoViewWithAccessibilityLabel { [tester tapViewWithAccessibilityLabel:@"Greeting"]; - [[[viewTester usingLabel:@"Greeting"] usingValue:@"Hello"] longPressWithDuration:2]; + [[[viewTester usingLabel:@"Greeting"] usingValue:@"Hello"] longPressWithDuration:1]; [[viewTester usingLabel:@"Select All"] tap]; [[viewTester usingLabel:@"Cut"] tap]; [[viewTester usingLabel:@"Greeting"] enterText:@"Yo"];