Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
68 changes: 47 additions & 21 deletions WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,41 @@
static const NSTimeInterval FBHomeButtonCoolOffTime = 1.;
static const NSTimeInterval FBScreenLockTimeout = 5.;

NSDictionary<NSString *, NSNumber *> *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;
Expand Down Expand Up @@ -210,6 +245,11 @@ - (BOOL)fb_activateSiriVoiceRecognitionWithText:(NSString *)text error:(NSError
}
}

- (BOOL)fb_hasButton:(NSString *)buttonName
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is expected to be called only for non-tvOS, then wrapping the entire method with #if !TARGET_OS_TV and reduce it inside this method would simplify the context switching when we read the code. Then, we can assume this button is dedicated for non-tvOS env.

Copy link
Author

@eglitise eglitise Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to extend it in the other tvOS PR, which is why I haven't added the target compiler guard for the whole method. Or would it be better to make two separate fb_hasButton methods?

Copy link
Member

@KazuCocoa KazuCocoa Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally yes. One is dedicated for tvOS and the other is iPhone, rather than mixed up both in one method and add many if/endif in it. (this is more internal/private method thing)

{
return availableButtonNames()[buttonName.lowercaseString] != nil;
}

- (BOOL)fb_pressButton:(NSString *)buttonName
forDuration:(nullable NSNumber *)duration
error:(NSError **)error
Expand Down Expand Up @@ -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];
}

Expand All @@ -290,29 +330,15 @@ - (BOOL)fb_pressButton:(NSString *)buttonName
- (BOOL)fb_pressButton:(NSString *)buttonName
error:(NSError **)error
{
NSMutableArray<NSString *> *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<NSString *, NSNumber *> *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
Expand Down
16 changes: 16 additions & 0 deletions WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading