diff --git a/WebDriverAgentLib/Commands/FBCustomCommands.m b/WebDriverAgentLib/Commands/FBCustomCommands.m index 9d8e324d7..934cdf73f 100644 --- a/WebDriverAgentLib/Commands/FBCustomCommands.m +++ b/WebDriverAgentLib/Commands/FBCustomCommands.m @@ -70,6 +70,9 @@ + (NSArray *)routes [[FBRoute GET:@"/wda/device/location"] respondWithTarget:self action:@selector(handleGetLocation:)], [[FBRoute GET:@"/wda/device/location"].withoutSession respondWithTarget:self action:@selector(handleGetLocation:)], #if !TARGET_OS_TV // tvOS does not provide relevant APIs +#if __clang_major__ >= 15 + [[FBRoute POST:@"/wda/element/:uuid/keyboardInput"] respondWithTarget:self action:@selector(handleKeyboardInput:)], +#endif [[FBRoute GET:@"/wda/simulatedLocation"] respondWithTarget:self action:@selector(handleGetSimulatedLocation:)], [[FBRoute GET:@"/wda/simulatedLocation"].withoutSession respondWithTarget:self action:@selector(handleGetSimulatedLocation:)], [[FBRoute POST:@"/wda/simulatedLocation"] respondWithTarget:self action:@selector(handleSetSimulatedLocation:)], @@ -543,6 +546,48 @@ + (NSString *)timeZone } return FBResponseWithOK(); } + +#if __clang_major__ >= 15 ++ (id)handleKeyboardInput:(FBRouteRequest *)request +{ + FBElementCache *elementCache = request.session.elementCache; + BOOL hasElement = nil != request.parameters[@"uuid"]; + XCUIElement *destination = hasElement + ? [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"]] + : request.session.activeApplication; + id keys = request.arguments[@"keys"]; + if (![keys isKindOfClass:NSArray.class]) { + NSString *message = @"The 'keys' argument must be an array"; + return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:message + traceback:nil]); + } + for (id item in (NSArray *)keys) { + if ([item isKindOfClass:NSString.class]) { + NSString *keyValue = [FBKeyboard keyValueForName:item] ?: item; + [destination typeKey:keyValue modifierFlags:XCUIKeyModifierNone]; + } else if ([item isKindOfClass:NSDictionary.class]) { + id key = [(NSDictionary *)item objectForKey:@"key"]; + if (![key isKindOfClass:NSString.class]) { + NSString *message = [NSString stringWithFormat:@"All dictionaries of 'keys' array must have the 'key' item of type string. Got '%@' instead in the item %@", key, item]; + return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:message + traceback:nil]); + } + id modifiers = [(NSDictionary *)item objectForKey:@"modifierFlags"]; + NSUInteger modifierFlags = XCUIKeyModifierNone; + if ([modifiers isKindOfClass:NSNumber.class]) { + modifierFlags = [(NSNumber *)modifiers unsignedIntValue]; + } + NSString *keyValue = [FBKeyboard keyValueForName:item] ?: key; + [destination typeKey:keyValue modifierFlags:modifierFlags]; + } else { + NSString *message = @"All items of the 'keys' array must be either dictionaries or strings"; + return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:message + traceback:nil]); + } + } + return FBResponseWithOK(); +} +#endif #endif + (id)handlePerformAccessibilityAudit:(FBRouteRequest *)request diff --git a/WebDriverAgentLib/Utilities/FBKeyboard.h b/WebDriverAgentLib/Utilities/FBKeyboard.h index cbe1180cd..5a16e1aed 100644 --- a/WebDriverAgentLib/Utilities/FBKeyboard.h +++ b/WebDriverAgentLib/Utilities/FBKeyboard.h @@ -13,6 +13,16 @@ NS_ASSUME_NONNULL_BEGIN @interface FBKeyboard : NSObject +#if (!TARGET_OS_TV && __clang_major__ >= 15) +/** + Transforms key name to its string representation, which could be used with XCTest + + @param name one of available keyboard key names defined in https://developer.apple.com/documentation/xctest/xcuikeyboardkey?language=objc + @return Either the key value or nil if no matches have been found + */ ++ (nullable NSString *)keyValueForName:(NSString *)name; +#endif + /** Types a string into active element. There must be element with keyboard focus; otherwise an error is raised. diff --git a/WebDriverAgentLib/Utilities/FBKeyboard.m b/WebDriverAgentLib/Utilities/FBKeyboard.m index 7d474ac02..0cf9964c6 100644 --- a/WebDriverAgentLib/Utilities/FBKeyboard.m +++ b/WebDriverAgentLib/Utilities/FBKeyboard.m @@ -80,4 +80,69 @@ + (BOOL)waitUntilVisibleForApplication:(XCUIApplication *)app timeout:(NSTimeInt error:error]; } +#if (!TARGET_OS_TV && __clang_major__ >= 15) + ++ (NSString *)keyValueForName:(NSString *)name +{ + static dispatch_once_t onceKeys; + static NSDictionary *keysMapping; + dispatch_once(&onceKeys, ^{ + keysMapping = @{ + @"XCUIKeyboardKeyDelete": XCUIKeyboardKeyDelete, + @"XCUIKeyboardKeyReturn": XCUIKeyboardKeyReturn, + @"XCUIKeyboardKeyEnter": XCUIKeyboardKeyEnter, + @"XCUIKeyboardKeyTab": XCUIKeyboardKeyTab, + @"XCUIKeyboardKeySpace": XCUIKeyboardKeySpace, + @"XCUIKeyboardKeyEscape": XCUIKeyboardKeyEscape, + + @"XCUIKeyboardKeyUpArrow": XCUIKeyboardKeyUpArrow, + @"XCUIKeyboardKeyDownArrow": XCUIKeyboardKeyDownArrow, + @"XCUIKeyboardKeyLeftArrow": XCUIKeyboardKeyLeftArrow, + @"XCUIKeyboardKeyRightArrow": XCUIKeyboardKeyRightArrow, + + @"XCUIKeyboardKeyF1": XCUIKeyboardKeyF1, + @"XCUIKeyboardKeyF2": XCUIKeyboardKeyF2, + @"XCUIKeyboardKeyF3": XCUIKeyboardKeyF3, + @"XCUIKeyboardKeyF4": XCUIKeyboardKeyF4, + @"XCUIKeyboardKeyF5": XCUIKeyboardKeyF5, + @"XCUIKeyboardKeyF6": XCUIKeyboardKeyF6, + @"XCUIKeyboardKeyF7": XCUIKeyboardKeyF7, + @"XCUIKeyboardKeyF8": XCUIKeyboardKeyF8, + @"XCUIKeyboardKeyF9": XCUIKeyboardKeyF9, + @"XCUIKeyboardKeyF10": XCUIKeyboardKeyF10, + @"XCUIKeyboardKeyF11": XCUIKeyboardKeyF11, + @"XCUIKeyboardKeyF12": XCUIKeyboardKeyF12, + @"XCUIKeyboardKeyF13": XCUIKeyboardKeyF13, + @"XCUIKeyboardKeyF14": XCUIKeyboardKeyF14, + @"XCUIKeyboardKeyF15": XCUIKeyboardKeyF15, + @"XCUIKeyboardKeyF16": XCUIKeyboardKeyF16, + @"XCUIKeyboardKeyF17": XCUIKeyboardKeyF17, + @"XCUIKeyboardKeyF18": XCUIKeyboardKeyF18, + @"XCUIKeyboardKeyF19": XCUIKeyboardKeyF19, + + @"XCUIKeyboardKeyForwardDelete": XCUIKeyboardKeyForwardDelete, + @"XCUIKeyboardKeyHome": XCUIKeyboardKeyHome, + @"XCUIKeyboardKeyEnd": XCUIKeyboardKeyEnd, + @"XCUIKeyboardKeyPageUp": XCUIKeyboardKeyPageUp, + @"XCUIKeyboardKeyPageDown": XCUIKeyboardKeyPageDown, + @"XCUIKeyboardKeyClear": XCUIKeyboardKeyClear, + @"XCUIKeyboardKeyHelp": XCUIKeyboardKeyHelp, + + @"XCUIKeyboardKeyCapsLock": XCUIKeyboardKeyCapsLock, + @"XCUIKeyboardKeyShift": XCUIKeyboardKeyShift, + @"XCUIKeyboardKeyControl": XCUIKeyboardKeyControl, + @"XCUIKeyboardKeyOption": XCUIKeyboardKeyOption, + @"XCUIKeyboardKeyCommand": XCUIKeyboardKeyCommand, + @"XCUIKeyboardKeyRightShift": XCUIKeyboardKeyRightShift, + @"XCUIKeyboardKeyRightControl": XCUIKeyboardKeyRightControl, + @"XCUIKeyboardKeyRightOption": XCUIKeyboardKeyRightOption, + @"XCUIKeyboardKeyRightCommand": XCUIKeyboardKeyRightCommand, + @"XCUIKeyboardKeySecondaryFn": XCUIKeyboardKeySecondaryFn + }; + }); + return keysMapping[name]; +} + +#endif + @end