Skip to content

Commit

Permalink
Added a few methods to KIF to allow recording against internal views …
Browse files Browse the repository at this point in the history
…which can't have accessibilityIdentifiers set.

Removed some old and/or redundant code.
  • Loading branch information
morganTS committed May 13, 2014
1 parent 977cb40 commit 1c6c059
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 37 deletions.
9 changes: 9 additions & 0 deletions Classes/KIFUITestActor.h
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@ static inline KIFDisplacement KIFDisplacementForSwipingInDirection(KIFSwipeDirec
*/
- (void)tapRowAtIndexPath:(NSIndexPath *)indexPath inTableViewWithAccessibilityIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0);

- (void)tapInternalViewOfClass:(Class)classType inViewWithAccessibilityIdentifier:(NSString *)identifier;
- (void)tapInternalViewOfClass:(Class)classType ofRowAtIndexPath:(NSIndexPath *)indexPath inTableViewWithAccessibilityIdentifier:(NSString *)identifier;

/*!
@abstract Taps the item at indexPath in a collection view with the given identifier.
@discussion This step will get the view with the specified accessibility identifier and tap the item at indexPath.
Expand All @@ -355,6 +358,8 @@ static inline KIFDisplacement KIFDisplacementForSwipingInDirection(KIFSwipeDirec
*/
- (void)tapItemAtIndexPath:(NSIndexPath *)indexPath inCollectionViewWithAccessibilityIdentifier:(NSString *)identifier;

#pragma mark - Swipe Methods

/*!
@abstract Swipes a particular view in the view hierarchy in the given direction.
@discussion The view will get the view with the specified accessibility label and swipe the screen in the given direction from the view's center.
Expand All @@ -364,6 +369,8 @@ static inline KIFDisplacement KIFDisplacementForSwipingInDirection(KIFSwipeDirec
- (void)swipeViewWithAccessibilityLabel:(NSString *)label inDirection:(KIFSwipeDirection)direction;
- (void)swipeViewWithAccessibilityIdentifier:(NSString *)identifier inDirection:(KIFSwipeDirection)direction;

#pragma mark - Scrolling Methods

/*!
@abstract Scrolls a particular view in the view hierarchy by an amount indicated as a fraction of its size.
@discussion The view will get the view with the specified accessibility label and scroll it by the indicated fraction of its size, with the scroll centered on the center of the view.
Expand All @@ -382,6 +389,8 @@ static inline KIFDisplacement KIFDisplacementForSwipingInDirection(KIFSwipeDirec
*/
- (void)scrollViewWithAccessibilityIdentifier:(NSString *)identifier byFractionOfSizeHorizontal:(CGFloat)horizontalFraction vertical:(CGFloat)verticalFraction NS_AVAILABLE_IOS(5_0);

- (void)scrollCellAtIndexPath:(NSIndexPath *)indexPath inTableViewWithAccessibilityIdentifier:(NSString *)identifier byFractionOfSizeHorizontal:(CGFloat)horizontalFraction vertical:(CGFloat)verticalFraction;

/*!
@abstract Waits until a view or accessibility element is the first responder.
@discussion The first responder is found by searching the view hierarchy of the application's
Expand Down
44 changes: 44 additions & 0 deletions Classes/KIFUITestActor.m
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,38 @@ - (void)tapRowAtIndexPath:(NSIndexPath *)indexPath inTableView:(UITableView *)ta
[tableView tapAtPoint:CGPointCenteredInRect(cellFrame)];
}

- (void)tapInternalViewOfClass:(Class)classType inViewWithAccessibilityIdentifier:(NSString *)identifier {
UIView *view = nil;
UIAccessibilityElement *element = nil;

[self waitForAccessibilityElement:&element view:&view withIdentifier:identifier traits:nil tappable:YES];
NSArray *controlArray = [view subviewsWithClassNameOrSuperClassNamePrefix:NSStringFromClass(classType)];
if (controlArray.count == 1) {
UIView *targetView = controlArray[0];
[self tapAccessibilityElement:(UIAccessibilityElement *)targetView inView:targetView];
}
else {
NSString *errorString = (controlArray.count > 0 ? @"Found too many internal views of type \"%@\"" : @"Could not find internal view of type \"%@\"");
[self failWithError:[NSError KIFErrorWithFormat:errorString, NSStringFromClass(classType)] stopTest:YES];
}
}

- (void)tapInternalViewOfClass:(Class)classType ofRowAtIndexPath:(NSIndexPath *)indexPath inTableViewWithAccessibilityIdentifier:(NSString *)identifier {
UITableView *tableView;
[self waitForAccessibilityElement:NULL view:&tableView withIdentifier:identifier tappable:NO];
UITableViewCell *cell = [self waitForCellAtIndexPath:indexPath inTableView:tableView];
NSArray *controlArray = [cell subviewsWithClassNameOrSuperClassNamePrefix:NSStringFromClass(classType)];
if (controlArray.count == 1) {
UIView *targetView = controlArray[0];
CGRect buttonFrame = [targetView convertRect:targetView.frame toView:tableView];
[tableView tapAtPoint:CGPointCenteredInRect(buttonFrame)];
}
else {
NSString *errorString = (controlArray.count > 0 ? @"Found too many internal views of type \"%@\"" : @"Could not find internal view of type \"%@\"");
[self failWithError:[NSError KIFErrorWithFormat:errorString, NSStringFromClass(classType)] stopTest:YES];
}
}

- (void)tapItemAtIndexPath:(NSIndexPath *)indexPath inCollectionViewWithAccessibilityIdentifier:(NSString *)identifier
{
UICollectionView *collectionView;
Expand Down Expand Up @@ -685,6 +717,8 @@ - (void)swipeElement:(UIAccessibilityElement *)element inView:(UIView *)viewToSw
[viewToSwipe dragFromPoint:swipeStart displacement:swipeDisplacement steps:kNumberOfPointsInSwipePath];
}

#pragma mark - Scroll Methods

- (void)scrollViewWithAccessibilityLabel:(NSString *)label byFractionOfSizeHorizontal:(CGFloat)horizontalFraction vertical:(CGFloat)verticalFraction
{
UIView *viewToScroll;
Expand All @@ -701,6 +735,16 @@ - (void)scrollViewWithAccessibilityIdentifier:(NSString *)identifier byFractionO
[self scrollAccessibilityElement:element inView:viewToScroll byFractionOfSizeHorizontal:horizontalFraction vertical:verticalFraction];
}

- (void)scrollCellAtIndexPath:(NSIndexPath *)indexPath inTableViewWithAccessibilityIdentifier:(NSString *)identifier byFractionOfSizeHorizontal:(CGFloat)horizontalFraction vertical:(CGFloat)verticalFraction {
UITableView *tableView;
[self waitForAccessibilityElement:NULL view:&tableView withIdentifier:identifier tappable:NO];

// Wait for the cell, convert it to a 'UIAccessibilityElement' and scroll it
UITableViewCell *cell = [self waitForCellAtIndexPath:indexPath inTableView:tableView];
UIAccessibilityElement *element = (UIAccessibilityElement *)cell;
[self scrollAccessibilityElement:element inView:cell byFractionOfSizeHorizontal:horizontalFraction vertical:verticalFraction];
}

- (void)scrollAccessibilityElement:(UIAccessibilityElement *)element inView:(UIView *)viewToScroll byFractionOfSizeHorizontal:(CGFloat)horizontalFraction vertical:(CGFloat)verticalFraction
{
const NSUInteger kNumberOfPointsInScrollPath = 5;
Expand Down
5 changes: 5 additions & 0 deletions KIF.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,10 @@
"$(DEVELOPER_FRAMEWORKS_DIR)",
);
GCC_PREFIX_HEADER = "Classes/KIF-XCTestPrefix.pch";
GCC_PREPROCESSOR_DEFINITIONS = (
DEBUG,
"KIF_XCTEST=1",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "KIF-XCTest";
};
Expand Down Expand Up @@ -1083,6 +1087,7 @@
"$(DEVELOPER_FRAMEWORKS_DIR)",
);
GCC_PREFIX_HEADER = "Classes/KIF-XCTestPrefix.pch";
GCC_PREPROCESSOR_DEFINITIONS = "KIF_XCTEST=1";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "KIF-XCTest";
};
Expand Down
1 change: 1 addition & 0 deletions KIFRecorder/KIFRecorder/Classes/KIFRecorder.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//
// KIFRecorder.h
// KIFRecorder
// Version 0.0.15
//
// Created by Morgan Pretty on 1/04/2014.
// Copyright (c) 2014 Tigerspike. All rights reserved.
Expand Down
2 changes: 2 additions & 0 deletions KIFRecorder/KIFRecorder/Classes/Test Data/KIFRTargetInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
@property (nonatomic, copy) NSString *accessibilityIdentifier;
@property (nonatomic, copy) Class targetClass;
@property (nonatomic, assign) CGRect frame;
@property (nonatomic, copy) Class internalTargetClass;
@property (nonatomic, assign) BOOL isTargettingInternalSubview;

// UITableView Specific
@property (nonatomic, copy) NSString *tableViewAccessibilityIdentifier;
Expand Down
8 changes: 0 additions & 8 deletions KIFRecorder/KIFRecorder/Classes/Test Data/KIFRTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,6 @@ - (void)addStepForTestEvent:(KIFRTestEvent *)testEvent {
testStep.originalIndex = self.testStepsArray.count;

[self.testStepsArray addObject:testStep];

// In this one case we need to create an additional step
if (testStep.stepType == KIFRStepTypeWaitForTableCell) {
KIFRTestStep *tapStep = [testStep createActualTapStep];
tapStep.originalIndex = self.testStepsArray.count;

[self.testStepsArray addObject:tapStep];
}
}

#pragma mark - Saving
Expand Down
1 change: 1 addition & 0 deletions KIFRecorder/KIFRecorder/Classes/Test Data/KIFRTestEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

@property (nonatomic, copy) NSString *eventID;
@property (nonatomic, weak) UIView *targetView;
@property (nonatomic, weak) UIView *internalTargetView;
@property (nonatomic, strong) NSArray *potentialTargets;

@property (nonatomic, strong) KIFRTargetInfo *targetInfo;
Expand Down
26 changes: 26 additions & 0 deletions KIFRecorder/KIFRecorder/Classes/Test Data/KIFRTestEvent.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@

@implementation KIFRTestEvent

+ (NSArray *)classesWithInternalViews {
return @[
[UITableViewCell class],
[UISearchBar class]
];
}

+ (KIFRTestEvent *)eventWithID:(NSString *)eventID touches:(NSArray *)touches targetView:(UIView *)target andPotentialTargets:(NSArray *)potentialTargets {
KIFRTestEvent *testEvent = [KIFRTestEvent new];
testEvent.eventID = eventID;
Expand Down Expand Up @@ -95,6 +102,25 @@ - (void)endWithTouches:(NSArray *)touches {
[self finalizeUISegmentedControlWithTouches:touches];
}

// Check if we tapped an 'internal view' (a control which we don't have direct access to)
if ([[KIFRTestEvent classesWithInternalViews] containsObject:self.targetInfo.targetClass]) {
// Are we tapping an internal view inside the TableViewCell?
UITouch *touch = touches.firstObject;
for (UITouch *eventTouch in touches) {
if (eventTouch.view) {
touch = eventTouch;
break;
}
}

// If the touch's view is not the same as the targetClass (then it's probably an internal view)
if (![touch.view isKindOfClass:self.targetInfo.targetClass]) {
self.targetInfo.isTargettingInternalSubview = YES;
self.internalTargetView = touch.view;
self.targetInfo.internalTargetClass = [touch.view class];
}
}

// We don't want to keep hanging on the the 'targetView' or 'potentialTargets' references (to avoid memory issues, also the red 'BorderView' will only disappear once this value has been cleared)
self.targetView = nil;
self.potentialTargets = nil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
@interface KIFRTestStep (ToString)

- (void)generateStepData;
- (KIFRTestStep *)createActualTapStep;
- (KIFRTestStep *)createStepToWaitForCell;

@end
47 changes: 35 additions & 12 deletions KIFRecorder/KIFRecorder/Classes/Test Data/KIFRTestStep+ToString.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ - (void)generateStepData {
}
}

- (KIFRTestStep *)createActualTapStep {
// Should only call this on a step which is waiting for a UITableViewCell
if (!self.stepType == KIFRStepTypeWaitForTableCell) {
- (KIFRTestStep *)createStepToWaitForCell {
// Should only call this for a UITableViewCell
if ([self.testEventData.targetInfo.targetClass isSubclassOfClass:[UITableViewCell class]]) {
return nil;
}

Expand All @@ -59,8 +59,8 @@ - (KIFRTestStep *)createActualTapStep {
step.stepType = KIFRStepTypeTapTableCell;

KIFRTargetInfo *targetInfo = self.testEventData.targetInfo;
step.readableString = [NSString stringWithFormat:@"Tap cell at (%li, %li) in the table '%@'.", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
step.testString = [NSString stringWithFormat:@"\n [tester tapRowAtIndexPath:[NSIndexPath indexPathForRow:%li inSection:%li] inTableViewWithAccessibilityIdentifier:@\"%@\"];\n", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
self.readableString = [NSString stringWithFormat:@"Wait for cell at (%li, %li) in the table '%@'.", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
self.testString = [NSString stringWithFormat:@"\n [tester waitForRowAtIndexPath:[NSIndexPath indexPathForRow:%li inSection:%li] inTableViewWithAccessibilityIdentifier:@\"%@\"];", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];

return step;
}
Expand All @@ -71,13 +71,30 @@ - (void)generateTapStep {
KIFRTargetInfo *targetInfo = self.testEventData.targetInfo;

if (self.testEventData.numberOfTaps == 1) {
// If it's a UITableViewCell then use the specific method
if ([targetInfo.targetClass isSubclassOfClass:[UITableViewCell class]]) {
self.stepType = KIFRStepTypeWaitForTableCell;
self.readableString = [NSString stringWithFormat:@"Wait for cell at (%li, %li) in the table '%@'.", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
self.testString = [NSString stringWithFormat:@"\n [tester waitForRowAtIndexPath:[NSIndexPath indexPathForRow:%li inSection:%li] inTableViewWithAccessibilityIdentifier:@\"%@\"];", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
if (targetInfo.isTargettingInternalSubview) {
// Internal views need to be targetted slightly differently
if ([targetInfo.targetClass isSubclassOfClass:[UITableViewCell class]]) {
self.stepType = KIFRStepTypeTapTableCell;

self.readableString = [NSString stringWithFormat:@"Tap internal class '%@' of cell at (%li, %li) in the table '%@'.", targetInfo.internalTargetClass, (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
self.testString = [NSString stringWithFormat:@"\n [tester tapInternalViewOfClass:NSClassFromString(@\"%@\") ofRowAtIndexPath:[NSIndexPath indexPathForRow:%li inSection:%li] inTableViewWithAccessibilityIdentifier:@\"%@\"];\n", targetInfo.internalTargetClass, (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
}
else {
self.stepType = KIFRStepTypeTap;

self.readableString = [NSString stringWithFormat:@"Tap internal class '%@' of view '%@'.", targetInfo.internalTargetClass, targetInfo.tableViewAccessibilityIdentifier];
self.testString = [NSString stringWithFormat:@"\n [tester tapInternalViewOfClass:NSClassFromString(@\"%@\") inViewWithAccessibilityIdentifier:@\"%@\"];\n", targetInfo.internalTargetClass, targetInfo.tableViewAccessibilityIdentifier];
}
}
else if ([targetInfo.targetClass isSubclassOfClass:[UITableViewCell class]]) {
// If it's a UITableViewCell then use the specific method
self.stepType = KIFRStepTypeTapTableCell;

self.readableString = [NSString stringWithFormat:@"Tap cell at (%li, %li) in the table '%@'.", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
self.testString = [NSString stringWithFormat:@"\n [tester tapRowAtIndexPath:[NSIndexPath indexPathForRow:%li inSection:%li] inTableViewWithAccessibilityIdentifier:@\"%@\"];\n", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier];
}
else if ([targetInfo.targetClass isSubclassOfClass:[UISegmentedControl class]]) {
// If it's a UISegmentedControl then use the specific method
self.stepType = KIFRStepTypeSelectSegment;
self.readableString = [NSString stringWithFormat:@"Tap segment %li in the segment control '%@'.", (long)targetInfo.selectedIndex, targetInfo.accessibilityIdentifier];
self.testString = [NSString stringWithFormat:@"\n [tester tapSegmentAtIndex:%li inSegmentedControlWithAccessibilityIdentifier:@\"%@\"];", (long)targetInfo.selectedIndex, targetInfo.accessibilityIdentifier];
Expand Down Expand Up @@ -124,8 +141,14 @@ - (void)generatePanStep {
CGFloat horizontalFraction = (xDistance / targetInfo.frame.size.width);
CGFloat verticalFraction = (yDistance / targetInfo.frame.size.height);

self.readableString = [NSString stringWithFormat:@"Scroll view '%@' by %.0f%% width and %.0f%% height.", targetInfo.accessibilityIdentifier, (horizontalFraction * 100), (verticalFraction * 100)];
self.testString = [NSString stringWithFormat:@"\n [tester scrollViewWithAccessibilityIdentifier:@\"%@\" byFractionOfSizeHorizontal:%f vertical:%f];", targetInfo.accessibilityIdentifier, horizontalFraction, verticalFraction];
if ([targetInfo.targetClass isSubclassOfClass:[UITableViewCell class]]) {
self.readableString = [NSString stringWithFormat:@"Scroll cell at (%li, %li) in the table '%@' by %.0f%% width and %.0f%% height.", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.tableViewAccessibilityIdentifier, (horizontalFraction * 100), (verticalFraction * 100)];
self.testString = [NSString stringWithFormat:@"\n [tester scrollCellAtIndexPath:[NSIndexPath indexPathForRow:%li inSection:%li] inTableViewWithAccessibilityIdentifier:@\"%@\" byFractionOfSizeHorizontal:%f vertical:%f];", (long)targetInfo.cellIndexPath.row, (long)targetInfo.cellIndexPath.section, targetInfo.accessibilityIdentifier, horizontalFraction, verticalFraction];
}
else {
self.readableString = [NSString stringWithFormat:@"Scroll view '%@' by %.0f%% width and %.0f%% height.", targetInfo.accessibilityIdentifier, (horizontalFraction * 100), (verticalFraction * 100)];
self.testString = [NSString stringWithFormat:@"\n [tester scrollViewWithAccessibilityIdentifier:@\"%@\" byFractionOfSizeHorizontal:%f vertical:%f];", targetInfo.accessibilityIdentifier, horizontalFraction, verticalFraction];
}
}
else {
NSLog(@"Warning - Attempt to have unsupported multi-finger pan!");
Expand Down
23 changes: 7 additions & 16 deletions KIFRecorder/KIFRecorder/Classes/UI Components/KIFRBorderView.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,23 @@ + (void)createBorderForEvent:(KIFRTestEvent *)event {
}

// Is it better to attach border to the window or the view?
BOOL attachToWindow = YES;
KIFRBorderView *borderView = [KIFRBorderView new];
borderView.event = event;
borderView.userInteractionEnabled = NO;
borderView.layer.borderWidth = 3;
borderView.layer.borderColor = [UIColor redColor].CGColor;

// Create a red border around the tapped view
if (attachToWindow) {
if (event.isKeyboardEvent) {
borderView.frame = [event.targetView.window convertRect:event.keyboardKeyFrame fromView:event.targetView];
}
else {
borderView.frame = [event.targetView.window convertRect:event.targetView.frame fromView:event.targetView.superview];
}
[event.targetView.window addSubview:borderView];
if (event.isKeyboardEvent) {
borderView.frame = [event.targetView.window convertRect:event.keyboardKeyFrame fromView:event.targetView];
}
else if (event.targetInfo.isTargettingInternalSubview) {
borderView.frame = [event.internalTargetView.window convertRect:event.internalTargetView.frame fromView:event.internalTargetView.superview];
}
else {
if (event.isKeyboardEvent) {
borderView.frame = event.keyboardKeyFrame;
}
else {
borderView.frame = event.targetView.bounds;
}
[event.targetView addSubview:borderView];
borderView.frame = [event.targetView.window convertRect:event.targetView.frame fromView:event.targetView.superview];
}
[event.targetView.window addSubview:borderView];

[borderView tryRemoveBorder];
}
Expand Down

0 comments on commit 1c6c059

Please sign in to comment.