Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

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
This page is out of date. Refresh to see the latest.
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.