From 88de359498565d8138f934dea0836db869de3b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Egl=C4=ABtis?= Date: Tue, 3 Mar 2026 18:50:25 +0200 Subject: [PATCH 1/8] feat: add support for `action` and `camera` values for `mobile: pressButton` --- .../Categories/XCUIDevice+FBHelpers.h | 3 ++- .../Categories/XCUIDevice+FBHelpers.m | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h index 2f9c9d86f..7751f5ba0 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h @@ -98,7 +98,8 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) { /** Presses the corresponding hardware button on the device with duration. - @param buttonName One of the supported button names: volumeUp (real devices only), volumeDown (real device only), home + @param buttonName One of the supported button names: volumeUp (real devices only), volumeDown (real device only), + camera (supported iOS 16+ real devices only), action (supported iOS 16+ devices only), home @param duration Duration in seconds or nil. This argument works only on tvOS. When this argument is nil on tvOS, https://developer.apple.com/documentation/xctest/xcuiremote/1627476-pressbutton will be called. diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m index 660885630..d2a21d0a8 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m @@ -296,15 +296,34 @@ - (BOOL)fb_pressButton:(NSString *)buttonName dstButton = XCUIDeviceButtonHome; } [supportedButtonNames addObject:@"home"]; + + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0")) { + if ([self hasHardwareButton:XCUIDeviceButtonAction]) { + if ([buttonName.lowercaseString isEqualToString:@"action"]) { + dstButton = XCUIDeviceButtonAction; + } + [supportedButtonNames addObject:@"action"]; + } + } #if !TARGET_OS_SIMULATOR if ([buttonName.lowercaseString isEqualToString:@"volumeup"]) { dstButton = XCUIDeviceButtonVolumeUp; } + [supportedButtonNames addObject:@"volumeUp"]; + if ([buttonName.lowercaseString isEqualToString:@"volumedown"]) { dstButton = XCUIDeviceButtonVolumeDown; } - [supportedButtonNames addObject:@"volumeUp"]; [supportedButtonNames addObject:@"volumeDown"]; + + if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0")) { + if ([self hasHardwareButton:XCUIDeviceButtonCamera]) { + if ([buttonName.lowercaseString isEqualToString:@"camera"]) { + dstButton = XCUIDeviceButtonCamera; + } + [supportedButtonNames addObject:@"camera"]; + } + } #endif if (dstButton == 0) { From 9567bbb09165ee1f7534bbda138240fcc235fb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Egl=C4=ABtis?= Date: Tue, 3 Mar 2026 19:44:28 +0200 Subject: [PATCH 2/8] test: add scenario for pressing action button --- .../IntegrationTests/XCUIDeviceHelperTests.m | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m b/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m index 97908add4..cc207277a 100644 --- a/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m +++ b/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m @@ -186,6 +186,23 @@ - (void)testPressingSupportedButton XCTAssertNil(error); } +- (void)testPressingDeviceSpecificButton +{ + NSError *error; + BOOL hasActionButton = SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0") + && [XCUIDevice.sharedDevice hasHardwareButton:XCUIDeviceButtonAction]; + BOOL didPressButton = [XCUIDevice.sharedDevice fb_pressButton:@"action" + forDuration:nil + error:&error]; + if (hasActionButton) { + XCTAssertTrue(didPressButton); + XCTAssertNil(error); + } else { + XCTAssertFalse(didPressButton); + XCTAssertNotNil(error); + } +} + - (void)testPressingSupportedButtonNumber { NSError *error; From 77352bd19022b75891f85354a2744480a1241357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Egl=C4=ABtis?= Date: Tue, 3 Mar 2026 21:39:42 +0200 Subject: [PATCH 3/8] use compiler guards for older Xcodes --- .../Categories/XCUIDevice+FBHelpers.m | 24 +++++++++---------- .../IntegrationTests/XCUIDeviceHelperTests.m | 4 +++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m index d2a21d0a8..4e2004533 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m @@ -297,14 +297,14 @@ - (BOOL)fb_pressButton:(NSString *)buttonName } [supportedButtonNames addObject:@"home"]; - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0")) { - if ([self hasHardwareButton:XCUIDeviceButtonAction]) { - if ([buttonName.lowercaseString isEqualToString:@"action"]) { - dstButton = XCUIDeviceButtonAction; - } - [supportedButtonNames addObject:@"action"]; + #if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) + if (@available(iOS 16.0, *) && [self hasHardwareButton:XCUIDeviceButtonAction]) { + if ([buttonName.lowercaseString isEqualToString:@"action"]) { + dstButton = XCUIDeviceButtonAction; } + [supportedButtonNames addObject:@"action"]; } + #endif #if !TARGET_OS_SIMULATOR if ([buttonName.lowercaseString isEqualToString:@"volumeup"]) { dstButton = XCUIDeviceButtonVolumeUp; @@ -316,14 +316,14 @@ - (BOOL)fb_pressButton:(NSString *)buttonName } [supportedButtonNames addObject:@"volumeDown"]; - if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0")) { - if ([self hasHardwareButton:XCUIDeviceButtonCamera]) { - if ([buttonName.lowercaseString isEqualToString:@"camera"]) { - dstButton = XCUIDeviceButtonCamera; - } - [supportedButtonNames addObject:@"camera"]; + #if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) + if (@available(iOS 16.0, *) && [self hasHardwareButton:XCUIDeviceButtonCamera]) { + if ([buttonName.lowercaseString isEqualToString:@"camera"]) { + dstButton = XCUIDeviceButtonCamera; } + [supportedButtonNames addObject:@"camera"]; } + #endif #endif if (dstButton == 0) { diff --git a/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m b/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m index cc207277a..af2a63045 100644 --- a/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m +++ b/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m @@ -186,10 +186,11 @@ - (void)testPressingSupportedButton XCTAssertNil(error); } +#if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) - (void)testPressingDeviceSpecificButton { NSError *error; - BOOL hasActionButton = SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"16.0") + BOOL hasActionButton = @available(iOS 16.0, *) && [XCUIDevice.sharedDevice hasHardwareButton:XCUIDeviceButtonAction]; BOOL didPressButton = [XCUIDevice.sharedDevice fb_pressButton:@"action" forDuration:nil @@ -202,6 +203,7 @@ - (void)testPressingDeviceSpecificButton XCTAssertNotNil(error); } } +#endif - (void)testPressingSupportedButtonNumber { From 818d8d644ee2e6dc2af4f466d9c04296a2064958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Egl=C4=ABtis?= Date: Wed, 4 Mar 2026 16:39:20 +0200 Subject: [PATCH 4/8] add two helper methods to improve clarity --- .../Categories/XCUIDevice+FBHelpers.h | 8 +++ .../Categories/XCUIDevice+FBHelpers.m | 72 ++++++++++++++----- .../IntegrationTests/XCUIDeviceHelperTests.m | 5 +- 3 files changed, 62 insertions(+), 23 deletions(-) diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h index 7751f5ba0..db03b7a1f 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h @@ -95,6 +95,14 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) { */ - (BOOL)fb_openUrl:(NSString *)url withApplication:(NSString *)bundleId error:(NSError **)error; +/** + Checks if the device has a specific hardware button available. + + @param buttonName The name of the button to check (e.g., "home", "volumeUp", "volumeDown", "action", "camera") + @return YES if the button is available on the device, otherwise NO + */ +- (BOOL)fb_hasButton:(NSString *)buttonName; + /** Presses the corresponding hardware button on the device with duration. diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m index 4e2004533..7111bab85 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m @@ -210,6 +210,48 @@ - (BOOL)fb_activateSiriVoiceRecognitionWithText:(NSString *)text error:(NSError } } +- (BOOL)fb_hasButton:(NSString *)buttonName +{ + NSString *buttonNameLC = buttonName.lowercaseString; + + if ([buttonNameLC isEqualToString:@"home"]) { + return YES; + } + +#if !TARGET_OS_TV +#if !TARGET_OS_SIMULATOR + if ([buttonNameLC isEqualToString:@"volumeup"] || [buttonNameLC isEqualToString:@"volumedown"]) { + return YES; + } +#endif + +#if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) + if (@available(iOS 16.0, *)) { + if ([buttonNameLC isEqualToString:@"action"]) { + return [self hasHardwareButton:XCUIDeviceButtonAction]; + } +#if !TARGET_OS_SIMULATOR + if ([buttonNameLC isEqualToString:@"camera"]) { + return [self hasHardwareButton:XCUIDeviceButtonCamera]; + } +#endif + } +#endif +#endif + + return NO; +} + +- (void)fb_addButtonsIfAvailable:(NSArray *)buttonNames + toSupportedButtonNames:(NSMutableArray *)supportedButtonNames +{ + for (NSString *buttonName in buttonNames) { + if ([self fb_hasButton:buttonName]) { + [supportedButtonNames addObject:buttonName]; + } + } +} + - (BOOL)fb_pressButton:(NSString *)buttonName forDuration:(nullable NSNumber *)duration error:(NSError **)error @@ -290,42 +332,34 @@ - (BOOL)fb_pressButton:(NSString *)buttonName - (BOOL)fb_pressButton:(NSString *)buttonName error:(NSError **)error { + NSString *buttonNameLC = buttonName.lowercaseString; NSMutableArray *supportedButtonNames = [NSMutableArray array]; XCUIDeviceButton dstButton = 0; - if ([buttonName.lowercaseString isEqualToString:@"home"]) { + if ([buttonNameLC isEqualToString:@"home"]) { dstButton = XCUIDeviceButtonHome; } - [supportedButtonNames addObject:@"home"]; - #if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) - if (@available(iOS 16.0, *) && [self hasHardwareButton:XCUIDeviceButtonAction]) { - if ([buttonName.lowercaseString isEqualToString:@"action"]) { - dstButton = XCUIDeviceButtonAction; - } - [supportedButtonNames addObject:@"action"]; + if ([buttonNameLC isEqualToString:@"action"] && [self fb_hasButton:@"action"]) { + dstButton = XCUIDeviceButtonAction; } #endif #if !TARGET_OS_SIMULATOR - if ([buttonName.lowercaseString isEqualToString:@"volumeup"]) { + if ([buttonNameLC isEqualToString:@"volumeup"]) { dstButton = XCUIDeviceButtonVolumeUp; } - [supportedButtonNames addObject:@"volumeUp"]; - - if ([buttonName.lowercaseString isEqualToString:@"volumedown"]) { + if ([buttonNameLC isEqualToString:@"volumedown"]) { dstButton = XCUIDeviceButtonVolumeDown; } - [supportedButtonNames addObject:@"volumeDown"]; - #if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) - if (@available(iOS 16.0, *) && [self hasHardwareButton:XCUIDeviceButtonCamera]) { - if ([buttonName.lowercaseString isEqualToString:@"camera"]) { - dstButton = XCUIDeviceButtonCamera; - } - [supportedButtonNames addObject:@"camera"]; + if ([buttonNameLC isEqualToString:@"camera"] && [self fb_hasButton:@"camera"]) { + dstButton = XCUIDeviceButtonCamera; } #endif #endif + [self fb_addButtonsIfAvailable:@[@"home", @"action", @"volumeUp", @"volumeDown", @"camera"] + toSupportedButtonNames:supportedButtonNames]; + if (dstButton == 0) { return [[[FBErrorBuilder builder] withDescriptionFormat:@"The button '%@' is unknown. Only the following button names are supported: %@", buttonName, supportedButtonNames] diff --git a/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m b/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m index af2a63045..2f1b8a467 100644 --- a/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m +++ b/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m @@ -186,12 +186,10 @@ - (void)testPressingSupportedButton XCTAssertNil(error); } -#if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) - (void)testPressingDeviceSpecificButton { NSError *error; - BOOL hasActionButton = @available(iOS 16.0, *) - && [XCUIDevice.sharedDevice hasHardwareButton:XCUIDeviceButtonAction]; + BOOL hasActionButton = [XCUIDevice.sharedDevice fb_hasButton:@"action"]; BOOL didPressButton = [XCUIDevice.sharedDevice fb_pressButton:@"action" forDuration:nil error:&error]; @@ -203,7 +201,6 @@ - (void)testPressingDeviceSpecificButton XCTAssertNotNil(error); } } -#endif - (void)testPressingSupportedButtonNumber { From 2098b985874f6f209620f21756003fda3332ca97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Egl=C4=ABtis?= Date: Wed, 4 Mar 2026 17:07:13 +0200 Subject: [PATCH 5/8] use compiler guards that depend on the actual constant definition --- .../Categories/XCUIDevice+FBHelpers.m | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m index 7111bab85..4adbb52eb 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m @@ -225,20 +225,22 @@ - (BOOL)fb_hasButton:(NSString *)buttonName } #endif -#if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) if (@available(iOS 16.0, *)) { +#if defined(XCUIDeviceButtonAction) if ([buttonNameLC isEqualToString:@"action"]) { return [self hasHardwareButton:XCUIDeviceButtonAction]; } +#endif +#if defined(XCUIDeviceButtonCamera) #if !TARGET_OS_SIMULATOR if ([buttonNameLC isEqualToString:@"camera"]) { return [self hasHardwareButton:XCUIDeviceButtonCamera]; } #endif - } #endif + } #endif - + return NO; } @@ -338,11 +340,11 @@ - (BOOL)fb_pressButton:(NSString *)buttonName if ([buttonNameLC isEqualToString:@"home"]) { dstButton = XCUIDeviceButtonHome; } - #if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) +#if defined(XCUIDeviceButtonAction) if ([buttonNameLC isEqualToString:@"action"] && [self fb_hasButton:@"action"]) { dstButton = XCUIDeviceButtonAction; } - #endif +#endif #if !TARGET_OS_SIMULATOR if ([buttonNameLC isEqualToString:@"volumeup"]) { dstButton = XCUIDeviceButtonVolumeUp; @@ -350,11 +352,11 @@ - (BOOL)fb_pressButton:(NSString *)buttonName if ([buttonNameLC isEqualToString:@"volumedown"]) { dstButton = XCUIDeviceButtonVolumeDown; } - #if __clang_major__ >= 17 || (__clang_major__ == 16 && __clang_minor__ >= 3) +#if defined(XCUIDeviceButtonCamera) if ([buttonNameLC isEqualToString:@"camera"] && [self fb_hasButton:@"camera"]) { dstButton = XCUIDeviceButtonCamera; } - #endif +#endif #endif [self fb_addButtonsIfAvailable:@[@"home", @"action", @"volumeUp", @"volumeDown", @"camera"] From 8f4c7bd69d08d41a282c2332c4af045404ea36e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Egl=C4=ABtis?= Date: Wed, 4 Mar 2026 18:48:24 +0200 Subject: [PATCH 6/8] remove fb_hasButton from public headers --- WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h index db03b7a1f..7751f5ba0 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h @@ -95,14 +95,6 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) { */ - (BOOL)fb_openUrl:(NSString *)url withApplication:(NSString *)bundleId error:(NSError **)error; -/** - Checks if the device has a specific hardware button available. - - @param buttonName The name of the button to check (e.g., "home", "volumeUp", "volumeDown", "action", "camera") - @return YES if the button is available on the device, otherwise NO - */ -- (BOOL)fb_hasButton:(NSString *)buttonName; - /** Presses the corresponding hardware button on the device with duration. From 32a676de095983b357c0ba829272d6b36160eab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Egl=C4=ABtis?= Date: Wed, 4 Mar 2026 21:35:56 +0200 Subject: [PATCH 7/8] move addButtonsIfAvailable, restore headers --- .../Categories/XCUIDevice+FBHelpers.h | 8 ++++++++ .../Categories/XCUIDevice+FBHelpers.m | 20 +++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h index 7751f5ba0..db03b7a1f 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h @@ -95,6 +95,14 @@ typedef NS_ENUM(NSUInteger, FBUIInterfaceAppearance) { */ - (BOOL)fb_openUrl:(NSString *)url withApplication:(NSString *)bundleId error:(NSError **)error; +/** + Checks if the device has a specific hardware button available. + + @param buttonName The name of the button to check (e.g., "home", "volumeUp", "volumeDown", "action", "camera") + @return YES if the button is available on the device, otherwise NO + */ +- (BOOL)fb_hasButton:(NSString *)buttonName; + /** Presses the corresponding hardware button on the device with duration. diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m index 4adbb52eb..d5d0170de 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m @@ -244,16 +244,6 @@ - (BOOL)fb_hasButton:(NSString *)buttonName return NO; } -- (void)fb_addButtonsIfAvailable:(NSArray *)buttonNames - toSupportedButtonNames:(NSMutableArray *)supportedButtonNames -{ - for (NSString *buttonName in buttonNames) { - if ([self fb_hasButton:buttonName]) { - [supportedButtonNames addObject:buttonName]; - } - } -} - - (BOOL)fb_pressButton:(NSString *)buttonName forDuration:(nullable NSNumber *)duration error:(NSError **)error @@ -440,4 +430,14 @@ - (BOOL)fb_clearSimulatedLocation:(NSError **)error } #endif +- (void)fb_addButtonsIfAvailable:(NSArray *)buttonNames + toSupportedButtonNames:(NSMutableArray *)supportedButtonNames +{ + for (NSString *buttonName in buttonNames) { + if ([self fb_hasButton:buttonName]) { + [supportedButtonNames addObject:buttonName]; + } + } +} + @end From 94207802f8eb45cfa710b4aa455b49fd47cbdf4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgars=20Egl=C4=ABtis?= Date: Wed, 4 Mar 2026 23:02:57 +0200 Subject: [PATCH 8/8] use singleton initialiser method --- .../Categories/XCUIDevice+FBHelpers.m | 115 +++++++----------- 1 file changed, 43 insertions(+), 72 deletions(-) diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m index d5d0170de..6335b3a60 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m @@ -26,6 +26,41 @@ static const NSTimeInterval FBHomeButtonCoolOffTime = 1.; static const NSTimeInterval FBScreenLockTimeout = 5.; +NSDictionary *availableButtonNames(void) { + static dispatch_once_t onceToken; + static NSDictionary *result; + dispatch_once(&onceToken, ^{ + NSMutableDictionary *buttons = [NSMutableDictionary dictionary]; + + // Home button is always available + buttons[@"home"] = @(XCUIDeviceButtonHome); + +#if !TARGET_OS_TV +#if !TARGET_OS_SIMULATOR + buttons[@"volumeup"] = @(XCUIDeviceButtonVolumeUp); + buttons[@"volumedown"] = @(XCUIDeviceButtonVolumeDown); +#endif + + if (@available(iOS 16.0, *)) { +#if defined(XCUIDeviceButtonAction) + if ([XCUIDevice.sharedDevice hasHardwareButton:XCUIDeviceButtonAction]) { + buttons[@"action"] = @(XCUIDeviceButtonAction); + } +#endif +#if defined(XCUIDeviceButtonCamera) +#if !TARGET_OS_SIMULATOR + if ([XCUIDevice.sharedDevice hasHardwareButton:XCUIDeviceButtonCamera]) { + buttons[@"camera"] = @(XCUIDeviceButtonCamera); + } +#endif +#endif + } +#endif + result = [buttons copy]; + }); + return result; +} + @implementation XCUIDevice (FBHelpers) static bool fb_isLocked; @@ -212,36 +247,7 @@ - (BOOL)fb_activateSiriVoiceRecognitionWithText:(NSString *)text error:(NSError - (BOOL)fb_hasButton:(NSString *)buttonName { - NSString *buttonNameLC = buttonName.lowercaseString; - - if ([buttonNameLC isEqualToString:@"home"]) { - return YES; - } - -#if !TARGET_OS_TV -#if !TARGET_OS_SIMULATOR - if ([buttonNameLC isEqualToString:@"volumeup"] || [buttonNameLC isEqualToString:@"volumedown"]) { - return YES; - } -#endif - - if (@available(iOS 16.0, *)) { -#if defined(XCUIDeviceButtonAction) - if ([buttonNameLC isEqualToString:@"action"]) { - return [self hasHardwareButton:XCUIDeviceButtonAction]; - } -#endif -#if defined(XCUIDeviceButtonCamera) -#if !TARGET_OS_SIMULATOR - if ([buttonNameLC isEqualToString:@"camera"]) { - return [self hasHardwareButton:XCUIDeviceButtonCamera]; - } -#endif -#endif - } -#endif - - return NO; + return availableButtonNames()[buttonName.lowercaseString] != nil; } - (BOOL)fb_pressButton:(NSString *)buttonName @@ -304,7 +310,7 @@ - (BOOL)fb_pressButton:(NSString *)buttonName if (remoteButton == -1) { return [[[FBErrorBuilder builder] - withDescriptionFormat:@"The button '%@' is unknown. Only the following button names are supported: %@", buttonName, supportedButtonNames] + withDescriptionFormat:@"The button '%@' is not supported. The device under test only supports the following buttons: %@", buttonName, supportedButtonNames] buildError:error]; } @@ -324,40 +330,15 @@ - (BOOL)fb_pressButton:(NSString *)buttonName - (BOOL)fb_pressButton:(NSString *)buttonName error:(NSError **)error { - NSString *buttonNameLC = buttonName.lowercaseString; - NSMutableArray *supportedButtonNames = [NSMutableArray array]; - XCUIDeviceButton dstButton = 0; - if ([buttonNameLC isEqualToString:@"home"]) { - dstButton = XCUIDeviceButtonHome; - } -#if defined(XCUIDeviceButtonAction) - if ([buttonNameLC isEqualToString:@"action"] && [self fb_hasButton:@"action"]) { - dstButton = XCUIDeviceButtonAction; - } -#endif -#if !TARGET_OS_SIMULATOR - if ([buttonNameLC isEqualToString:@"volumeup"]) { - dstButton = XCUIDeviceButtonVolumeUp; - } - if ([buttonNameLC isEqualToString:@"volumedown"]) { - dstButton = XCUIDeviceButtonVolumeDown; - } -#if defined(XCUIDeviceButtonCamera) - if ([buttonNameLC isEqualToString:@"camera"] && [self fb_hasButton:@"camera"]) { - dstButton = XCUIDeviceButtonCamera; - } -#endif -#endif - - [self fb_addButtonsIfAvailable:@[@"home", @"action", @"volumeUp", @"volumeDown", @"camera"] - toSupportedButtonNames:supportedButtonNames]; - - if (dstButton == 0) { + NSDictionary *availableButtons = availableButtonNames(); + NSNumber *buttonValue = availableButtons[buttonName.lowercaseString]; + + if (!buttonValue) { return [[[FBErrorBuilder builder] - withDescriptionFormat:@"The button '%@' is unknown. Only the following button names are supported: %@", buttonName, supportedButtonNames] + withDescriptionFormat:@"The button '%@' is not supported. The device under test only supports the following buttons: %@", buttonName, availableButtons.allKeys] buildError:error]; } - [self pressButton:dstButton]; + [self pressButton:(XCUIDeviceButton)[buttonValue unsignedIntegerValue]]; return YES; } #endif @@ -430,14 +411,4 @@ - (BOOL)fb_clearSimulatedLocation:(NSError **)error } #endif -- (void)fb_addButtonsIfAvailable:(NSArray *)buttonNames - toSupportedButtonNames:(NSMutableArray *)supportedButtonNames -{ - for (NSString *buttonName in buttonNames) { - if ([self fb_hasButton:buttonName]) { - [supportedButtonNames addObject:buttonName]; - } - } -} - @end