diff --git a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h index 2f9c9d86f..db03b7a1f 100644 --- a/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +++ b/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h @@ -95,10 +95,19 @@ 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. - @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..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; @@ -210,6 +245,11 @@ - (BOOL)fb_activateSiriVoiceRecognitionWithText:(NSString *)text error:(NSError } } +- (BOOL)fb_hasButton:(NSString *)buttonName +{ + return availableButtonNames()[buttonName.lowercaseString] != nil; +} + - (BOOL)fb_pressButton:(NSString *)buttonName forDuration:(nullable NSNumber *)duration error:(NSError **)error @@ -270,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]; } @@ -290,29 +330,15 @@ - (BOOL)fb_pressButton:(NSString *)buttonName - (BOOL)fb_pressButton:(NSString *)buttonName error:(NSError **)error { - NSMutableArray *supportedButtonNames = [NSMutableArray array]; - XCUIDeviceButton dstButton = 0; - if ([buttonName.lowercaseString isEqualToString:@"home"]) { - dstButton = XCUIDeviceButtonHome; - } - [supportedButtonNames addObject:@"home"]; -#if !TARGET_OS_SIMULATOR - if ([buttonName.lowercaseString isEqualToString:@"volumeup"]) { - dstButton = XCUIDeviceButtonVolumeUp; - } - if ([buttonName.lowercaseString isEqualToString:@"volumedown"]) { - dstButton = XCUIDeviceButtonVolumeDown; - } - [supportedButtonNames addObject:@"volumeUp"]; - [supportedButtonNames addObject:@"volumeDown"]; -#endif - - 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 diff --git a/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m b/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m index 97908add4..2f1b8a467 100644 --- a/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m +++ b/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m @@ -186,6 +186,22 @@ - (void)testPressingSupportedButton XCTAssertNil(error); } +- (void)testPressingDeviceSpecificButton +{ + NSError *error; + BOOL hasActionButton = [XCUIDevice.sharedDevice fb_hasButton:@"action"]; + 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;