Skip to content

Commit 8266342

Browse files
author
Mykola Mokhnach
authored
Add support for JSONWP and W3C touch actions (#10)
* Add support for JSONWP and W3C touch actions * Update merge issues
1 parent a93bf4f commit 8266342

20 files changed

+2721
-51
lines changed

WebDriverAgent.xcodeproj/project.pbxproj

Lines changed: 56 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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+
11+
#import <XCTest/XCTest.h>
12+
#import "FBElementCache.h"
13+
14+
NS_ASSUME_NONNULL_BEGIN
15+
16+
@interface XCUIApplication (FBTouchAction)
17+
18+
/**
19+
Perform complex touch action in scope of the current application.
20+
Touch actions are represented as lists of dictionaries with predefined sets of values and keys.
21+
Each dictionary must contain 'action' key, which is one of the following:
22+
- 'tap' to perform a single tap
23+
- 'longPress' to perform long tap
24+
- 'press' to perform press
25+
- 'release' to release the finger
26+
- 'moveTo' to move the virtual finger
27+
- 'wait' to modify the duration of the preceeding action
28+
- 'cancel' to cancel the preceeding action in the chain
29+
Each dictionary can also contain 'options' key with additional parameters dictionary related to the appropriate action.
30+
31+
The following options are mandatory for 'tap', 'longPress', 'press' and 'moveTo' actions:
32+
- 'x' the X coordinate of the action
33+
- 'y' the Y coordinate of the action
34+
- 'element' the corresponding element instance, for which the action is going to be performed
35+
If only 'element' is set then hit point coordinates of this element will be used.
36+
If only 'x' and 'y' are set then these will be considered as absolute coordinates.
37+
If both 'element' and 'x'/'y' are set then these will act as relative element coordinates.
38+
39+
It is also mandatory, that 'release' and 'wait' actions are preceeded with at least one chain item, which contains absolute coordinates, like 'tap', 'press' or 'longPress'. Empty chains are not allowed.
40+
41+
The following additional options are available for different actions:
42+
- 'tap': 'count' (defines count of taps to be performed in a row; 1 by default)
43+
- 'longPress': 'duration' (number of milliseconds to hold/move the virtual finger; 500.0 ms by default)
44+
- 'wait': 'ms' (number of milliseconds to wait for the preceeding action; 0.0 ms by default)
45+
- 'press', 'longPress': 'pressure' (float number, which defines pressure value; 0.0 by default)
46+
47+
List of lists can be passed there is order to perform multi-finger touch action. Each single actions chain is going to be executed by a separate virtual finger in such case.
48+
49+
@param actions Either array of dictionaries, whose format is described above to peform single-finger touch action or array of array to perform multi-finger touch action.
50+
@param elementCache Cached elements mapping for the currrent application. The method assumes all elements are already represented by their actual instances if nil value is set
51+
@param error If there is an error, upon return contains an NSError object that describes the problem
52+
@return YES If the touch action has been successfully performed without errors
53+
*/
54+
- (BOOL)fb_performAppiumTouchActions:(NSArray *)actions elementCache:(nullable FBElementCache *)elementCache error:(NSError **)error;
55+
56+
/**
57+
Perform complex touch action in scope of the current application.
58+
59+
@param actions Array of dictionaries, whose format is described in W3C spec (https://github.com/jlipps/simple-wd-spec#perform-actions)
60+
@param elementCache Cached elements mapping for the currrent application. The method assumes all elements are already represented by their actual instances if nil value is set
61+
@param error If there is an error, upon return contains an NSError object that describes the problem
62+
@return YES If the touch action has been successfully performed without errors
63+
*/
64+
- (BOOL)fb_performW3CTouchActions:(NSArray *)actions elementCache:(nullable FBElementCache *)elementCache error:(NSError **)error;
65+
66+
@end
67+
68+
NS_ASSUME_NONNULL_END
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
11+
#import "XCUIApplication+FBTouchAction.h"
12+
13+
#import "FBAppiumActionsSynthesizer.h"
14+
#import "FBBaseActionsSynthesizer.h"
15+
#import "FBLogger.h"
16+
#import "FBRunLoopSpinner.h"
17+
#import "FBW3CActionsSynthesizer.h"
18+
#import "XCEventGenerator.h"
19+
#import "XCTRunnerDaemonSession.h"
20+
21+
@implementation XCUIApplication (FBTouchAction)
22+
23+
- (BOOL)fb_performActionsWithSynthesizerType:(Class)synthesizerType actions:(NSArray *)actions elementCache:(nullable FBElementCache *)elementCache error:(NSError **)error
24+
{
25+
FBBaseActionsSynthesizer *synthesizer = [[synthesizerType alloc] initWithActions:actions forApplication:self elementCache:elementCache error:error];
26+
if (nil == synthesizer) {
27+
return NO;
28+
}
29+
XCSynthesizedEventRecord *eventRecord = [synthesizer synthesizeWithError:error];
30+
if (nil == eventRecord) {
31+
return NO;
32+
}
33+
return [self fb_synthesizeEvent:eventRecord error:error];
34+
}
35+
36+
- (BOOL)fb_performAppiumTouchActions:(NSArray *)actions elementCache:(nullable FBElementCache *)elementCache error:(NSError **)error
37+
{
38+
return [self fb_performActionsWithSynthesizerType:FBAppiumActionsSynthesizer.class actions:actions elementCache:elementCache error:error];
39+
}
40+
41+
- (BOOL)fb_performW3CTouchActions:(NSArray *)actions elementCache:(nullable FBElementCache *)elementCache error:(NSError **)error
42+
{
43+
return [self fb_performActionsWithSynthesizerType:FBW3CActionsSynthesizer.class actions:actions elementCache:elementCache error:error];
44+
}
45+
46+
- (BOOL)fb_synthesizeEvent:(XCSynthesizedEventRecord *)event error:(NSError *__autoreleasing*)error
47+
{
48+
__block BOOL didSucceed;
49+
[FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
50+
XCEventGeneratorHandler handlerBlock = ^(XCSynthesizedEventRecord *record, NSError *commandError) {
51+
if (commandError) {
52+
[FBLogger logFmt:@"Failed to perform complex gesture: %@", commandError];
53+
}
54+
if (error) {
55+
*error = commandError;
56+
}
57+
didSucceed = (commandError == nil);
58+
completion();
59+
};
60+
61+
[[XCTRunnerDaemonSession sharedSession] synthesizeEvent:event completion:^(NSError *invokeError){
62+
handlerBlock(event, invokeError);
63+
}];
64+
}];
65+
return didSucceed;
66+
}
67+
68+
69+
@end

WebDriverAgentLib/Categories/XCUIElement+FBTap.m

Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -9,69 +9,37 @@
99

1010
#import "XCUIElement+FBTap.h"
1111

12-
#import "FBRunLoopSpinner.h"
13-
#import "FBLogger.h"
14-
#import "FBMacros.h"
15-
#import "FBMathUtils.h"
16-
#import "FBRuntimeUtils.h"
12+
#import "XCUIApplication+FBTouchAction.h"
1713
#import "XCUIElement+FBUtilities.h"
18-
#import "XCEventGenerator.h"
19-
#import "XCSynthesizedEventRecord.h"
20-
#import "XCElementSnapshot+FBHitPoint.h"
2114

22-
const CGFloat FBTapDuration = 0.01f;
2315

2416
@implementation XCUIElement (FBTap)
2517

2618
- (BOOL)fb_tapWithError:(NSError **)error
2719
{
28-
XCElementSnapshot *snapshot = self.fb_lastSnapshot;
29-
CGPoint hitpoint = snapshot.fb_hitPoint;
30-
if (CGPointEqualToPoint(hitpoint, CGPointMake(-1, -1))) {
31-
hitpoint = [snapshot.suggestedHitpoints.lastObject CGPointValue];
32-
}
33-
return [self fb_performTapAtPoint:hitpoint error:error];
20+
NSArray<NSDictionary<NSString *, id> *> *tapGesture =
21+
@[
22+
@{@"action": @"tap",
23+
@"options": @{@"element": self}
24+
}
25+
];
26+
[self fb_waitUntilFrameIsStable];
27+
return [self.application fb_performAppiumTouchActions:tapGesture elementCache:nil error:error];
3428
}
3529

3630
- (BOOL)fb_tapCoordinate:(CGPoint)relativeCoordinate error:(NSError **)error
3731
{
38-
CGPoint hitPoint = CGPointMake(self.frame.origin.x + relativeCoordinate.x, self.frame.origin.y + relativeCoordinate.y);
39-
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
40-
/*
41-
Since iOS 10.0 XCTest has a bug when it always returns portrait coordinates for UI elements
42-
even if the device is not in portait mode. That is why we need to recalculate them manually
43-
based on the current orientation value
44-
*/
45-
hitPoint = FBInvertPointForApplication(hitPoint, self.application.frame.size, self.application.interfaceOrientation);
46-
}
47-
return [self fb_performTapAtPoint:hitPoint error:error];
48-
}
49-
50-
- (BOOL)fb_performTapAtPoint:(CGPoint)hitPoint error:(NSError *__autoreleasing*)error
51-
{
52-
[self fb_waitUntilFrameIsStable];
53-
__block BOOL didSucceed;
54-
[FBRunLoopSpinner spinUntilCompletion:^(void(^completion)(void)){
55-
XCEventGeneratorHandler handlerBlock = ^(XCSynthesizedEventRecord *record, NSError *commandError) {
56-
if (commandError) {
57-
[FBLogger logFmt:@"Failed to perform tap: %@", commandError];
58-
}
59-
if (error) {
60-
*error = commandError;
32+
NSArray<NSDictionary<NSString *, id> *> *tapGesture =
33+
@[
34+
@{@"action": @"tap",
35+
@"options": @{@"element": self,
36+
@"x": @(relativeCoordinate.x),
37+
@"y": @(relativeCoordinate.y)
38+
}
6139
}
62-
didSucceed = (commandError == nil);
63-
completion();
64-
};
65-
66-
// Xcode 10.2 and below
67-
XCEventGenerator *eventGenerator = [XCEventGenerator sharedGenerator];
68-
if ([eventGenerator respondsToSelector:@selector(tapAtTouchLocations:numberOfTaps:orientation:handler:)]) {
69-
[eventGenerator tapAtTouchLocations:@[[NSValue valueWithCGPoint:hitPoint]] numberOfTaps:1 orientation:self.interfaceOrientation handler:handlerBlock];
70-
} else {
71-
[eventGenerator tapAtPoint:hitPoint orientation:self.interfaceOrientation handler:handlerBlock];
72-
}
73-
}];
74-
return didSucceed;
40+
];
41+
[self fb_waitUntilFrameIsStable];
42+
return [self.application fb_performAppiumTouchActions:tapGesture elementCache:nil error:error];
7543
}
7644

7745
@end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 <Foundation/Foundation.h>
11+
12+
#import <WebDriverAgentLib/FBCommandHandler.h>
13+
14+
NS_ASSUME_NONNULL_BEGIN
15+
16+
@interface FBTouchActionCommands : NSObject <FBCommandHandler>
17+
18+
@end
19+
20+
NS_ASSUME_NONNULL_END
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 "FBTouchActionCommands.h"
11+
12+
#import "FBApplication.h"
13+
#import "FBRoute.h"
14+
#import "FBRouteRequest.h"
15+
#import "FBSession.h"
16+
#import "XCUIApplication+FBTouchAction.h"
17+
18+
@implementation FBTouchActionCommands
19+
20+
#pragma mark - <FBCommandHandler>
21+
22+
+ (NSArray *)routes
23+
{
24+
return
25+
@[
26+
[[FBRoute POST:@"/wda/touch/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)],
27+
[[FBRoute POST:@"/wda/touch/multi/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)],
28+
[[FBRoute POST:@"/actions"] respondWithTarget:self action:@selector(handlePerformW3CTouchActions:)],
29+
];
30+
}
31+
32+
#pragma mark - Commands
33+
34+
+ (id<FBResponsePayload>)handlePerformAppiumTouchActions:(FBRouteRequest *)request
35+
{
36+
XCUIApplication *application = request.session.activeApplication;
37+
NSArray *actions = (NSArray *)request.arguments[@"actions"];
38+
NSError *error;
39+
if (![application fb_performAppiumTouchActions:actions elementCache:request.session.elementCache error:&error]) {
40+
return FBResponseWithError(error);
41+
}
42+
return FBResponseWithOK();
43+
}
44+
45+
+ (id<FBResponsePayload>)handlePerformW3CTouchActions:(FBRouteRequest *)request
46+
{
47+
XCUIApplication *application = request.session.activeApplication;
48+
NSArray *actions = (NSArray *)request.arguments[@"actions"];
49+
NSError *error;
50+
if (![application fb_performW3CTouchActions:actions elementCache:request.session.elementCache error:&error]) {
51+
return FBResponseWithError(error);
52+
}
53+
return FBResponseWithOK();
54+
}
55+
56+
@end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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 "FBBaseActionsSynthesizer.h"
11+
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
@interface FBAppiumActionsSynthesizer : FBBaseActionsSynthesizer
15+
16+
@end
17+
18+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)