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

Commit a5b61b4

Browse files
alexander.balabanfacebook-github-bot
authored andcommitted
force touch support
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
1 parent 762760d commit a5b61b4

File tree

10 files changed

+279
-10
lines changed

10 files changed

+279
-10
lines changed

WebDriverAgent.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@
282282
EE7E271F1D06C69F001BEC7B /* FBXCTestCaseImplementationFailureHoldingProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = EE7E271B1D06C69F001BEC7B /* FBXCTestCaseImplementationFailureHoldingProxy.m */; };
283283
EE8BA97A1DCCED9A00A9DEF8 /* FBNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8BA9791DCCED9A00A9DEF8 /* FBNavigationController.m */; };
284284
EE8DDD7920C565FB004D4925 /* XCUIApplicationFBHelpersTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8DDD7820C565FB004D4925 /* XCUIApplicationFBHelpersTests.m */; };
285+
EE8DDD7B20C57320004D4925 /* FBForceTouchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8DDD7A20C57320004D4925 /* FBForceTouchTests.m */; };
286+
EE8DDD7E20C5733C004D4925 /* XCUIElement+FBForceTouch.m in Sources */ = {isa = PBXBuildFile; fileRef = EE8DDD7C20C5733B004D4925 /* XCUIElement+FBForceTouch.m */; };
287+
EE8DDD7F20C5733C004D4925 /* XCUIElement+FBForceTouch.h in Headers */ = {isa = PBXBuildFile; fileRef = EE8DDD7D20C5733C004D4925 /* XCUIElement+FBForceTouch.h */; settings = {ATTRIBUTES = (Public, ); }; };
285288
EE9AB8011CAEE048008C271F /* UITestingUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9AB7FD1CAEE048008C271F /* UITestingUITests.m */; };
286289
EE9B76591CF7987800275851 /* FBRouteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76571CF7987300275851 /* FBRouteTests.m */; };
287290
EE9B768E1CF7997600275851 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EE9B76831CF7997600275851 /* AppDelegate.m */; };
@@ -613,6 +616,9 @@
613616
EE8BA9781DCCED9A00A9DEF8 /* FBNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBNavigationController.h; sourceTree = "<group>"; };
614617
EE8BA9791DCCED9A00A9DEF8 /* FBNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBNavigationController.m; sourceTree = "<group>"; };
615618
EE8DDD7820C565FB004D4925 /* XCUIApplicationFBHelpersTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCUIApplicationFBHelpersTests.m; sourceTree = "<group>"; };
619+
EE8DDD7A20C57320004D4925 /* FBForceTouchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBForceTouchTests.m; sourceTree = "<group>"; };
620+
EE8DDD7C20C5733B004D4925 /* XCUIElement+FBForceTouch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIElement+FBForceTouch.m"; sourceTree = "<group>"; };
621+
EE8DDD7D20C5733C004D4925 /* XCUIElement+FBForceTouch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBForceTouch.h"; sourceTree = "<group>"; };
616622
EE9AB7451CAEDF0C008C271F /* XCUIElement+FBAccessibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBAccessibility.h"; sourceTree = "<group>"; };
617623
EE9AB7461CAEDF0C008C271F /* XCUIElement+FBAccessibility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XCUIElement+FBAccessibility.m"; sourceTree = "<group>"; };
618624
EE9AB7471CAEDF0C008C271F /* XCUIElement+FBIsVisible.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCUIElement+FBIsVisible.h"; sourceTree = "<group>"; };
@@ -911,6 +917,8 @@
911917
71A7EAF41E20516B001DA4F2 /* XCUIElement+FBClassChain.m */,
912918
EEBBD4891D47746D00656A81 /* XCUIElement+FBFind.h */,
913919
EEBBD48A1D47746D00656A81 /* XCUIElement+FBFind.m */,
920+
EE8DDD7D20C5733C004D4925 /* XCUIElement+FBForceTouch.h */,
921+
EE8DDD7C20C5733B004D4925 /* XCUIElement+FBForceTouch.m */,
914922
EE9AB7471CAEDF0C008C271F /* XCUIElement+FBIsVisible.h */,
915923
EE9AB7481CAEDF0C008C271F /* XCUIElement+FBIsVisible.m */,
916924
7136A4771E8918E60024FC3D /* XCUIElement+FBPickerWheel.h */,
@@ -1075,6 +1083,7 @@
10751083
7126FF0D1FD99C7E00DEFB38 /* FBElementScreenshotTests.m */,
10761084
EE006EAC1EB99B15006900A4 /* FBElementVisibilityTests.m */,
10771085
EE6A89361D0B35920083E92B /* FBFailureProofTestCaseTests.m */,
1086+
EE8DDD7A20C57320004D4925 /* FBForceTouchTests.m */,
10781087
EE1E06DB1D18090F007CF043 /* FBIntegrationTestCase.h */,
10791088
EE1E06D91D1808C2007CF043 /* FBIntegrationTestCase.m */,
10801089
EE05BAF91D13003C00A3EB00 /* FBKeyboardTests.m */,
@@ -1421,6 +1430,7 @@
14211430
711084441DA3AA7500F913D6 /* FBXPath.h in Headers */,
14221431
EE35AD771E3B77D600A02D78 /* XCUIRecorderTimingMessage.h in Headers */,
14231432
EE35AD271E3B77D600A02D78 /* XCApplicationMonitor.h in Headers */,
1433+
EE8DDD7F20C5733C004D4925 /* XCUIElement+FBForceTouch.h in Headers */,
14241434
EE158AEA1CBD456F00A3E3F0 /* FBRuntimeUtils.h in Headers */,
14251435
7136A4791E8918E60024FC3D /* XCUIElement+FBPickerWheel.h in Headers */,
14261436
EE35AD511E3B77D600A02D78 /* XCTestObservation-Protocol.h in Headers */,
@@ -1777,6 +1787,7 @@
17771787
EE158AF81CBD456F00A3E3F0 /* FBSpringboardApplication.m in Sources */,
17781788
EE158AD91CBD456F00A3E3F0 /* FBResponseFilePayload.m in Sources */,
17791789
EEE3764A1D59FAE900ED88DD /* XCUIElement+FBWebDriverAttributes.m in Sources */,
1790+
EE8DDD7E20C5733C004D4925 /* XCUIElement+FBForceTouch.m in Sources */,
17801791
EE158ACB1CBD456F00A3E3F0 /* FBTouchIDCommands.m in Sources */,
17811792
EE158ABD1CBD456F00A3E3F0 /* FBDebugCommands.m in Sources */,
17821793
716E0BCF1E917E810087A825 /* NSString+FBXMLSafeString.m in Sources */,
@@ -1821,6 +1832,7 @@
18211832
EE2202131ECC612200A29571 /* FBIntegrationTestCase.m in Sources */,
18221833
EE22021E1ECC618900A29571 /* FBTapTest.m in Sources */,
18231834
7126FF0E1FD99C7E00DEFB38 /* FBElementScreenshotTests.m in Sources */,
1835+
EE8DDD7B20C57320004D4925 /* FBForceTouchTests.m in Sources */,
18241836
);
18251837
runOnlyForDeploymentPostprocessing = 0;
18261838
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import <WebDriverAgentLib/XCUIElement.h>
11+
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
@interface XCUIElement (FBForceTouch)
15+
16+
/**
17+
Waits for element to become stable (not move) and performs sync force touch on element
18+
19+
@param error If there is an error, upon return contains an NSError object that describes the problem.
20+
@param pressure The pressure of the force touch – valid values are [0, touch.maximumPossibleForce]
21+
@param duration The duration of the gesture
22+
@return YES if the operation succeeds, otherwise NO.
23+
*/
24+
- (BOOL)fb_forceTouchWithPressure:(double)pressure duration:(double)duration error:(NSError **)error;
25+
26+
/**
27+
Waits for element to become stable (not move) and performs sync force touch on element
28+
29+
@param relativeCoordinate hit point coordinate relative to the current element position
30+
@param pressure The pressure of the force touch – valid values are [0, touch.maximumPossibleForce]
31+
@param duration The duration of the gesture
32+
@param error If there is an error, upon return contains an NSError object that describes the problem.
33+
@return YES if the operation succeeds, otherwise NO.
34+
*/
35+
- (BOOL)fb_forceTouchCoordinate:(CGPoint)relativeCoordinate pressure:(double)pressure duration:(double)duration error:(NSError **)error;
36+
37+
@end
38+
39+
NS_ASSUME_NONNULL_END
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "XCUIElement+FBForceTouch.h"
11+
12+
#import "FBRunLoopSpinner.h"
13+
#import "FBLogger.h"
14+
#import "FBMacros.h"
15+
#import "FBMathUtils.h"
16+
#import "XCUIElement+FBUtilities.h"
17+
#import "XCEventGenerator.h"
18+
#import "XCSynthesizedEventRecord.h"
19+
#import "XCElementSnapshot+FBHitPoint.h"
20+
#import "XCPointerEventPath.h"
21+
#import "XCTRunnerDaemonSession.h"
22+
23+
@implementation XCUIElement (FBForceTouch)
24+
25+
- (BOOL)fb_forceTouchWithPressure:(double)pressure duration:(double)duration error:(NSError **)error
26+
{
27+
XCElementSnapshot *snapshot = self.fb_lastSnapshot;
28+
CGPoint hitpoint = snapshot.fb_hitPoint;
29+
if (CGPointEqualToPoint(hitpoint, CGPointMake(-1, -1))) {
30+
hitpoint = [snapshot.suggestedHitpoints.lastObject CGPointValue];
31+
}
32+
return [self fb_performFourceTouchAtPoint:hitpoint pressure:pressure duration:duration error:error];
33+
}
34+
35+
- (BOOL)fb_forceTouchCoordinate:(CGPoint)relativeCoordinate pressure:(double)pressure duration:(double)duration error:(NSError **)error
36+
{
37+
CGPoint hitPoint = CGPointMake(self.frame.origin.x + relativeCoordinate.x, self.frame.origin.y + relativeCoordinate.y);
38+
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
39+
/*
40+
Since iOS 10.0 XCTest has a bug when it always returns portrait coordinates for UI elements
41+
even if the device is not in portait mode. That is why we need to recalculate them manually
42+
based on the current orientation value
43+
*/
44+
hitPoint = FBInvertPointForApplication(hitPoint, self.application.frame.size, self.application.interfaceOrientation);
45+
}
46+
return [self fb_performFourceTouchAtPoint:hitPoint pressure:pressure duration:duration error:error];
47+
}
48+
49+
- (BOOL)fb_performFourceTouchAtPoint:(CGPoint)hitPoint pressure:(double)pressure duration:(double)duration error:(NSError *__autoreleasing*)error
50+
{
51+
[self fb_waitUntilFrameIsStable];
52+
__block BOOL didSucceed;
53+
[FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
54+
XCEventGeneratorHandler handlerBlock = ^(XCSynthesizedEventRecord *record, NSError *commandError) {
55+
if (commandError) {
56+
[FBLogger logFmt:@"Failed to perform force touch: %@", commandError];
57+
}
58+
if (error) {
59+
*error = commandError;
60+
}
61+
didSucceed = (commandError == nil);
62+
completion();
63+
};
64+
65+
XCSynthesizedEventRecord *event = [self fb_generateForceTouchEvent:hitPoint pressure:pressure duration:duration orientation:self.interfaceOrientation];
66+
[[XCTRunnerDaemonSession sharedSession] synthesizeEvent:event completion:^(NSError *invokeError){
67+
handlerBlock(event, invokeError);
68+
}];
69+
}];
70+
return didSucceed;
71+
}
72+
73+
- (XCSynthesizedEventRecord *)fb_generateForceTouchEvent:(CGPoint)hitPoint pressure:(double)pressure duration:(double)duration orientation:(UIInterfaceOrientation)orientation
74+
{
75+
XCPointerEventPath *eventPath = [[XCPointerEventPath alloc] initForTouchAtPoint:hitPoint offset:0.0];
76+
[eventPath pressDownWithPressure:pressure atOffset:0.0];
77+
if (![XCTRunnerDaemonSession sharedSession].useLegacyEventCoordinateTransformationPath) {
78+
orientation = UIInterfaceOrientationPortrait;
79+
}
80+
[eventPath liftUpAtOffset:duration];
81+
XCSynthesizedEventRecord *event =
82+
[[XCSynthesizedEventRecord alloc]
83+
initWithName:[NSString stringWithFormat:@"Force touch on %@", NSStringFromCGPoint(hitPoint)]
84+
interfaceOrientation:orientation];
85+
[event addPointerEventPath:eventPath];
86+
return event;
87+
}
88+
89+
@end

WebDriverAgentLib/Commands/FBElementCommands.m

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
#import "FBMathUtils.h"
2525
#import "FBRuntimeUtils.h"
2626
#import "NSPredicate+FBFormat.h"
27+
#import "XCEventGenerator.h"
2728
#import "XCUICoordinate.h"
2829
#import "XCUIDevice.h"
2930
#import "XCUIElement+FBIsVisible.h"
3031
#import "XCUIElement+FBPickerWheel.h"
3132
#import "XCUIElement+FBScrolling.h"
3233
#import "XCUIElement+FBTap.h"
34+
#import "XCUIElement+FBForceTouch.h"
3335
#import "XCUIElement+FBTyping.h"
3436
#import "XCUIElement+FBUtilities.h"
3537
#import "XCUIElement+FBWebDriverAttributes.h"
@@ -74,7 +76,9 @@ + (NSArray *)routes
7476
[[FBRoute POST:@"/wda/touchAndHold"] respondWithTarget:self action:@selector(handleTouchAndHoldCoordinate:)],
7577
[[FBRoute POST:@"/wda/doubleTap"] respondWithTarget:self action:@selector(handleDoubleTapCoordinate:)],
7678
[[FBRoute POST:@"/wda/keys"] respondWithTarget:self action:@selector(handleKeys:)],
77-
[[FBRoute POST:@"/wda/pickerwheel/:uuid/select"] respondWithTarget:self action:@selector(handleWheelSelect:)]
79+
[[FBRoute POST:@"/wda/pickerwheel/:uuid/select"] respondWithTarget:self action:@selector(handleWheelSelect:)],
80+
[[FBRoute POST:@"/wda/element/forceTouch/:uuid"] respondWithTarget:self action:@selector(handleForceTouch:)],
81+
[[FBRoute POST:@"/wda/element/forceTouchByCoordinate/:uuid"] respondWithTarget:self action:@selector(handleForceTouchByCoordinateOnElement:)]
7882
];
7983
}
8084

@@ -241,6 +245,33 @@ + (NSArray *)routes
241245
return FBResponseWithOK();
242246
}
243247

248+
+ (id<FBResponsePayload>)handleForceTouch:(FBRouteRequest *)request
249+
{
250+
FBElementCache *elementCache = request.session.elementCache;
251+
XCUIElement *element = [elementCache elementForUUID:request.parameters[@"uuid"]];
252+
double pressure = [request.arguments[@"pressure"] doubleValue];
253+
double duration = [request.arguments[@"duration"] doubleValue];
254+
NSError *error = nil;
255+
if (![element fb_forceTouchWithPressure:pressure duration:duration error:&error]) {
256+
return FBResponseWithError(error);
257+
}
258+
return FBResponseWithOK();
259+
}
260+
261+
+ (id<FBResponsePayload>)handleForceTouchByCoordinateOnElement:(FBRouteRequest *)request
262+
{
263+
FBElementCache *elementCache = request.session.elementCache;
264+
XCUIElement *element = [elementCache elementForUUID:request.parameters[@"uuid"]];
265+
double pressure = [request.arguments[@"pressure"] doubleValue];
266+
double duration = [request.arguments[@"duration"] doubleValue];
267+
CGPoint forceTouchPoint = CGPointMake((CGFloat)[request.arguments[@"x"] doubleValue], (CGFloat)[request.arguments[@"y"] doubleValue]);
268+
NSError *error = nil;
269+
if (![element fb_forceTouchCoordinate:forceTouchPoint pressure:pressure duration:duration error:&error]) {
270+
return FBResponseWithError(error);
271+
}
272+
return FBResponseWithOK();
273+
}
274+
244275
+ (id<FBResponsePayload>)handleScroll:(FBRouteRequest *)request
245276
{
246277
FBElementCache *elementCache = request.session.elementCache;

WebDriverAgentLib/WebDriverAgentLib.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,6 @@ FOUNDATION_EXPORT const unsigned char WebDriverAgentLib_VersionString[];
5151
#import <WebDriverAgentLib/XCUIElement+FBIsVisible.h>
5252
#import <WebDriverAgentLib/XCUIElement+FBScrolling.h>
5353
#import <WebDriverAgentLib/XCUIElement+FBTap.h>
54+
#import <WebDriverAgentLib/XCUIElement+FBForceTouch.h>
5455
#import <WebDriverAgentLib/XCUIElement+FBUtilities.h>
5556
#import <WebDriverAgentLib/XCUIElement+FBWebDriverAttributes.h>

WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.m

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,7 @@ @implementation FBAlertViewController
2020

2121
- (IBAction)createAppAlert:(UIButton *)sender
2222
{
23-
UIAlertController *alerController =
24-
[UIAlertController alertControllerWithTitle:@"Magic"
25-
message:@"Should read"
26-
preferredStyle:UIAlertControllerStyleAlert];
27-
[alerController addAction:[UIAlertAction actionWithTitle:@"Will do" style:UIAlertActionStyleDefault handler:nil]];
28-
[self presentViewController:alerController animated:YES completion:nil];
23+
[self presentAlertController];
2924
}
3025

3126
- (IBAction)createAppSheet:(UIButton *)sender
@@ -57,4 +52,22 @@ - (IBAction)createGPSAccessAlert:(UIButton *)sender
5752
[self.locationManager requestAlwaysAuthorization];
5853
}
5954

55+
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
56+
[super touchesMoved:touches withEvent:event];
57+
for (UITouch *touch in touches) {
58+
if (fabs(touch.maximumPossibleForce - touch.force) < 0.0001) {
59+
[self presentAlertController];
60+
}
61+
}
62+
}
63+
64+
- (void)presentAlertController {
65+
UIAlertController *alerController =
66+
[UIAlertController alertControllerWithTitle:@"Magic"
67+
message:@"Should read"
68+
preferredStyle:UIAlertControllerStyleAlert];
69+
[alerController addAction:[UIAlertAction actionWithTitle:@"Will do" style:UIAlertActionStyleDefault handler:nil]];
70+
[self presentViewController:alerController animated:YES completion:nil];
71+
}
72+
6073
@end

0 commit comments

Comments
 (0)