Skip to content

Loading…

Extract keyboard automation code into its own KIFTypist class #141

Merged
merged 1 commit into from

2 participants

@moredip

Most of the keyboard automation implementation doesn't need to be exposed, even to KIFTestStep's internals. Pulling the implementation out into its own class seems cleaner and a better separation of concerns.

@puls puls merged commit 46d4805 into kif-framework:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Showing with 186 additions and 136 deletions.
  1. +2 −136 Classes/KIFTestStep.m
  2. +11 −0 Classes/KIFTypist.h
  3. +165 −0 Classes/KIFTypist.m
  4. +8 −0 KIF.xcodeproj/project.pbxproj
View
138 Classes/KIFTestStep.m
@@ -15,6 +15,7 @@
#import "UITouch-KIFAdditions.h"
#import "UIView-KIFAdditions.h"
#import "UIWindow-KIFAdditions.h"
+#import "KIFTypist.h"
static NSTimeInterval KIFTestStepDefaultTimeout = 10.0;
@@ -30,10 +31,6 @@ @interface KIFTestStep ()
+ (BOOL)_isUserInteractionEnabledForView:(UIView *)view;
-+ (BOOL)_enterCharacter:(NSString *)characterString;
-+ (BOOL)_enterCharacter:(NSString *)characterString history:(NSMutableDictionary *)history;
-+ (BOOL)_enterCustomKeyboardCharacter:(NSString *)characterString;
-
+ (UIAccessibilityElement *)_accessibilityElementWithLabel:(NSString *)label accessibilityValue:(NSString *)value tappable:(BOOL)mustBeTappable traits:(UIAccessibilityTraits)traits error:(out NSError **)error;
typedef CGPoint KIFDisplacement;
@@ -383,7 +380,7 @@ + (id)stepToEnterText:(NSString *)text intoViewWithAccessibilityLabel:(NSString
for (NSUInteger characterIndex = 0; characterIndex < [text length]; characterIndex++) {
NSString *characterString = [text substringWithRange:NSMakeRange(characterIndex, 1)];
- if (![self _enterCharacter:characterString]) {
+ if (![KIFTypist enterCharacter:characterString]) {
// Attempt to cheat if we couldn't find the character
if ([view isKindOfClass:[UITextField class]] || [view isKindOfClass:[UITextView class]]) {
NSLog(@"KIF: Unable to find keyboard key for %@. Inserting manually.", characterString);
@@ -773,137 +770,6 @@ + (NSString *)_representedKeyboardStringForCharacter:(NSString *)characterString
return characterString;
}
-+ (BOOL)_enterCharacter:(NSString *)characterString;
-{
- return [self _enterCharacter:characterString history:[NSMutableDictionary dictionary]];
-}
-
-+ (BOOL)_enterCharacter:(NSString *)characterString history:(NSMutableDictionary *)history;
-{
- const NSTimeInterval keystrokeDelay = 0.05f;
-
- // Each key on the keyboard does not have its own view, so we have to ask for the list of keys,
- // find the appropriate one, and tap inside the frame of that key on the main keyboard view.
- if (!characterString.length) {
- return YES;
- }
-
- UIWindow *keyboardWindow = [[UIApplication sharedApplication] keyboardWindow];
- UIView *keyboardView = [[keyboardWindow subviewsWithClassNamePrefix:@"UIKBKeyplaneView"] lastObject];
-
- // If we didn't find the standard keyboard view, then we may have a custom keyboard
- if (!keyboardView) {
- return [self _enterCustomKeyboardCharacter:characterString];
- }
- id /*UIKBKeyplane*/ keyplane = [keyboardView valueForKey:@"keyplane"];
- BOOL isShiftKeyplane = [[keyplane valueForKey:@"isShiftKeyplane"] boolValue];
-
- NSMutableArray *unvisitedForKeyplane = [history objectForKey:[NSValue valueWithNonretainedObject:keyplane]];
- if (!unvisitedForKeyplane) {
- unvisitedForKeyplane = [NSMutableArray arrayWithObjects:@"More", @"International", nil];
- if (!isShiftKeyplane) {
- [unvisitedForKeyplane insertObject:@"Shift" atIndex:0];
- }
- [history setObject:unvisitedForKeyplane forKey:[NSValue valueWithNonretainedObject:keyplane]];
- }
-
- NSArray *keys = [keyplane valueForKey:@"keys"];
-
- // Interpret control characters appropriately
- characterString = [self _representedKeyboardStringForCharacter:characterString];
-
- id keyToTap = nil;
- id modifierKey = nil;
- NSString *selectedModifierRepresentedString = nil;
-
- while (YES) {
- for (id/*UIKBKey*/ key in keys) {
- NSString *representedString = [key valueForKey:@"representedString"];
-
- // Find the key based on the key's represented string
- if ([representedString isEqual:characterString]) {
- keyToTap = key;
- }
-
- if (!modifierKey && unvisitedForKeyplane.count && [[unvisitedForKeyplane objectAtIndex:0] isEqual:representedString]) {
- modifierKey = key;
- selectedModifierRepresentedString = representedString;
- [unvisitedForKeyplane removeObjectAtIndex:0];
- }
- }
-
- if (keyToTap) {
- break;
- }
-
- if (modifierKey) {
- break;
- }
-
- if (!unvisitedForKeyplane.count) {
- return NO;
- }
-
- // If we didn't find the key or the modifier, then this modifier must not exist on this keyboard. Remove it.
- [unvisitedForKeyplane removeObjectAtIndex:0];
- }
-
- if (keyToTap) {
- [keyboardView tapAtPoint:CGPointCenteredInRect([keyToTap frame])];
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
-
- return YES;
- }
-
- // We didn't find anything, so try the symbols pane
- if (modifierKey) {
- [keyboardView tapAtPoint:CGPointCenteredInRect([modifierKey frame])];
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
-
- // If we're back at a place we've been before, and we still have things to explore in the previous
- id /*UIKBKeyplane*/ newKeyplane = [keyboardView valueForKey:@"keyplane"];
- id /*UIKBKeyplane*/ previousKeyplane = [history valueForKey:@"previousKeyplane"];
-
- if (newKeyplane == previousKeyplane) {
- // Come back to the keyplane that we just tested so that we can try the other modifiers
- NSMutableArray *previousKeyplaneHistory = [history objectForKey:[NSValue valueWithNonretainedObject:newKeyplane]];
- [previousKeyplaneHistory insertObject:[history valueForKey:@"lastModifierRepresentedString"] atIndex:0];
- } else {
- [history setValue:keyplane forKey:@"previousKeyplane"];
- [history setValue:selectedModifierRepresentedString forKey:@"lastModifierRepresentedString"];
- }
-
- return [self _enterCharacter:characterString history:history];
- }
-
- return NO;
-}
-
-+ (BOOL)_enterCustomKeyboardCharacter:(NSString *)characterString;
-{
- const NSTimeInterval keystrokeDelay = 0.05f;
-
- if (!characterString.length) {
- return YES;
- }
-
- characterString = [self _representedKeyboardStringForCharacter:characterString];
-
- // For custom keyboards, use the classic methods of looking up views based on accessibility labels
- UIWindow *keyboardWindow = [[UIApplication sharedApplication] keyboardWindow];
-
- UIAccessibilityElement *element = [keyboardWindow accessibilityElementWithLabel:characterString];
- if (!element) {
- return NO;
- }
-
- UIView *view = [UIAccessibilityElement viewContainingAccessibilityElement:element];
- CGRect keyFrame = [view.window convertRect:[element accessibilityFrame] toView:view];
- [view tapAtPoint:CGPointCenteredInRect(keyFrame)];
- CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
-
- return YES;
-}
+ (UIAccessibilityElement *)_accessibilityElementWithLabel:(NSString *)label accessibilityValue:(NSString *)value tappable:(BOOL)mustBeTappable traits:(UIAccessibilityTraits)traits error:(out NSError **)error;
{
View
11 Classes/KIFTypist.h
@@ -0,0 +1,11 @@
+//
+// KIFTypist.h
+// KIF
+//
+// Created by Pete Hodgson on 8/12/12.
+//
+//
+
+@interface KIFTypist : NSObject
++ (BOOL)enterCharacter:(NSString *)characterString;
+@end
View
165 Classes/KIFTypist.m
@@ -0,0 +1,165 @@
+//
+// KIFTypist.m
+// KIF
+//
+// Created by Pete Hodgson on 8/12/12.
+//
+//
+
+#import "KIFTypist.h"
+#import "UIApplication-KIFAdditions.h"
+#import "UIView-KIFAdditions.h"
+#import "CGGeometry-KIFAdditions.h"
+#import "UIAccessibilityElement-KIFAdditions.h"
+
+@interface KIFTypist()
++ (NSString *)_representedKeyboardStringForCharacter:(NSString *)characterString;
++ (BOOL)_enterCharacter:(NSString *)characterString history:(NSMutableDictionary *)history;
++ (BOOL)_enterCustomKeyboardCharacter:(NSString *)characterString;
+@end
+
+@implementation KIFTypist
+
++ (NSString *)_representedKeyboardStringForCharacter:(NSString *)characterString;
+{
+ // Interpret control characters appropriately
+ if ([characterString isEqual:@"\b"]) {
+ characterString = @"Delete";
+ }
+
+ return characterString;
+}
+
++ (BOOL)enterCharacter:(NSString *)characterString;
+{
+ return [self _enterCharacter:characterString history:[NSMutableDictionary dictionary]];
+}
+
++ (BOOL)_enterCharacter:(NSString *)characterString history:(NSMutableDictionary *)history;
+{
+ const NSTimeInterval keystrokeDelay = 0.05f;
+
+ // Each key on the keyboard does not have its own view, so we have to ask for the list of keys,
+ // find the appropriate one, and tap inside the frame of that key on the main keyboard view.
+ if (!characterString.length) {
+ return YES;
+ }
+
+ UIWindow *keyboardWindow = [[UIApplication sharedApplication] keyboardWindow];
+ UIView *keyboardView = [[keyboardWindow subviewsWithClassNamePrefix:@"UIKBKeyplaneView"] lastObject];
+
+ // If we didn't find the standard keyboard view, then we may have a custom keyboard
+ if (!keyboardView) {
+ return [self _enterCustomKeyboardCharacter:characterString];
+ }
+ id /*UIKBKeyplane*/ keyplane = [keyboardView valueForKey:@"keyplane"];
+ BOOL isShiftKeyplane = [[keyplane valueForKey:@"isShiftKeyplane"] boolValue];
+
+ NSMutableArray *unvisitedForKeyplane = [history objectForKey:[NSValue valueWithNonretainedObject:keyplane]];
+ if (!unvisitedForKeyplane) {
+ unvisitedForKeyplane = [NSMutableArray arrayWithObjects:@"More", @"International", nil];
+ if (!isShiftKeyplane) {
+ [unvisitedForKeyplane insertObject:@"Shift" atIndex:0];
+ }
+ [history setObject:unvisitedForKeyplane forKey:[NSValue valueWithNonretainedObject:keyplane]];
+ }
+
+ NSArray *keys = [keyplane valueForKey:@"keys"];
+
+ // Interpret control characters appropriately
+ characterString = [self _representedKeyboardStringForCharacter:characterString];
+
+ id keyToTap = nil;
+ id modifierKey = nil;
+ NSString *selectedModifierRepresentedString = nil;
+
+ while (YES) {
+ for (id/*UIKBKey*/ key in keys) {
+ NSString *representedString = [key valueForKey:@"representedString"];
+
+ // Find the key based on the key's represented string
+ if ([representedString isEqual:characterString]) {
+ keyToTap = key;
+ }
+
+ if (!modifierKey && unvisitedForKeyplane.count && [[unvisitedForKeyplane objectAtIndex:0] isEqual:representedString]) {
+ modifierKey = key;
+ selectedModifierRepresentedString = representedString;
+ [unvisitedForKeyplane removeObjectAtIndex:0];
+ }
+ }
+
+ if (keyToTap) {
+ break;
+ }
+
+ if (modifierKey) {
+ break;
+ }
+
+ if (!unvisitedForKeyplane.count) {
+ return NO;
+ }
+
+ // If we didn't find the key or the modifier, then this modifier must not exist on this keyboard. Remove it.
+ [unvisitedForKeyplane removeObjectAtIndex:0];
+ }
+
+ if (keyToTap) {
+ [keyboardView tapAtPoint:CGPointCenteredInRect([keyToTap frame])];
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
+
+ return YES;
+ }
+
+ // We didn't find anything, so try the symbols pane
+ if (modifierKey) {
+ [keyboardView tapAtPoint:CGPointCenteredInRect([modifierKey frame])];
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
+
+ // If we're back at a place we've been before, and we still have things to explore in the previous
+ id /*UIKBKeyplane*/ newKeyplane = [keyboardView valueForKey:@"keyplane"];
+ id /*UIKBKeyplane*/ previousKeyplane = [history valueForKey:@"previousKeyplane"];
+
+ if (newKeyplane == previousKeyplane) {
+ // Come back to the keyplane that we just tested so that we can try the other modifiers
+ NSMutableArray *previousKeyplaneHistory = [history objectForKey:[NSValue valueWithNonretainedObject:newKeyplane]];
+ [previousKeyplaneHistory insertObject:[history valueForKey:@"lastModifierRepresentedString"] atIndex:0];
+ } else {
+ [history setValue:keyplane forKey:@"previousKeyplane"];
+ [history setValue:selectedModifierRepresentedString forKey:@"lastModifierRepresentedString"];
+ }
+
+ return [self _enterCharacter:characterString history:history];
+ }
+
+ return NO;
+}
+
++ (BOOL)_enterCustomKeyboardCharacter:(NSString *)characterString;
+{
+ const NSTimeInterval keystrokeDelay = 0.05f;
+
+ if (!characterString.length) {
+ return YES;
+ }
+
+ characterString = [self _representedKeyboardStringForCharacter:characterString];
+
+ // For custom keyboards, use the classic methods of looking up views based on accessibility labels
+ UIWindow *keyboardWindow = [[UIApplication sharedApplication] keyboardWindow];
+
+ UIAccessibilityElement *element = [keyboardWindow accessibilityElementWithLabel:characterString];
+ if (!element) {
+ return NO;
+ }
+
+ UIView *view = [UIAccessibilityElement viewContainingAccessibilityElement:element];
+ CGRect keyFrame = [view.window convertRect:[element accessibilityFrame] toView:view];
+ [view tapAtPoint:CGPointCenteredInRect(keyFrame)];
+ CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
+
+ return YES;
+}
+
+@end
View
8 KIF.xcodeproj/project.pbxproj
@@ -31,6 +31,8 @@
AAB072B213971AB2008AF393 /* UIWindow-KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = AAB072A213971AB2008AF393 /* UIWindow-KIFAdditions.h */; };
AAB072B313971AB2008AF393 /* UIWindow-KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AAB072A313971AB2008AF393 /* UIWindow-KIFAdditions.m */; };
AAB072B513971AEA008AF393 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAB072B413971AEA008AF393 /* UIKit.framework */; };
+ C194255815D83DE9004FC314 /* KIFTypist.h in Headers */ = {isa = PBXBuildFile; fileRef = C194255615D83DE9004FC314 /* KIFTypist.h */; };
+ C194255915D83DE9004FC314 /* KIFTypist.m in Sources */ = {isa = PBXBuildFile; fileRef = C194255715D83DE9004FC314 /* KIFTypist.m */; };
CDFD8E86139728B4008D299F /* NSFileManager-KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CDFD8E84139728B4008D299F /* NSFileManager-KIFAdditions.h */; };
CDFD8E87139728B4008D299F /* NSFileManager-KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CDFD8E85139728B4008D299F /* NSFileManager-KIFAdditions.m */; };
/* End PBXBuildFile section */
@@ -62,6 +64,8 @@
AAB072A213971AB2008AF393 /* UIWindow-KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIWindow-KIFAdditions.h"; sourceTree = "<group>"; };
AAB072A313971AB2008AF393 /* UIWindow-KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIWindow-KIFAdditions.m"; sourceTree = "<group>"; };
AAB072B413971AEA008AF393 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+ C194255615D83DE9004FC314 /* KIFTypist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KIFTypist.h; sourceTree = "<group>"; };
+ C194255715D83DE9004FC314 /* KIFTypist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KIFTypist.m; sourceTree = "<group>"; };
CDFD8E84139728B4008D299F /* NSFileManager-KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager-KIFAdditions.h"; sourceTree = "<group>"; };
CDFD8E85139728B4008D299F /* NSFileManager-KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager-KIFAdditions.m"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -126,6 +130,8 @@
AAB0728913971A98008AF393 /* KIFTestScenario.m */,
AAB0728A13971A98008AF393 /* KIFTestStep.h */,
AAB0728B13971A98008AF393 /* KIFTestStep.m */,
+ C194255615D83DE9004FC314 /* KIFTypist.h */,
+ C194255715D83DE9004FC314 /* KIFTypist.m */,
);
path = Classes;
sourceTree = "<group>";
@@ -174,6 +180,7 @@
AAB072B213971AB2008AF393 /* UIWindow-KIFAdditions.h in Headers */,
CDFD8E86139728B4008D299F /* NSFileManager-KIFAdditions.h in Headers */,
39160B1113D1E6BB00311E38 /* LoadableCategory.h in Headers */,
+ C194255815D83DE9004FC314 /* KIFTypist.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -238,6 +245,7 @@
AAB072B113971AB2008AF393 /* UIView-KIFAdditions.m in Sources */,
AAB072B313971AB2008AF393 /* UIWindow-KIFAdditions.m in Sources */,
CDFD8E87139728B4008D299F /* NSFileManager-KIFAdditions.m in Sources */,
+ C194255915D83DE9004FC314 /* KIFTypist.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Something went wrong with that request. Please try again.