Skip to content

Commit aaf70c9

Browse files
feat: Add an endpoint for keyboard input (#797)
1 parent ea6fb73 commit aaf70c9

File tree

3 files changed

+120
-0
lines changed

3 files changed

+120
-0
lines changed

WebDriverAgentLib/Commands/FBCustomCommands.m

+45
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ + (NSArray *)routes
7070
[[FBRoute GET:@"/wda/device/location"] respondWithTarget:self action:@selector(handleGetLocation:)],
7171
[[FBRoute GET:@"/wda/device/location"].withoutSession respondWithTarget:self action:@selector(handleGetLocation:)],
7272
#if !TARGET_OS_TV // tvOS does not provide relevant APIs
73+
#if __clang_major__ >= 15
74+
[[FBRoute POST:@"/wda/element/:uuid/keyboardInput"] respondWithTarget:self action:@selector(handleKeyboardInput:)],
75+
#endif
7376
[[FBRoute GET:@"/wda/simulatedLocation"] respondWithTarget:self action:@selector(handleGetSimulatedLocation:)],
7477
[[FBRoute GET:@"/wda/simulatedLocation"].withoutSession respondWithTarget:self action:@selector(handleGetSimulatedLocation:)],
7578
[[FBRoute POST:@"/wda/simulatedLocation"] respondWithTarget:self action:@selector(handleSetSimulatedLocation:)],
@@ -543,6 +546,48 @@ + (NSString *)timeZone
543546
}
544547
return FBResponseWithOK();
545548
}
549+
550+
#if __clang_major__ >= 15
551+
+ (id<FBResponsePayload>)handleKeyboardInput:(FBRouteRequest *)request
552+
{
553+
FBElementCache *elementCache = request.session.elementCache;
554+
BOOL hasElement = nil != request.parameters[@"uuid"];
555+
XCUIElement *destination = hasElement
556+
? [elementCache elementForUUID:(NSString *)request.parameters[@"uuid"]]
557+
: request.session.activeApplication;
558+
id keys = request.arguments[@"keys"];
559+
if (![keys isKindOfClass:NSArray.class]) {
560+
NSString *message = @"The 'keys' argument must be an array";
561+
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:message
562+
traceback:nil]);
563+
}
564+
for (id item in (NSArray *)keys) {
565+
if ([item isKindOfClass:NSString.class]) {
566+
NSString *keyValue = [FBKeyboard keyValueForName:item] ?: item;
567+
[destination typeKey:keyValue modifierFlags:XCUIKeyModifierNone];
568+
} else if ([item isKindOfClass:NSDictionary.class]) {
569+
id key = [(NSDictionary *)item objectForKey:@"key"];
570+
if (![key isKindOfClass:NSString.class]) {
571+
NSString *message = [NSString stringWithFormat:@"All dictionaries of 'keys' array must have the 'key' item of type string. Got '%@' instead in the item %@", key, item];
572+
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:message
573+
traceback:nil]);
574+
}
575+
id modifiers = [(NSDictionary *)item objectForKey:@"modifierFlags"];
576+
NSUInteger modifierFlags = XCUIKeyModifierNone;
577+
if ([modifiers isKindOfClass:NSNumber.class]) {
578+
modifierFlags = [(NSNumber *)modifiers unsignedIntValue];
579+
}
580+
NSString *keyValue = [FBKeyboard keyValueForName:item] ?: key;
581+
[destination typeKey:keyValue modifierFlags:modifierFlags];
582+
} else {
583+
NSString *message = @"All items of the 'keys' array must be either dictionaries or strings";
584+
return FBResponseWithStatus([FBCommandStatus invalidArgumentErrorWithMessage:message
585+
traceback:nil]);
586+
}
587+
}
588+
return FBResponseWithOK();
589+
}
590+
#endif
546591
#endif
547592

548593
+ (id<FBResponsePayload>)handlePerformAccessibilityAudit:(FBRouteRequest *)request

WebDriverAgentLib/Utilities/FBKeyboard.h

+10
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ NS_ASSUME_NONNULL_BEGIN
1313

1414
@interface FBKeyboard : NSObject
1515

16+
#if (!TARGET_OS_TV && __clang_major__ >= 15)
17+
/**
18+
Transforms key name to its string representation, which could be used with XCTest
19+
20+
@param name one of available keyboard key names defined in https://developer.apple.com/documentation/xctest/xcuikeyboardkey?language=objc
21+
@return Either the key value or nil if no matches have been found
22+
*/
23+
+ (nullable NSString *)keyValueForName:(NSString *)name;
24+
#endif
25+
1626
/**
1727
Types a string into active element. There must be element with keyboard focus; otherwise an
1828
error is raised.

WebDriverAgentLib/Utilities/FBKeyboard.m

+65
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,69 @@ + (BOOL)waitUntilVisibleForApplication:(XCUIApplication *)app timeout:(NSTimeInt
8080
error:error];
8181
}
8282

83+
#if (!TARGET_OS_TV && __clang_major__ >= 15)
84+
85+
+ (NSString *)keyValueForName:(NSString *)name
86+
{
87+
static dispatch_once_t onceKeys;
88+
static NSDictionary<NSString *, NSString *> *keysMapping;
89+
dispatch_once(&onceKeys, ^{
90+
keysMapping = @{
91+
@"XCUIKeyboardKeyDelete": XCUIKeyboardKeyDelete,
92+
@"XCUIKeyboardKeyReturn": XCUIKeyboardKeyReturn,
93+
@"XCUIKeyboardKeyEnter": XCUIKeyboardKeyEnter,
94+
@"XCUIKeyboardKeyTab": XCUIKeyboardKeyTab,
95+
@"XCUIKeyboardKeySpace": XCUIKeyboardKeySpace,
96+
@"XCUIKeyboardKeyEscape": XCUIKeyboardKeyEscape,
97+
98+
@"XCUIKeyboardKeyUpArrow": XCUIKeyboardKeyUpArrow,
99+
@"XCUIKeyboardKeyDownArrow": XCUIKeyboardKeyDownArrow,
100+
@"XCUIKeyboardKeyLeftArrow": XCUIKeyboardKeyLeftArrow,
101+
@"XCUIKeyboardKeyRightArrow": XCUIKeyboardKeyRightArrow,
102+
103+
@"XCUIKeyboardKeyF1": XCUIKeyboardKeyF1,
104+
@"XCUIKeyboardKeyF2": XCUIKeyboardKeyF2,
105+
@"XCUIKeyboardKeyF3": XCUIKeyboardKeyF3,
106+
@"XCUIKeyboardKeyF4": XCUIKeyboardKeyF4,
107+
@"XCUIKeyboardKeyF5": XCUIKeyboardKeyF5,
108+
@"XCUIKeyboardKeyF6": XCUIKeyboardKeyF6,
109+
@"XCUIKeyboardKeyF7": XCUIKeyboardKeyF7,
110+
@"XCUIKeyboardKeyF8": XCUIKeyboardKeyF8,
111+
@"XCUIKeyboardKeyF9": XCUIKeyboardKeyF9,
112+
@"XCUIKeyboardKeyF10": XCUIKeyboardKeyF10,
113+
@"XCUIKeyboardKeyF11": XCUIKeyboardKeyF11,
114+
@"XCUIKeyboardKeyF12": XCUIKeyboardKeyF12,
115+
@"XCUIKeyboardKeyF13": XCUIKeyboardKeyF13,
116+
@"XCUIKeyboardKeyF14": XCUIKeyboardKeyF14,
117+
@"XCUIKeyboardKeyF15": XCUIKeyboardKeyF15,
118+
@"XCUIKeyboardKeyF16": XCUIKeyboardKeyF16,
119+
@"XCUIKeyboardKeyF17": XCUIKeyboardKeyF17,
120+
@"XCUIKeyboardKeyF18": XCUIKeyboardKeyF18,
121+
@"XCUIKeyboardKeyF19": XCUIKeyboardKeyF19,
122+
123+
@"XCUIKeyboardKeyForwardDelete": XCUIKeyboardKeyForwardDelete,
124+
@"XCUIKeyboardKeyHome": XCUIKeyboardKeyHome,
125+
@"XCUIKeyboardKeyEnd": XCUIKeyboardKeyEnd,
126+
@"XCUIKeyboardKeyPageUp": XCUIKeyboardKeyPageUp,
127+
@"XCUIKeyboardKeyPageDown": XCUIKeyboardKeyPageDown,
128+
@"XCUIKeyboardKeyClear": XCUIKeyboardKeyClear,
129+
@"XCUIKeyboardKeyHelp": XCUIKeyboardKeyHelp,
130+
131+
@"XCUIKeyboardKeyCapsLock": XCUIKeyboardKeyCapsLock,
132+
@"XCUIKeyboardKeyShift": XCUIKeyboardKeyShift,
133+
@"XCUIKeyboardKeyControl": XCUIKeyboardKeyControl,
134+
@"XCUIKeyboardKeyOption": XCUIKeyboardKeyOption,
135+
@"XCUIKeyboardKeyCommand": XCUIKeyboardKeyCommand,
136+
@"XCUIKeyboardKeyRightShift": XCUIKeyboardKeyRightShift,
137+
@"XCUIKeyboardKeyRightControl": XCUIKeyboardKeyRightControl,
138+
@"XCUIKeyboardKeyRightOption": XCUIKeyboardKeyRightOption,
139+
@"XCUIKeyboardKeyRightCommand": XCUIKeyboardKeyRightCommand,
140+
@"XCUIKeyboardKeySecondaryFn": XCUIKeyboardKeySecondaryFn
141+
};
142+
});
143+
return keysMapping[name];
144+
}
145+
146+
#endif
147+
83148
@end

0 commit comments

Comments
 (0)