Skip to content
This repository has been archived by the owner on Dec 26, 2019. It is now read-only.

Commit

Permalink
force touch support
Browse files Browse the repository at this point in the history
Summary:
A couple of commands which make possible to force touch given element or point by coordinates. In fact – copy of the tap extension. Baked with integration tests.

Updated with mykola-mokhnach improvements in  appium#79
Closes #917

Differential Revision: D8220249

Pulled By: marekcirkos

fbshipit-source-id: 2a14ab5759894577a1f5e40f20b4a6d79e519419
  • Loading branch information
alexander.balaban authored and facebook-github-bot committed Jun 4, 2018
1 parent 762760d commit a5b61b4
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 10 deletions.
12 changes: 12 additions & 0 deletions WebDriverAgent.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,9 @@
EE7E271F1D06C69F001BEC7B /* FBXCTestCaseImplementationFailureHoldingProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = EE7E271B1D06C69F001BEC7B /* FBXCTestCaseImplementationFailureHoldingProxy.m */; };
EE8BA97A1DCCED9A00A9DEF8 /* FBNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA9791DCCED9A00A9DEF8 /* FBNavigationController.m */; };
EE8DDD7920C565FB004D4925 /* XCUIApplicationFBHelpersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8DDD7820C565FB004D4925 /* XCUIApplicationFBHelpersTests.m */; };
EE8DDD7B20C57320004D4925 /* FBForceTouchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8DDD7A20C57320004D4925 /* FBForceTouchTests.m */; };
EE8DDD7E20C5733C004D4925 /* XCUIElement+FBForceTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8DDD7C20C5733B004D4925 /* XCUIElement+FBForceTouch.m */; };
EE8DDD7F20C5733C004D4925 /* XCUIElement+FBForceTouch.h in Headers */ = {isa = PBXBuildFile; fileRef = EE8DDD7D20C5733C004D4925 /* XCUIElement+FBForceTouch.h */; settings = {ATTRIBUTES = (Public, ); }; };
EE9AB8011CAEE048008C271F /* UITestingUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7FD1CAEE048008C271F /* UITestingUITests.m */; };
EE9B76591CF7987800275851 /* FBRouteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76571CF7987300275851 /* FBRouteTests.m */; };
EE9B768E1CF7997600275851 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76831CF7997600275851 /* AppDelegate.m */; };
Expand Down Expand Up @@ -613,6 +616,9 @@
EE8BA9781DCCED9A00A9DEF8 /* FBNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBNavigationController.h; sourceTree = "<group>"; };
EE8BA9791DCCED9A00A9DEF8 /* FBNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBNavigationController.m; sourceTree = "<group>"; };
EE8DDD7820C565FB004D4925 /* XCUIApplicationFBHelpersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCUIApplicationFBHelpersTests.m; sourceTree = "<group>"; };
EE8DDD7A20C57320004D4925 /* FBForceTouchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBForceTouchTests.m; sourceTree = "<group>"; };
EE8DDD7C20C5733B004D4925 /* XCUIElement+FBForceTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIElement+FBForceTouch.m"; sourceTree = "<group>"; };
EE8DDD7D20C5733C004D4925 /* XCUIElement+FBForceTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBForceTouch.h"; sourceTree = "<group>"; };
EE9AB7451CAEDF0C008C271F /* XCUIElement+FBAccessibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBAccessibility.h"; sourceTree = "<group>"; };
EE9AB7461CAEDF0C008C271F /* XCUIElement+FBAccessibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIElement+FBAccessibility.m"; sourceTree = "<group>"; };
EE9AB7471CAEDF0C008C271F /* XCUIElement+FBIsVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBIsVisible.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -911,6 +917,8 @@
71A7EAF41E20516B001DA4F2 /* XCUIElement+FBClassChain.m */,
EEBBD4891D47746D00656A81 /* XCUIElement+FBFind.h */,
EEBBD48A1D47746D00656A81 /* XCUIElement+FBFind.m */,
EE8DDD7D20C5733C004D4925 /* XCUIElement+FBForceTouch.h */,
EE8DDD7C20C5733B004D4925 /* XCUIElement+FBForceTouch.m */,
EE9AB7471CAEDF0C008C271F /* XCUIElement+FBIsVisible.h */,
EE9AB7481CAEDF0C008C271F /* XCUIElement+FBIsVisible.m */,
7136A4771E8918E60024FC3D /* XCUIElement+FBPickerWheel.h */,
Expand Down Expand Up @@ -1075,6 +1083,7 @@
7126FF0D1FD99C7E00DEFB38 /* FBElementScreenshotTests.m */,
EE006EAC1EB99B15006900A4 /* FBElementVisibilityTests.m */,
EE6A89361D0B35920083E92B /* FBFailureProofTestCaseTests.m */,
EE8DDD7A20C57320004D4925 /* FBForceTouchTests.m */,
EE1E06DB1D18090F007CF043 /* FBIntegrationTestCase.h */,
EE1E06D91D1808C2007CF043 /* FBIntegrationTestCase.m */,
EE05BAF91D13003C00A3EB00 /* FBKeyboardTests.m */,
Expand Down Expand Up @@ -1421,6 +1430,7 @@
711084441DA3AA7500F913D6 /* FBXPath.h in Headers */,
EE35AD771E3B77D600A02D78 /* XCUIRecorderTimingMessage.h in Headers */,
EE35AD271E3B77D600A02D78 /* XCApplicationMonitor.h in Headers */,
EE8DDD7F20C5733C004D4925 /* XCUIElement+FBForceTouch.h in Headers */,
EE158AEA1CBD456F00A3E3F0 /* FBRuntimeUtils.h in Headers */,
7136A4791E8918E60024FC3D /* XCUIElement+FBPickerWheel.h in Headers */,
EE35AD511E3B77D600A02D78 /* XCTestObservation-Protocol.h in Headers */,
Expand Down Expand Up @@ -1777,6 +1787,7 @@
EE158AF81CBD456F00A3E3F0 /* FBSpringboardApplication.m in Sources */,
EE158AD91CBD456F00A3E3F0 /* FBResponseFilePayload.m in Sources */,
EEE3764A1D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m in Sources */,
EE8DDD7E20C5733C004D4925 /* XCUIElement+FBForceTouch.m in Sources */,
EE158ACB1CBD456F00A3E3F0 /* FBTouchIDCommands.m in Sources */,
EE158ABD1CBD456F00A3E3F0 /* FBDebugCommands.m in Sources */,
716E0BCF1E917E810087A825 /* NSString+FBXMLSafeString.m in Sources */,
Expand Down Expand Up @@ -1821,6 +1832,7 @@
EE2202131ECC612200A29571 /* FBIntegrationTestCase.m in Sources */,
EE22021E1ECC618900A29571 /* FBTapTest.m in Sources */,
7126FF0E1FD99C7E00DEFB38 /* FBElementScreenshotTests.m in Sources */,
EE8DDD7B20C57320004D4925 /* FBForceTouchTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
39 changes: 39 additions & 0 deletions WebDriverAgentLib/Categories/XCUIElement+FBForceTouch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import <WebDriverAgentLib/XCUIElement.h>

NS_ASSUME_NONNULL_BEGIN

@interface XCUIElement (FBForceTouch)

/**
Waits for element to become stable (not move) and performs sync force touch on element
@param error If there is an error, upon return contains an NSError object that describes the problem.
@param pressure The pressure of the force touch – valid values are [0, touch.maximumPossibleForce]
@param duration The duration of the gesture
@return YES if the operation succeeds, otherwise NO.
*/
- (BOOL)fb_forceTouchWithPressure:(double)pressure duration:(double)duration error:(NSError **)error;

/**
Waits for element to become stable (not move) and performs sync force touch on element
@param relativeCoordinate hit point coordinate relative to the current element position
@param pressure The pressure of the force touch – valid values are [0, touch.maximumPossibleForce]
@param duration The duration of the gesture
@param error If there is an error, upon return contains an NSError object that describes the problem.
@return YES if the operation succeeds, otherwise NO.
*/
- (BOOL)fb_forceTouchCoordinate:(CGPoint)relativeCoordinate pressure:(double)pressure duration:(double)duration error:(NSError **)error;

@end

NS_ASSUME_NONNULL_END
89 changes: 89 additions & 0 deletions WebDriverAgentLib/Categories/XCUIElement+FBForceTouch.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

#import "XCUIElement+FBForceTouch.h"

#import "FBRunLoopSpinner.h"
#import "FBLogger.h"
#import "FBMacros.h"
#import "FBMathUtils.h"
#import "XCUIElement+FBUtilities.h"
#import "XCEventGenerator.h"
#import "XCSynthesizedEventRecord.h"
#import "XCElementSnapshot+FBHitPoint.h"
#import "XCPointerEventPath.h"
#import "XCTRunnerDaemonSession.h"

@implementation XCUIElement (FBForceTouch)

- (BOOL)fb_forceTouchWithPressure:(double)pressure duration:(double)duration error:(NSError **)error
{
XCElementSnapshot *snapshot = self.fb_lastSnapshot;
CGPoint hitpoint = snapshot.fb_hitPoint;
if (CGPointEqualToPoint(hitpoint, CGPointMake(-1, -1))) {
hitpoint = [snapshot.suggestedHitpoints.lastObject CGPointValue];
}
return [self fb_performFourceTouchAtPoint:hitpoint pressure:pressure duration:duration error:error];
}

- (BOOL)fb_forceTouchCoordinate:(CGPoint)relativeCoordinate pressure:(double)pressure duration:(double)duration error:(NSError **)error
{
CGPoint hitPoint = CGPointMake(self.frame.origin.x + relativeCoordinate.x, self.frame.origin.y + relativeCoordinate.y);
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
/*
Since iOS 10.0 XCTest has a bug when it always returns portrait coordinates for UI elements
even if the device is not in portait mode. That is why we need to recalculate them manually
based on the current orientation value
*/
hitPoint = FBInvertPointForApplication(hitPoint, self.application.frame.size, self.application.interfaceOrientation);
}
return [self fb_performFourceTouchAtPoint:hitPoint pressure:pressure duration:duration error:error];
}

- (BOOL)fb_performFourceTouchAtPoint:(CGPoint)hitPoint pressure:(double)pressure duration:(double)duration error:(NSError *__autoreleasing*)error
{
[self fb_waitUntilFrameIsStable];
__block BOOL didSucceed;
[FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
XCEventGeneratorHandler handlerBlock = ^(XCSynthesizedEventRecord *record, NSError *commandError) {
if (commandError) {
[FBLogger logFmt:@"Failed to perform force touch: %@", commandError];
}
if (error) {
*error = commandError;
}
didSucceed = (commandError == nil);
completion();
};

XCSynthesizedEventRecord *event = [self fb_generateForceTouchEvent:hitPoint pressure:pressure duration:duration orientation:self.interfaceOrientation];
[[XCTRunnerDaemonSession sharedSession] synthesizeEvent:event completion:^(NSError *invokeError){
handlerBlock(event, invokeError);
}];
}];
return didSucceed;
}

- (XCSynthesizedEventRecord *)fb_generateForceTouchEvent:(CGPoint)hitPoint pressure:(double)pressure duration:(double)duration orientation:(UIInterfaceOrientation)orientation
{
XCPointerEventPath *eventPath = [[XCPointerEventPath alloc] initForTouchAtPoint:hitPoint offset:0.0];
[eventPath pressDownWithPressure:pressure atOffset:0.0];
if (![XCTRunnerDaemonSession sharedSession].useLegacyEventCoordinateTransformationPath) {
orientation = UIInterfaceOrientationPortrait;
}
[eventPath liftUpAtOffset:duration];
XCSynthesizedEventRecord *event =
[[XCSynthesizedEventRecord alloc]
initWithName:[NSString stringWithFormat:@"Force touch on %@", NSStringFromCGPoint(hitPoint)]
interfaceOrientation:orientation];
[event addPointerEventPath:eventPath];
return event;
}

@end
33 changes: 32 additions & 1 deletion WebDriverAgentLib/Commands/FBElementCommands.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
#import "FBMathUtils.h"
#import "FBRuntimeUtils.h"
#import "NSPredicate+FBFormat.h"
#import "XCEventGenerator.h"
#import "XCUICoordinate.h"
#import "XCUIDevice.h"
#import "XCUIElement+FBIsVisible.h"
#import "XCUIElement+FBPickerWheel.h"
#import "XCUIElement+FBScrolling.h"
#import "XCUIElement+FBTap.h"
#import "XCUIElement+FBForceTouch.h"
#import "XCUIElement+FBTyping.h"
#import "XCUIElement+FBUtilities.h"
#import "XCUIElement+FBWebDriverAttributes.h"
Expand Down Expand Up @@ -74,7 +76,9 @@ + (NSArray *)routes
[[FBRoute POST:@"/wda/touchAndHold"] respondWithTarget:self action:@selector(handleTouchAndHoldCoordinate:)],
[[FBRoute POST:@"/wda/doubleTap"] respondWithTarget:self action:@selector(handleDoubleTapCoordinate:)],
[[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)],
[[FBRoute POST:@"/wda/pickerwheel/:uuid/select"] respondWithTarget:self action:@selector(handleWheelSelect:)]
[[FBRoute POST:@"/wda/pickerwheel/:uuid/select"] respondWithTarget:self action:@selector(handleWheelSelect:)],
[[FBRoute POST:@"/wda/element/forceTouch/:uuid"] respondWithTarget:self action:@selector(handleForceTouch:)],
[[FBRoute POST:@"/wda/element/forceTouchByCoordinate/:uuid"] respondWithTarget:self action:@selector(handleForceTouchByCoordinateOnElement:)]
];
}

Expand Down Expand Up @@ -241,6 +245,33 @@ + (NSArray *)routes
return FBResponseWithOK();
}

+ (id<FBResponsePayload>)handleForceTouch:(FBRouteRequest *)request
{
FBElementCache *elementCache = request.session.elementCache;
XCUIElement *element = [elementCache elementForUUID:request.parameters[@"uuid"]];
double pressure = [request.arguments[@"pressure"] doubleValue];
double duration = [request.arguments[@"duration"] doubleValue];
NSError *error = nil;
if (![element fb_forceTouchWithPressure:pressure duration:duration error:&error]) {
return FBResponseWithError(error);
}
return FBResponseWithOK();
}

+ (id<FBResponsePayload>)handleForceTouchByCoordinateOnElement:(FBRouteRequest *)request
{
FBElementCache *elementCache = request.session.elementCache;
XCUIElement *element = [elementCache elementForUUID:request.parameters[@"uuid"]];
double pressure = [request.arguments[@"pressure"] doubleValue];
double duration = [request.arguments[@"duration"] doubleValue];
CGPoint forceTouchPoint = CGPointMake((CGFloat)[request.arguments[@"x"] doubleValue], (CGFloat)[request.arguments[@"y"] doubleValue]);
NSError *error = nil;
if (![element fb_forceTouchCoordinate:forceTouchPoint pressure:pressure duration:duration error:&error]) {
return FBResponseWithError(error);
}
return FBResponseWithOK();
}

+ (id<FBResponsePayload>)handleScroll:(FBRouteRequest *)request
{
FBElementCache *elementCache = request.session.elementCache;
Expand Down
1 change: 1 addition & 0 deletions WebDriverAgentLib/WebDriverAgentLib.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,6 @@ FOUNDATION_EXPORT const unsigned char WebDriverAgentLib_VersionString[];
#import <WebDriverAgentLib/XCUIElement+FBIsVisible.h>
#import <WebDriverAgentLib/XCUIElement+FBScrolling.h>
#import <WebDriverAgentLib/XCUIElement+FBTap.h>
#import <WebDriverAgentLib/XCUIElement+FBForceTouch.h>
#import <WebDriverAgentLib/XCUIElement+FBUtilities.h>
#import <WebDriverAgentLib/XCUIElement+FBWebDriverAttributes.h>
25 changes: 19 additions & 6 deletions WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,7 @@ @implementation FBAlertViewController

- (IBAction)createAppAlert:(UIButton *)sender
{
UIAlertController *alerController =
[UIAlertController alertControllerWithTitle:@"Magic"
message:@"Should read"
preferredStyle:UIAlertControllerStyleAlert];
[alerController addAction:[UIAlertAction actionWithTitle:@"Will do" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alerController animated:YES completion:nil];
[self presentAlertController];
}

- (IBAction)createAppSheet:(UIButton *)sender
Expand Down Expand Up @@ -57,4 +52,22 @@ - (IBAction)createGPSAccessAlert:(UIButton *)sender
[self.locationManager requestAlwaysAuthorization];
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
for (UITouch *touch in touches) {
if (fabs(touch.maximumPossibleForce - touch.force) < 0.0001) {
[self presentAlertController];
}
}
}

- (void)presentAlertController {
UIAlertController *alerController =
[UIAlertController alertControllerWithTitle:@"Magic"
message:@"Should read"
preferredStyle:UIAlertControllerStyleAlert];
[alerController addAction:[UIAlertAction actionWithTitle:@"Will do" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alerController animated:YES completion:nil];
}

@end
Loading

0 comments on commit a5b61b4

Please sign in to comment.