Skip to content

Commit 353e9a2

Browse files
authored
[image_picker_ios] Update UITests for Xcode 15/iOS 17 (#5176)
With Xcode 15, XCTest's `addUIInterruptionMonitorWithDescription` sometimes doesn't work. To fix, I added a fallback to query for the buttons in the permissions dialog. Also, the Allow text in the permissions dialog is different in iOS 17 than previous versions. Fixes flutter/flutter#136747 Example passing on Xcode 15 with iOS 17 simulator: https://ci.chromium.org/ui/p/flutter/builders/try/Mac_arm64%20ios_platform_tests_shard_3%20master/7604/overview
1 parent f95d534 commit 353e9a2

File tree

2 files changed

+91
-12
lines changed

2 files changed

+91
-12
lines changed

packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromGalleryUITests.m

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,30 @@ @interface ImagePickerFromGalleryUITests : XCTestCase
1111

1212
@property(nonatomic, strong) XCUIApplication *app;
1313

14+
@property(nonatomic, assign) BOOL interceptedPermissionInterruption;
15+
1416
@end
1517

1618
@implementation ImagePickerFromGalleryUITests
1719

1820
- (void)setUp {
1921
[super setUp];
20-
// Delete the app if already exists, to test permission popups
2122

2223
self.continueAfterFailure = NO;
2324
self.app = [[XCUIApplication alloc] init];
25+
if (@available(iOS 13.4, *)) {
26+
// Reset the authorization status for Photos to test permission popups
27+
[self.app resetAuthorizationStatusForResource:XCUIProtectedResourcePhotos];
28+
}
2429
[self.app launch];
30+
self.interceptedPermissionInterruption = NO;
2531
__weak typeof(self) weakSelf = self;
2632
[self addUIInterruptionMonitorWithDescription:@"Permission popups"
2733
handler:^BOOL(XCUIElement *_Nonnull interruptingElement) {
2834
if (@available(iOS 14, *)) {
2935
XCUIElement *allPhotoPermission =
3036
interruptingElement
31-
.buttons[@"Allow Access to All Photos"];
37+
.buttons[weakSelf.allowAccessPermissionText];
3238
if (![allPhotoPermission waitForExistenceWithTimeout:
3339
kElementWaitingTime]) {
3440
os_log_error(OS_LOG_DEFAULT, "%@",
@@ -50,6 +56,7 @@ - (void)setUp {
5056
}
5157
[ok tap];
5258
}
59+
weakSelf.interceptedPermissionInterruption = YES;
5360
return YES;
5461
}];
5562
}
@@ -59,6 +66,46 @@ - (void)tearDown {
5966
[self.app terminate];
6067
}
6168

69+
- (NSString *)allowAccessPermissionText {
70+
NSString *fullAccessButtonText = @"Allow Access to All Photos";
71+
if (@available(iOS 17, *)) {
72+
fullAccessButtonText = @"Allow Full Access";
73+
}
74+
return fullAccessButtonText;
75+
}
76+
77+
- (void)handlePermissionInterruption {
78+
// addUIInterruptionMonitorWithDescription is only invoked when trying to interact with an element
79+
// (the app in this case) the alert is blocking. We expect a permission popup here so do a swipe
80+
// up action (which should be harmless).
81+
[self.app swipeUp];
82+
83+
if (@available(iOS 17, *)) {
84+
// addUIInterruptionMonitorWithDescription does not work consistently on Xcode 15 simulators, so
85+
// use a backup method of accepting permissions popup.
86+
87+
if (self.interceptedPermissionInterruption == YES) {
88+
return;
89+
}
90+
91+
// If cancel button exists, permission has already been given.
92+
XCUIElement *cancelButton = self.app.buttons[@"Cancel"].firstMatch;
93+
if ([cancelButton waitForExistenceWithTimeout:kElementWaitingTime]) {
94+
return;
95+
}
96+
97+
XCUIApplication *springboardApp =
98+
[[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"];
99+
XCUIElement *allowButton = springboardApp.buttons[self.allowAccessPermissionText];
100+
if (![allowButton waitForExistenceWithTimeout:kElementWaitingTime]) {
101+
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
102+
XCTFail(@"Failed due to not able to find Allow Access button with %@ seconds",
103+
@(kElementWaitingTime));
104+
}
105+
[allowButton tap];
106+
}
107+
}
108+
62109
- (void)testCancel {
63110
// Find and tap on the pick from gallery button.
64111
XCUIElement *imageFromGalleryButton =
@@ -80,9 +127,7 @@ - (void)testCancel {
80127

81128
[pickButton tap];
82129

83-
// There is a known bug where the permission popups interruption won't get fired until a tap
84-
// happened in the app. We expect a permission popup so we do a tap here.
85-
[self.app tap];
130+
[self handlePermissionInterruption];
86131

87132
// Find and tap on the `Cancel` button.
88133
XCUIElement *cancelButton = self.app.buttons[@"Cancel"].firstMatch;
@@ -151,9 +196,7 @@ - (void)launchPickerAndPickWithMaxWidth:(NSNumber *)maxWidth
151196
}
152197
[pickButton tap];
153198

154-
// There is a known bug where the permission popups interruption won't get fired until a tap
155-
// happened in the app. We expect a permission popup so we do a tap here.
156-
[self.app tap];
199+
[self handlePermissionInterruption];
157200

158201
// Find an image and tap on it. (IOS 14 UI, images are showing directly)
159202
XCUIElement *aImage;

packages/image_picker/image_picker_ios/example/ios/RunnerUITests/ImagePickerFromLimitedGalleryUITests.m

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,21 @@ @interface ImagePickerFromLimitedGalleryUITests : XCTestCase
1111

1212
@property(nonatomic, strong) XCUIApplication *app;
1313

14+
@property(nonatomic, assign) BOOL interceptedPermissionInterruption;
15+
1416
@end
1517

1618
@implementation ImagePickerFromLimitedGalleryUITests
1719

1820
- (void)setUp {
1921
[super setUp];
20-
// Delete the app if already exists, to test permission popups
2122

2223
self.continueAfterFailure = NO;
2324
self.app = [[XCUIApplication alloc] init];
25+
if (@available(iOS 13.4, *)) {
26+
// Reset the authorization status for Photos to test permission popups
27+
[self.app resetAuthorizationStatusForResource:XCUIProtectedResourcePhotos];
28+
}
2429
[self.app launch];
2530
__weak typeof(self) weakSelf = self;
2631
[self addUIInterruptionMonitorWithDescription:@"Permission popups"
@@ -37,6 +42,7 @@ - (void)setUp {
3742
@(kLimitedElementWaitingTime));
3843
}
3944
[limitedPhotoPermission tap];
45+
weakSelf.interceptedPermissionInterruption = YES;
4046
return YES;
4147
}];
4248
}
@@ -46,6 +52,38 @@ - (void)tearDown {
4652
[self.app terminate];
4753
}
4854

55+
- (void)handlePermissionInterruption {
56+
// addUIInterruptionMonitorWithDescription is only invoked when trying to interact with an element
57+
// (the app in this case) the alert is blocking. We expect a permission popup here so do a swipe
58+
// up action (which should be harmless).
59+
[self.app swipeUp];
60+
61+
if (@available(iOS 17, *)) {
62+
// addUIInterruptionMonitorWithDescription does not work consistently on Xcode 15 simulators, so
63+
// use a backup method of accepting permissions popup.
64+
65+
if (self.interceptedPermissionInterruption == YES) {
66+
return;
67+
}
68+
69+
// If cancel button exists, permission has already been given.
70+
XCUIElement *cancelButton = self.app.buttons[@"Cancel"].firstMatch;
71+
if ([cancelButton waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
72+
return;
73+
}
74+
75+
XCUIApplication *springboardApp =
76+
[[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"];
77+
XCUIElement *allowButton = springboardApp.buttons[@"Limit Access…"];
78+
if (![allowButton waitForExistenceWithTimeout:kLimitedElementWaitingTime]) {
79+
os_log_error(OS_LOG_DEFAULT, "%@", self.app.debugDescription);
80+
XCTFail(@"Failed due to not able to find Limit Access button with %@ seconds",
81+
@(kLimitedElementWaitingTime));
82+
}
83+
[allowButton tap];
84+
}
85+
}
86+
4987
// Test the `Select Photos` button which is available after iOS 14.
5088
- (void)testSelectingFromGallery API_AVAILABLE(ios(14)) {
5189
// Find and tap on the pick from gallery button.
@@ -66,9 +104,7 @@ - (void)testSelectingFromGallery API_AVAILABLE(ios(14)) {
66104
}
67105
[pickButton tap];
68106

69-
// There is a known bug where the permission popups interruption won't get fired until a tap
70-
// happened in the app. We expect a permission popup so we do a tap here.
71-
[self.app tap];
107+
[self handlePermissionInterruption];
72108

73109
// Find an image and tap on it.
74110
XCUIElement *aImage = [self.app.scrollViews.firstMatch.images elementBoundByIndex:1];

0 commit comments

Comments
 (0)