Skip to content
This repository

Extract keyboard automation code into its own KIFTypist class #141

Merged
merged 1 commit into from over 1 year ago

2 participants

Pete Hodgson Jim Puls
Pete Hodgson

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.

Jim Puls puls merged commit 46d4805 into from December 06, 2012
Jim Puls puls closed this December 06, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 1 unique commit by 1 author.

Aug 12, 2012
Pete Hodgson Extract keyboard automation code into its own KIFTypist class 64e2c9b
This page is out of date. Refresh to see the latest.
138  Classes/KIFTestStep.m
@@ -15,6 +15,7 @@
15 15
 #import "UITouch-KIFAdditions.h"
16 16
 #import "UIView-KIFAdditions.h"
17 17
 #import "UIWindow-KIFAdditions.h"
  18
+#import "KIFTypist.h"
18 19
 
19 20
 
20 21
 static NSTimeInterval KIFTestStepDefaultTimeout = 10.0;
@@ -30,10 +31,6 @@ @interface KIFTestStep ()
30 31
 
31 32
 + (BOOL)_isUserInteractionEnabledForView:(UIView *)view;
32 33
 
33  
-+ (BOOL)_enterCharacter:(NSString *)characterString;
34  
-+ (BOOL)_enterCharacter:(NSString *)characterString history:(NSMutableDictionary *)history;
35  
-+ (BOOL)_enterCustomKeyboardCharacter:(NSString *)characterString;
36  
-
37 34
 + (UIAccessibilityElement *)_accessibilityElementWithLabel:(NSString *)label accessibilityValue:(NSString *)value tappable:(BOOL)mustBeTappable traits:(UIAccessibilityTraits)traits error:(out NSError **)error;
38 35
 
39 36
 typedef CGPoint KIFDisplacement;
@@ -383,7 +380,7 @@ + (id)stepToEnterText:(NSString *)text intoViewWithAccessibilityLabel:(NSString
383 380
         for (NSUInteger characterIndex = 0; characterIndex < [text length]; characterIndex++) {
384 381
             NSString *characterString = [text substringWithRange:NSMakeRange(characterIndex, 1)];
385 382
             
386  
-            if (![self _enterCharacter:characterString]) {
  383
+            if (![KIFTypist enterCharacter:characterString]) {
387 384
                 // Attempt to cheat if we couldn't find the character
388 385
                 if ([view isKindOfClass:[UITextField class]] || [view isKindOfClass:[UITextView class]]) {
389 386
                     NSLog(@"KIF: Unable to find keyboard key for %@. Inserting manually.", characterString);
@@ -773,137 +770,6 @@ + (NSString *)_representedKeyboardStringForCharacter:(NSString *)characterString
773 770
     return characterString;
774 771
 }
775 772
 
776  
-+ (BOOL)_enterCharacter:(NSString *)characterString;
777  
-{
778  
-    return [self _enterCharacter:characterString history:[NSMutableDictionary dictionary]];
779  
-}
780  
-
781  
-+ (BOOL)_enterCharacter:(NSString *)characterString history:(NSMutableDictionary *)history;
782  
-{
783  
-    const NSTimeInterval keystrokeDelay = 0.05f;
784  
-    
785  
-    // Each key on the keyboard does not have its own view, so we have to ask for the list of keys,
786  
-    // find the appropriate one, and tap inside the frame of that key on the main keyboard view.
787  
-    if (!characterString.length) {
788  
-        return YES;
789  
-    }
790  
-    
791  
-    UIWindow *keyboardWindow = [[UIApplication sharedApplication] keyboardWindow];
792  
-    UIView *keyboardView = [[keyboardWindow subviewsWithClassNamePrefix:@"UIKBKeyplaneView"] lastObject];
793  
-    
794  
-    // If we didn't find the standard keyboard view, then we may have a custom keyboard
795  
-    if (!keyboardView) {
796  
-        return [self _enterCustomKeyboardCharacter:characterString];
797  
-    }
798  
-    id /*UIKBKeyplane*/ keyplane = [keyboardView valueForKey:@"keyplane"];
799  
-    BOOL isShiftKeyplane = [[keyplane valueForKey:@"isShiftKeyplane"] boolValue];
800  
-    
801  
-    NSMutableArray *unvisitedForKeyplane = [history objectForKey:[NSValue valueWithNonretainedObject:keyplane]];
802  
-    if (!unvisitedForKeyplane) {
803  
-        unvisitedForKeyplane = [NSMutableArray arrayWithObjects:@"More", @"International", nil];
804  
-        if (!isShiftKeyplane) {
805  
-            [unvisitedForKeyplane insertObject:@"Shift" atIndex:0];
806  
-        }
807  
-        [history setObject:unvisitedForKeyplane forKey:[NSValue valueWithNonretainedObject:keyplane]];
808  
-    }
809  
-    
810  
-    NSArray *keys = [keyplane valueForKey:@"keys"];
811  
-    
812  
-    // Interpret control characters appropriately
813  
-    characterString = [self _representedKeyboardStringForCharacter:characterString];
814  
-    
815  
-    id keyToTap = nil;
816  
-    id modifierKey = nil;
817  
-    NSString *selectedModifierRepresentedString = nil;
818  
-    
819  
-    while (YES) {
820  
-        for (id/*UIKBKey*/ key in keys) {
821  
-            NSString *representedString = [key valueForKey:@"representedString"];
822  
-            
823  
-            // Find the key based on the key's represented string
824  
-            if ([representedString isEqual:characterString]) {
825  
-                keyToTap = key;
826  
-            }
827  
-            
828  
-            if (!modifierKey && unvisitedForKeyplane.count && [[unvisitedForKeyplane objectAtIndex:0] isEqual:representedString]) {
829  
-                modifierKey = key;
830  
-                selectedModifierRepresentedString = representedString;
831  
-                [unvisitedForKeyplane removeObjectAtIndex:0];
832  
-            }
833  
-        }
834  
-        
835  
-        if (keyToTap) {
836  
-            break;
837  
-        }
838  
-        
839  
-        if (modifierKey) {
840  
-            break;
841  
-        }
842  
-        
843  
-        if (!unvisitedForKeyplane.count) {
844  
-            return NO;
845  
-        }
846  
-        
847  
-        // If we didn't find the key or the modifier, then this modifier must not exist on this keyboard. Remove it.
848  
-        [unvisitedForKeyplane removeObjectAtIndex:0];
849  
-    }
850  
-    
851  
-    if (keyToTap) {
852  
-        [keyboardView tapAtPoint:CGPointCenteredInRect([keyToTap frame])];
853  
-        CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
854  
-        
855  
-        return YES;
856  
-    }
857  
-    
858  
-    // We didn't find anything, so try the symbols pane
859  
-    if (modifierKey) {
860  
-        [keyboardView tapAtPoint:CGPointCenteredInRect([modifierKey frame])];
861  
-        CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
862  
-        
863  
-        // If we're back at a place we've been before, and we still have things to explore in the previous
864  
-        id /*UIKBKeyplane*/ newKeyplane = [keyboardView valueForKey:@"keyplane"];
865  
-        id /*UIKBKeyplane*/ previousKeyplane = [history valueForKey:@"previousKeyplane"];
866  
-        
867  
-        if (newKeyplane == previousKeyplane) {
868  
-            // Come back to the keyplane that we just tested so that we can try the other modifiers
869  
-            NSMutableArray *previousKeyplaneHistory = [history objectForKey:[NSValue valueWithNonretainedObject:newKeyplane]];
870  
-            [previousKeyplaneHistory insertObject:[history valueForKey:@"lastModifierRepresentedString"] atIndex:0];
871  
-        } else {
872  
-            [history setValue:keyplane forKey:@"previousKeyplane"];
873  
-            [history setValue:selectedModifierRepresentedString forKey:@"lastModifierRepresentedString"];
874  
-        }
875  
-        
876  
-        return [self _enterCharacter:characterString history:history];
877  
-    }
878  
-    
879  
-    return NO;
880  
-}
881  
-
882  
-+ (BOOL)_enterCustomKeyboardCharacter:(NSString *)characterString;
883  
-{
884  
-    const NSTimeInterval keystrokeDelay = 0.05f;
885  
-    
886  
-    if (!characterString.length) {
887  
-        return YES;
888  
-    }
889  
-    
890  
-    characterString = [self _representedKeyboardStringForCharacter:characterString];
891  
-    
892  
-    // For custom keyboards, use the classic methods of looking up views based on accessibility labels
893  
-    UIWindow *keyboardWindow = [[UIApplication sharedApplication] keyboardWindow];
894  
-    
895  
-    UIAccessibilityElement *element = [keyboardWindow accessibilityElementWithLabel:characterString];
896  
-    if (!element) {
897  
-        return NO;
898  
-    }
899  
-    
900  
-    UIView *view = [UIAccessibilityElement viewContainingAccessibilityElement:element];
901  
-    CGRect keyFrame = [view.window convertRect:[element accessibilityFrame] toView:view];
902  
-    [view tapAtPoint:CGPointCenteredInRect(keyFrame)];
903  
-    CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
904  
-    
905  
-    return YES;
906  
-}
907 773
 
908 774
 + (UIAccessibilityElement *)_accessibilityElementWithLabel:(NSString *)label accessibilityValue:(NSString *)value tappable:(BOOL)mustBeTappable traits:(UIAccessibilityTraits)traits error:(out NSError **)error;
909 775
 {
11  Classes/KIFTypist.h
... ...
@@ -0,0 +1,11 @@
  1
+//
  2
+//  KIFTypist.h
  3
+//  KIF
  4
+//
  5
+//  Created by Pete Hodgson on 8/12/12.
  6
+//
  7
+//
  8
+
  9
+@interface KIFTypist : NSObject
  10
++ (BOOL)enterCharacter:(NSString *)characterString;
  11
+@end
165  Classes/KIFTypist.m
... ...
@@ -0,0 +1,165 @@
  1
+//
  2
+//  KIFTypist.m
  3
+//  KIF
  4
+//
  5
+//  Created by Pete Hodgson on 8/12/12.
  6
+//
  7
+//
  8
+
  9
+#import "KIFTypist.h"
  10
+#import "UIApplication-KIFAdditions.h"
  11
+#import "UIView-KIFAdditions.h"
  12
+#import "CGGeometry-KIFAdditions.h"
  13
+#import "UIAccessibilityElement-KIFAdditions.h"
  14
+
  15
+@interface KIFTypist()
  16
++ (NSString *)_representedKeyboardStringForCharacter:(NSString *)characterString;
  17
++ (BOOL)_enterCharacter:(NSString *)characterString history:(NSMutableDictionary *)history;
  18
++ (BOOL)_enterCustomKeyboardCharacter:(NSString *)characterString;
  19
+@end
  20
+
  21
+@implementation KIFTypist
  22
+
  23
++ (NSString *)_representedKeyboardStringForCharacter:(NSString *)characterString;
  24
+{
  25
+    // Interpret control characters appropriately
  26
+    if ([characterString isEqual:@"\b"]) {
  27
+        characterString = @"Delete";
  28
+    }
  29
+    
  30
+    return characterString;
  31
+}
  32
+
  33
++ (BOOL)enterCharacter:(NSString *)characterString;
  34
+{
  35
+    return [self _enterCharacter:characterString history:[NSMutableDictionary dictionary]];
  36
+}
  37
+
  38
++ (BOOL)_enterCharacter:(NSString *)characterString history:(NSMutableDictionary *)history;
  39
+{
  40
+    const NSTimeInterval keystrokeDelay = 0.05f;
  41
+    
  42
+    // Each key on the keyboard does not have its own view, so we have to ask for the list of keys,
  43
+    // find the appropriate one, and tap inside the frame of that key on the main keyboard view.
  44
+    if (!characterString.length) {
  45
+        return YES;
  46
+    }
  47
+    
  48
+    UIWindow *keyboardWindow = [[UIApplication sharedApplication] keyboardWindow];
  49
+    UIView *keyboardView = [[keyboardWindow subviewsWithClassNamePrefix:@"UIKBKeyplaneView"] lastObject];
  50
+    
  51
+    // If we didn't find the standard keyboard view, then we may have a custom keyboard
  52
+    if (!keyboardView) {
  53
+        return [self _enterCustomKeyboardCharacter:characterString];
  54
+    }
  55
+    id /*UIKBKeyplane*/ keyplane = [keyboardView valueForKey:@"keyplane"];
  56
+    BOOL isShiftKeyplane = [[keyplane valueForKey:@"isShiftKeyplane"] boolValue];
  57
+    
  58
+    NSMutableArray *unvisitedForKeyplane = [history objectForKey:[NSValue valueWithNonretainedObject:keyplane]];
  59
+    if (!unvisitedForKeyplane) {
  60
+        unvisitedForKeyplane = [NSMutableArray arrayWithObjects:@"More", @"International", nil];
  61
+        if (!isShiftKeyplane) {
  62
+            [unvisitedForKeyplane insertObject:@"Shift" atIndex:0];
  63
+        }
  64
+        [history setObject:unvisitedForKeyplane forKey:[NSValue valueWithNonretainedObject:keyplane]];
  65
+    }
  66
+    
  67
+    NSArray *keys = [keyplane valueForKey:@"keys"];
  68
+    
  69
+    // Interpret control characters appropriately
  70
+    characterString = [self _representedKeyboardStringForCharacter:characterString];
  71
+    
  72
+    id keyToTap = nil;
  73
+    id modifierKey = nil;
  74
+    NSString *selectedModifierRepresentedString = nil;
  75
+    
  76
+    while (YES) {
  77
+        for (id/*UIKBKey*/ key in keys) {
  78
+            NSString *representedString = [key valueForKey:@"representedString"];
  79
+            
  80
+            // Find the key based on the key's represented string
  81
+            if ([representedString isEqual:characterString]) {
  82
+                keyToTap = key;
  83
+            }
  84
+            
  85
+            if (!modifierKey && unvisitedForKeyplane.count && [[unvisitedForKeyplane objectAtIndex:0] isEqual:representedString]) {
  86
+                modifierKey = key;
  87
+                selectedModifierRepresentedString = representedString;
  88
+                [unvisitedForKeyplane removeObjectAtIndex:0];
  89
+            }
  90
+        }
  91
+        
  92
+        if (keyToTap) {
  93
+            break;
  94
+        }
  95
+        
  96
+        if (modifierKey) {
  97
+            break;
  98
+        }
  99
+        
  100
+        if (!unvisitedForKeyplane.count) {
  101
+            return NO;
  102
+        }
  103
+        
  104
+        // If we didn't find the key or the modifier, then this modifier must not exist on this keyboard. Remove it.
  105
+        [unvisitedForKeyplane removeObjectAtIndex:0];
  106
+    }
  107
+    
  108
+    if (keyToTap) {
  109
+        [keyboardView tapAtPoint:CGPointCenteredInRect([keyToTap frame])];
  110
+        CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
  111
+        
  112
+        return YES;
  113
+    }
  114
+    
  115
+    // We didn't find anything, so try the symbols pane
  116
+    if (modifierKey) {
  117
+        [keyboardView tapAtPoint:CGPointCenteredInRect([modifierKey frame])];
  118
+        CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
  119
+        
  120
+        // If we're back at a place we've been before, and we still have things to explore in the previous
  121
+        id /*UIKBKeyplane*/ newKeyplane = [keyboardView valueForKey:@"keyplane"];
  122
+        id /*UIKBKeyplane*/ previousKeyplane = [history valueForKey:@"previousKeyplane"];
  123
+        
  124
+        if (newKeyplane == previousKeyplane) {
  125
+            // Come back to the keyplane that we just tested so that we can try the other modifiers
  126
+            NSMutableArray *previousKeyplaneHistory = [history objectForKey:[NSValue valueWithNonretainedObject:newKeyplane]];
  127
+            [previousKeyplaneHistory insertObject:[history valueForKey:@"lastModifierRepresentedString"] atIndex:0];
  128
+        } else {
  129
+            [history setValue:keyplane forKey:@"previousKeyplane"];
  130
+            [history setValue:selectedModifierRepresentedString forKey:@"lastModifierRepresentedString"];
  131
+        }
  132
+        
  133
+        return [self _enterCharacter:characterString history:history];
  134
+    }
  135
+    
  136
+    return NO;
  137
+}
  138
+
  139
++ (BOOL)_enterCustomKeyboardCharacter:(NSString *)characterString;
  140
+{
  141
+    const NSTimeInterval keystrokeDelay = 0.05f;
  142
+    
  143
+    if (!characterString.length) {
  144
+        return YES;
  145
+    }
  146
+    
  147
+    characterString = [self _representedKeyboardStringForCharacter:characterString];
  148
+    
  149
+    // For custom keyboards, use the classic methods of looking up views based on accessibility labels
  150
+    UIWindow *keyboardWindow = [[UIApplication sharedApplication] keyboardWindow];
  151
+    
  152
+    UIAccessibilityElement *element = [keyboardWindow accessibilityElementWithLabel:characterString];
  153
+    if (!element) {
  154
+        return NO;
  155
+    }
  156
+    
  157
+    UIView *view = [UIAccessibilityElement viewContainingAccessibilityElement:element];
  158
+    CGRect keyFrame = [view.window convertRect:[element accessibilityFrame] toView:view];
  159
+    [view tapAtPoint:CGPointCenteredInRect(keyFrame)];
  160
+    CFRunLoopRunInMode(kCFRunLoopDefaultMode, keystrokeDelay, false);
  161
+    
  162
+    return YES;
  163
+}
  164
+
  165
+@end
8  KIF.xcodeproj/project.pbxproj
@@ -31,6 +31,8 @@
31 31
 		AAB072B213971AB2008AF393 /* UIWindow-KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = AAB072A213971AB2008AF393 /* UIWindow-KIFAdditions.h */; };
32 32
 		AAB072B313971AB2008AF393 /* UIWindow-KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = AAB072A313971AB2008AF393 /* UIWindow-KIFAdditions.m */; };
33 33
 		AAB072B513971AEA008AF393 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAB072B413971AEA008AF393 /* UIKit.framework */; };
  34
+		C194255815D83DE9004FC314 /* KIFTypist.h in Headers */ = {isa = PBXBuildFile; fileRef = C194255615D83DE9004FC314 /* KIFTypist.h */; };
  35
+		C194255915D83DE9004FC314 /* KIFTypist.m in Sources */ = {isa = PBXBuildFile; fileRef = C194255715D83DE9004FC314 /* KIFTypist.m */; };
34 36
 		CDFD8E86139728B4008D299F /* NSFileManager-KIFAdditions.h in Headers */ = {isa = PBXBuildFile; fileRef = CDFD8E84139728B4008D299F /* NSFileManager-KIFAdditions.h */; };
35 37
 		CDFD8E87139728B4008D299F /* NSFileManager-KIFAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CDFD8E85139728B4008D299F /* NSFileManager-KIFAdditions.m */; };
36 38
 /* End PBXBuildFile section */
@@ -62,6 +64,8 @@
62 64
 		AAB072A213971AB2008AF393 /* UIWindow-KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIWindow-KIFAdditions.h"; sourceTree = "<group>"; };
63 65
 		AAB072A313971AB2008AF393 /* UIWindow-KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIWindow-KIFAdditions.m"; sourceTree = "<group>"; };
64 66
 		AAB072B413971AEA008AF393 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
  67
+		C194255615D83DE9004FC314 /* KIFTypist.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KIFTypist.h; sourceTree = "<group>"; };
  68
+		C194255715D83DE9004FC314 /* KIFTypist.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KIFTypist.m; sourceTree = "<group>"; };
65 69
 		CDFD8E84139728B4008D299F /* NSFileManager-KIFAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSFileManager-KIFAdditions.h"; sourceTree = "<group>"; };
66 70
 		CDFD8E85139728B4008D299F /* NSFileManager-KIFAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager-KIFAdditions.m"; sourceTree = "<group>"; };
67 71
 /* End PBXFileReference section */
@@ -126,6 +130,8 @@
126 130
 				AAB0728913971A98008AF393 /* KIFTestScenario.m */,
127 131
 				AAB0728A13971A98008AF393 /* KIFTestStep.h */,
128 132
 				AAB0728B13971A98008AF393 /* KIFTestStep.m */,
  133
+				C194255615D83DE9004FC314 /* KIFTypist.h */,
  134
+				C194255715D83DE9004FC314 /* KIFTypist.m */,
129 135
 			);
130 136
 			path = Classes;
131 137
 			sourceTree = "<group>";
@@ -174,6 +180,7 @@
174 180
 				AAB072B213971AB2008AF393 /* UIWindow-KIFAdditions.h in Headers */,
175 181
 				CDFD8E86139728B4008D299F /* NSFileManager-KIFAdditions.h in Headers */,
176 182
 				39160B1113D1E6BB00311E38 /* LoadableCategory.h in Headers */,
  183
+				C194255815D83DE9004FC314 /* KIFTypist.h in Headers */,
177 184
 			);
178 185
 			runOnlyForDeploymentPostprocessing = 0;
179 186
 		};
@@ -238,6 +245,7 @@
238 245
 				AAB072B113971AB2008AF393 /* UIView-KIFAdditions.m in Sources */,
239 246
 				AAB072B313971AB2008AF393 /* UIWindow-KIFAdditions.m in Sources */,
240 247
 				CDFD8E87139728B4008D299F /* NSFileManager-KIFAdditions.m in Sources */,
  248
+				C194255915D83DE9004FC314 /* KIFTypist.m in Sources */,
241 249
 			);
242 250
 			runOnlyForDeploymentPostprocessing = 0;
243 251
 		};
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.