appium-webdriveragent 6.1.1 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/WebDriverAgent.xcodeproj/project.pbxproj +0 -20
- package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h +0 -37
- package/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +0 -15
- package/WebDriverAgentLib/Commands/FBTouchActionCommands.m +0 -13
- package/WebDriverAgentLib/FBAlert.m +1 -10
- package/package.json +1 -1
- package/WebDriverAgentLib/Utilities/FBAppiumActionsSynthesizer.h +0 -20
- package/WebDriverAgentLib/Utilities/FBAppiumActionsSynthesizer.m +0 -554
- package/WebDriverAgentTests/IntegrationTests/FBAppiumMultiTouchActionsIntegrationTests.m +0 -206
- package/WebDriverAgentTests/IntegrationTests/FBAppiumTouchActionsIntegrationTests.m +0 -404
|
@@ -1,554 +0,0 @@
|
|
|
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 "FBAppiumActionsSynthesizer.h"
|
|
11
|
-
|
|
12
|
-
#import "FBErrorBuilder.h"
|
|
13
|
-
#import "FBElementCache.h"
|
|
14
|
-
#import "FBLogger.h"
|
|
15
|
-
#import "FBMacros.h"
|
|
16
|
-
#import "FBMathUtils.h"
|
|
17
|
-
#import "FBXCTestDaemonsProxy.h"
|
|
18
|
-
#import "FBProtocolHelpers.h"
|
|
19
|
-
#import "XCUIElement+FBUtilities.h"
|
|
20
|
-
#import "XCUIElement.h"
|
|
21
|
-
#import "XCSynthesizedEventRecord.h"
|
|
22
|
-
#import "XCPointerEventPath.h"
|
|
23
|
-
#import "XCPointerEvent.h"
|
|
24
|
-
|
|
25
|
-
static NSString *const FB_ACTION_KEY = @"action";
|
|
26
|
-
static NSString *const FB_ACTION_TAP = @"tap";
|
|
27
|
-
static NSString *const FB_ACTION_PRESS = @"press";
|
|
28
|
-
static NSString *const FB_ACTION_LONG_PRESS = @"longPress";
|
|
29
|
-
static NSString *const FB_ACTION_MOVE_TO = @"moveTo";
|
|
30
|
-
static NSString *const FB_ACTION_RELEASE = @"release";
|
|
31
|
-
static NSString *const FB_ACTION_CANCEL = @"cancel";
|
|
32
|
-
static NSString *const FB_ACTION_WAIT = @"wait";
|
|
33
|
-
|
|
34
|
-
static NSString *const FB_OPTION_DURATION = @"duration";
|
|
35
|
-
static NSString *const FB_OPTION_COUNT = @"count";
|
|
36
|
-
static NSString *const FB_OPTION_MS = @"ms";
|
|
37
|
-
static NSString *const FB_OPTION_PRESSURE = @"pressure";
|
|
38
|
-
|
|
39
|
-
static NSString *const FB_OPTIONS_KEY = @"options";
|
|
40
|
-
|
|
41
|
-
#if !TARGET_OS_TV
|
|
42
|
-
// Some useful constants might be found at
|
|
43
|
-
// https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewConfiguration.java
|
|
44
|
-
static const double FB_TAP_DURATION_MS = 100.0;
|
|
45
|
-
static const double FB_INTERTAP_MIN_DURATION_MS = 40.0;
|
|
46
|
-
static const double FB_LONG_TAP_DURATION_MS = 600.0;
|
|
47
|
-
|
|
48
|
-
@interface FBAppiumGestureItem : FBBaseGestureItem
|
|
49
|
-
|
|
50
|
-
@end
|
|
51
|
-
|
|
52
|
-
@interface FBTapItem : FBAppiumGestureItem
|
|
53
|
-
|
|
54
|
-
@end
|
|
55
|
-
|
|
56
|
-
@interface FBPressItem : FBAppiumGestureItem
|
|
57
|
-
@property (nonatomic, nullable, readonly) NSNumber *pressure;
|
|
58
|
-
@end
|
|
59
|
-
|
|
60
|
-
@interface FBLongPressItem : FBAppiumGestureItem
|
|
61
|
-
|
|
62
|
-
@end
|
|
63
|
-
|
|
64
|
-
@interface FBWaitItem : FBAppiumGestureItem
|
|
65
|
-
|
|
66
|
-
@end
|
|
67
|
-
|
|
68
|
-
@interface FBMoveToItem : FBAppiumGestureItem
|
|
69
|
-
|
|
70
|
-
@property (nonatomic, nonnull) NSValue *recentPosition;
|
|
71
|
-
|
|
72
|
-
@end
|
|
73
|
-
|
|
74
|
-
@interface FBReleaseItem : FBAppiumGestureItem
|
|
75
|
-
|
|
76
|
-
@end
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@implementation FBAppiumGestureItem
|
|
80
|
-
|
|
81
|
-
- (nullable instancetype)initWithActionItem:(NSDictionary<NSString *, id> *)item
|
|
82
|
-
application:(XCUIApplication *)application
|
|
83
|
-
atPosition:(nullable XCUICoordinate *)atPosition
|
|
84
|
-
offset:(double)offset
|
|
85
|
-
error:(NSError **)error
|
|
86
|
-
{
|
|
87
|
-
self = [super init];
|
|
88
|
-
if (self) {
|
|
89
|
-
self.actionItem = item;
|
|
90
|
-
self.application = application;
|
|
91
|
-
self.offset = offset;
|
|
92
|
-
id options = [item objectForKey:FB_OPTIONS_KEY];
|
|
93
|
-
if (nil != atPosition) {
|
|
94
|
-
self.atPosition = (id) atPosition;
|
|
95
|
-
} else {
|
|
96
|
-
XCUICoordinate *result = [self coordinatesWithOptions:options error:error];
|
|
97
|
-
if (nil == result) {
|
|
98
|
-
return nil;
|
|
99
|
-
}
|
|
100
|
-
self.atPosition = result;
|
|
101
|
-
}
|
|
102
|
-
self.duration = [self durationWithOptions:options];
|
|
103
|
-
if (self.duration < 0) {
|
|
104
|
-
NSString *description = [NSString stringWithFormat:@"%@ value cannot be negative for '%@' action", FB_OPTION_DURATION, self.class.actionName];
|
|
105
|
-
if (error) {
|
|
106
|
-
*error = [[FBErrorBuilder.builder withDescription:description] build];
|
|
107
|
-
}
|
|
108
|
-
return nil;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return self;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
+ (BOOL)hasAbsolutePositioning
|
|
115
|
-
{
|
|
116
|
-
@throw [[FBErrorBuilder.builder withDescription:@"Override this method in subclasses"] build];
|
|
117
|
-
return NO;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
- (double)durationWithOptions:(nullable NSDictionary<NSString *, id> *)options
|
|
121
|
-
{
|
|
122
|
-
return (options && [options objectForKey:FB_OPTION_DURATION]) ?
|
|
123
|
-
((NSNumber *)[options objectForKey:FB_OPTION_DURATION]).doubleValue :
|
|
124
|
-
0.0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
- (nullable XCUICoordinate *)coordinatesWithOptions:(nullable NSDictionary<NSString *, id> *)options
|
|
128
|
-
error:(NSError **)error
|
|
129
|
-
{
|
|
130
|
-
if (![options isKindOfClass:NSDictionary.class]) {
|
|
131
|
-
NSString *description = [NSString stringWithFormat:@"'%@' key is mandatory for '%@' action", FB_OPTIONS_KEY, self.class.actionName];
|
|
132
|
-
if (error) {
|
|
133
|
-
*error = [[FBErrorBuilder.builder withDescription:description] build];
|
|
134
|
-
}
|
|
135
|
-
return nil;
|
|
136
|
-
}
|
|
137
|
-
XCUIElement *element = FBExtractElement((id) options);
|
|
138
|
-
NSNumber *x = [options objectForKey:@"x"];
|
|
139
|
-
NSNumber *y = [options objectForKey:@"y"];
|
|
140
|
-
if ((nil != x && nil == y) || (nil != y && nil == x) || (nil == x && nil == y && nil == element)) {
|
|
141
|
-
NSString *description = [NSString stringWithFormat:@"Either element or 'x' and 'y' options should be set for '%@' action", self.class.actionName];
|
|
142
|
-
if (error) {
|
|
143
|
-
*error = [[FBErrorBuilder.builder withDescription:description] build];
|
|
144
|
-
}
|
|
145
|
-
return nil;
|
|
146
|
-
}
|
|
147
|
-
NSValue *offset = (nil != x && nil != y) ? [NSValue valueWithCGPoint:CGPointMake(x.floatValue, y.floatValue)] : nil;
|
|
148
|
-
return [self hitpointWithElement:element positionOffset:offset error:error];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
@end
|
|
152
|
-
|
|
153
|
-
@implementation FBTapItem
|
|
154
|
-
|
|
155
|
-
+ (NSString *)actionName
|
|
156
|
-
{
|
|
157
|
-
return FB_ACTION_TAP;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
+ (BOOL)hasAbsolutePositioning
|
|
161
|
-
{
|
|
162
|
-
return YES;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
- (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
|
|
166
|
-
allItems:(NSArray *)allItems
|
|
167
|
-
currentItemIndex:(NSUInteger)currentItemIndex
|
|
168
|
-
error:(NSError **)error
|
|
169
|
-
{
|
|
170
|
-
NSTimeInterval currentOffset = FBMillisToSeconds(self.offset);
|
|
171
|
-
NSMutableArray<XCPointerEventPath *> *result = [NSMutableArray array];
|
|
172
|
-
XCPointerEventPath *currentPath = [[XCPointerEventPath alloc]
|
|
173
|
-
initForTouchAtPoint:self.atPosition.screenPoint
|
|
174
|
-
offset:currentOffset];
|
|
175
|
-
[result addObject:currentPath];
|
|
176
|
-
currentOffset += FBMillisToSeconds(FB_TAP_DURATION_MS);
|
|
177
|
-
[currentPath liftUpAtOffset:currentOffset];
|
|
178
|
-
|
|
179
|
-
id options = [self.actionItem objectForKey:FB_OPTIONS_KEY];
|
|
180
|
-
if ([options isKindOfClass:NSDictionary.class]) {
|
|
181
|
-
NSNumber *tapCount = [options objectForKey:FB_OPTION_COUNT] ?: @1;
|
|
182
|
-
for (NSInteger times = 1; times < tapCount.integerValue; ++times) {
|
|
183
|
-
currentOffset += FBMillisToSeconds(FB_INTERTAP_MIN_DURATION_MS);
|
|
184
|
-
XCPointerEventPath *nextPath = [[XCPointerEventPath alloc] initForTouchAtPoint:self.atPosition.screenPoint
|
|
185
|
-
offset:currentOffset];
|
|
186
|
-
[result addObject:nextPath];
|
|
187
|
-
currentOffset += FBMillisToSeconds(FB_TAP_DURATION_MS);
|
|
188
|
-
[nextPath liftUpAtOffset:currentOffset];
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return result.copy;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
- (double)durationWithOptions:(nullable NSDictionary<NSString *, id> *)options
|
|
195
|
-
{
|
|
196
|
-
NSNumber *tapCount = @1;
|
|
197
|
-
if ([options isKindOfClass:NSDictionary.class]) {
|
|
198
|
-
tapCount = [options objectForKey:FB_OPTION_COUNT] ?: tapCount;
|
|
199
|
-
}
|
|
200
|
-
return FB_TAP_DURATION_MS * tapCount.integerValue + FB_INTERTAP_MIN_DURATION_MS * (tapCount.integerValue - 1);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
@end
|
|
204
|
-
|
|
205
|
-
@implementation FBPressItem
|
|
206
|
-
|
|
207
|
-
- (nullable instancetype)initWithActionItem:(NSDictionary<NSString *, id> *)item
|
|
208
|
-
application:(XCUIApplication *)application
|
|
209
|
-
atPosition:(nullable XCUICoordinate *)atPosition
|
|
210
|
-
offset:(double)offset
|
|
211
|
-
error:(NSError **)error
|
|
212
|
-
{
|
|
213
|
-
self = [super initWithActionItem:item
|
|
214
|
-
application:application
|
|
215
|
-
atPosition:atPosition
|
|
216
|
-
offset:offset
|
|
217
|
-
error:error];
|
|
218
|
-
if (self) {
|
|
219
|
-
_pressure = nil;
|
|
220
|
-
id options = [item objectForKey:FB_OPTIONS_KEY];
|
|
221
|
-
if ([options isKindOfClass:NSDictionary.class]) {
|
|
222
|
-
_pressure = [options objectForKey:FB_OPTION_PRESSURE];
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return self;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
+ (NSString *)actionName
|
|
229
|
-
{
|
|
230
|
-
return FB_ACTION_PRESS;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
+ (BOOL)hasAbsolutePositioning
|
|
234
|
-
{
|
|
235
|
-
return YES;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
- (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
|
|
239
|
-
allItems:(NSArray *)allItems
|
|
240
|
-
currentItemIndex:(NSUInteger)currentItemIndex
|
|
241
|
-
error:(NSError **)error
|
|
242
|
-
{
|
|
243
|
-
XCPointerEventPath *result = [[XCPointerEventPath alloc]
|
|
244
|
-
initForTouchAtPoint:self.atPosition.screenPoint
|
|
245
|
-
offset:FBMillisToSeconds(self.offset)];
|
|
246
|
-
if (nil != self.pressure && nil != result.pointerEvents.lastObject) {
|
|
247
|
-
XCPointerEvent *pointerEvent = (XCPointerEvent *)result.pointerEvents.lastObject;
|
|
248
|
-
pointerEvent.pressure = self.pressure.doubleValue;
|
|
249
|
-
}
|
|
250
|
-
return @[result];
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
- (double)durationWithOptions:(nullable NSDictionary<NSString *, id> *)options
|
|
254
|
-
{
|
|
255
|
-
return 0.0;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
@end
|
|
259
|
-
|
|
260
|
-
@implementation FBLongPressItem
|
|
261
|
-
|
|
262
|
-
+ (NSString *)actionName
|
|
263
|
-
{
|
|
264
|
-
return FB_ACTION_LONG_PRESS;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
+ (BOOL)hasAbsolutePositioning
|
|
268
|
-
{
|
|
269
|
-
return YES;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
- (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
|
|
273
|
-
allItems:(NSArray *)allItems
|
|
274
|
-
currentItemIndex:(NSUInteger)currentItemIndex
|
|
275
|
-
error:(NSError **)error
|
|
276
|
-
{
|
|
277
|
-
return @[[[XCPointerEventPath alloc] initForTouchAtPoint:self.atPosition.screenPoint
|
|
278
|
-
offset:FBMillisToSeconds(self.offset)]];
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
- (double)durationWithOptions:(nullable NSDictionary<NSString *, id> *)options
|
|
282
|
-
{
|
|
283
|
-
return (options && [options objectForKey:FB_OPTION_DURATION]) ?
|
|
284
|
-
((NSNumber *)[options objectForKey:FB_OPTION_DURATION]).doubleValue :
|
|
285
|
-
FB_LONG_TAP_DURATION_MS;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
@end
|
|
289
|
-
|
|
290
|
-
@implementation FBWaitItem
|
|
291
|
-
|
|
292
|
-
+ (NSString *)actionName
|
|
293
|
-
{
|
|
294
|
-
return FB_ACTION_WAIT;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
+ (BOOL)hasAbsolutePositioning
|
|
298
|
-
{
|
|
299
|
-
return NO;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
- (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
|
|
303
|
-
allItems:(NSArray *)allItems
|
|
304
|
-
currentItemIndex:(NSUInteger)currentItemIndex
|
|
305
|
-
error:(NSError **)error
|
|
306
|
-
{
|
|
307
|
-
if (nil != eventPath) {
|
|
308
|
-
if (0 == currentItemIndex) {
|
|
309
|
-
return @[];
|
|
310
|
-
}
|
|
311
|
-
FBBaseGestureItem *preceedingItem = [allItems objectAtIndex:currentItemIndex - 1];
|
|
312
|
-
if (![preceedingItem isKindOfClass:FBReleaseItem.class] && currentItemIndex < allItems.count - 1) {
|
|
313
|
-
return @[];
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
NSTimeInterval currentOffset = FBMillisToSeconds(self.offset + self.duration);
|
|
317
|
-
XCPointerEventPath *result = [[XCPointerEventPath alloc] initForTouchAtPoint:self.atPosition.screenPoint
|
|
318
|
-
offset:currentOffset];
|
|
319
|
-
if (currentItemIndex == allItems.count - 1) {
|
|
320
|
-
[result liftUpAtOffset:currentOffset];
|
|
321
|
-
}
|
|
322
|
-
return @[result];
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
- (double)durationWithOptions:(nullable NSDictionary<NSString *, id> *)options
|
|
326
|
-
{
|
|
327
|
-
return (options && [options objectForKey:FB_OPTION_MS]) ?
|
|
328
|
-
((NSNumber *)[options objectForKey:FB_OPTION_MS]).doubleValue :
|
|
329
|
-
0.0;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
@end
|
|
333
|
-
|
|
334
|
-
@implementation FBMoveToItem
|
|
335
|
-
|
|
336
|
-
+ (NSString *)actionName
|
|
337
|
-
{
|
|
338
|
-
return FB_ACTION_MOVE_TO;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
+ (BOOL)hasAbsolutePositioning
|
|
342
|
-
{
|
|
343
|
-
return YES;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
- (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
|
|
347
|
-
allItems:(NSArray *)allItems
|
|
348
|
-
currentItemIndex:(NSUInteger)currentItemIndex
|
|
349
|
-
error:(NSError **)error
|
|
350
|
-
{
|
|
351
|
-
if (nil == eventPath) {
|
|
352
|
-
NSString *description = [NSString stringWithFormat:@"Move To must not be the first action in '%@'", self.actionItem];
|
|
353
|
-
if (error) {
|
|
354
|
-
*error = [[FBErrorBuilder.builder withDescription:description] build];
|
|
355
|
-
}
|
|
356
|
-
return nil;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
[eventPath moveToPoint:self.atPosition.screenPoint
|
|
360
|
-
atOffset:FBMillisToSeconds(self.offset)];
|
|
361
|
-
return @[];
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
@end
|
|
365
|
-
|
|
366
|
-
@implementation FBReleaseItem
|
|
367
|
-
|
|
368
|
-
+ (NSString *)actionName
|
|
369
|
-
{
|
|
370
|
-
return FB_ACTION_RELEASE;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
+ (BOOL)hasAbsolutePositioning
|
|
374
|
-
{
|
|
375
|
-
return NO;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
- (NSArray<XCPointerEventPath *> *)addToEventPath:(XCPointerEventPath *)eventPath
|
|
379
|
-
allItems:(NSArray *)allItems
|
|
380
|
-
currentItemIndex:(NSUInteger)currentItemIndex
|
|
381
|
-
error:(NSError **)error
|
|
382
|
-
{
|
|
383
|
-
if (nil == eventPath) {
|
|
384
|
-
NSString *description = [NSString stringWithFormat:@"Pointer Up must not be the first action in '%@'", self.actionItem];
|
|
385
|
-
if (error) {
|
|
386
|
-
*error = [[FBErrorBuilder.builder withDescription:description] build];
|
|
387
|
-
}
|
|
388
|
-
return nil;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
[eventPath liftUpAtOffset:FBMillisToSeconds(self.offset)];
|
|
392
|
-
return @[];
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
- (double)durationWithOptions:(nullable NSDictionary<NSString *, id> *)options
|
|
396
|
-
{
|
|
397
|
-
return 0.0;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
@end
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
@interface FBAppiumGestureItemsChain : FBBaseActionItemsChain
|
|
404
|
-
|
|
405
|
-
@end
|
|
406
|
-
|
|
407
|
-
@implementation FBAppiumGestureItemsChain
|
|
408
|
-
|
|
409
|
-
- (void)addItem:(FBBaseActionItem *)item
|
|
410
|
-
{
|
|
411
|
-
self.durationOffset += ((FBAppiumGestureItem *) item).duration;
|
|
412
|
-
[self.items addObject:item];
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
- (void)reset
|
|
416
|
-
{
|
|
417
|
-
[self.items removeAllObjects];
|
|
418
|
-
self.durationOffset = 0.0;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
@end
|
|
422
|
-
|
|
423
|
-
@implementation FBAppiumActionsSynthesizer
|
|
424
|
-
|
|
425
|
-
- (NSArray<NSDictionary<NSString *, id> *> *)preprocessAction:(NSArray<NSDictionary<NSString *, id> *> *)touchActionItems
|
|
426
|
-
{
|
|
427
|
-
NSMutableArray<NSDictionary<NSString *, id> *> *result = [NSMutableArray array];
|
|
428
|
-
BOOL shouldSkipNextItem = NO;
|
|
429
|
-
for (NSDictionary<NSString *, id> *touchItem in [touchActionItems reverseObjectEnumerator]) {
|
|
430
|
-
id actionItemName = [touchItem objectForKey:FB_ACTION_KEY];
|
|
431
|
-
if ([actionItemName isKindOfClass:NSString.class] && [actionItemName isEqualToString:FB_ACTION_CANCEL]) {
|
|
432
|
-
shouldSkipNextItem = YES;
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
if (shouldSkipNextItem) {
|
|
436
|
-
shouldSkipNextItem = NO;
|
|
437
|
-
continue;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
id options = [touchItem objectForKey:FB_OPTIONS_KEY];
|
|
441
|
-
if (![options isKindOfClass:NSDictionary.class]) {
|
|
442
|
-
[result addObject:touchItem];
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
id origin = FBExtractElement(options);
|
|
446
|
-
XCUIElement *element;
|
|
447
|
-
if ([origin isKindOfClass:XCUIElement.class]) {
|
|
448
|
-
element = origin;
|
|
449
|
-
} else if ([origin isKindOfClass:NSString.class]) {
|
|
450
|
-
element = [self.elementCache elementForUUID:(NSString *)origin];
|
|
451
|
-
} else {
|
|
452
|
-
[result addObject:touchItem];
|
|
453
|
-
continue;
|
|
454
|
-
}
|
|
455
|
-
NSMutableDictionary *elementDict = FBCleanupElements(options).mutableCopy;
|
|
456
|
-
[elementDict addEntriesFromDictionary:FBToElementDict(element)];
|
|
457
|
-
NSMutableDictionary<NSString *, id> *processedItem = touchItem.mutableCopy;
|
|
458
|
-
processedItem[FB_OPTIONS_KEY] = elementDict.copy;
|
|
459
|
-
[result addObject:processedItem.copy];
|
|
460
|
-
}
|
|
461
|
-
return [[result reverseObjectEnumerator] allObjects];
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
- (nullable NSArray<XCPointerEventPath *> *)eventPathsWithAction:(NSArray<NSDictionary<NSString *, id> *> *)action
|
|
465
|
-
error:(NSError **)error
|
|
466
|
-
{
|
|
467
|
-
static NSDictionary<NSString *, Class> *gestureItemsMapping;
|
|
468
|
-
static dispatch_once_t onceToken;
|
|
469
|
-
dispatch_once(&onceToken, ^{
|
|
470
|
-
NSMutableDictionary<NSString *, Class> *itemsMapping = [NSMutableDictionary dictionary];
|
|
471
|
-
for (Class cls in @[FBTapItem.class,
|
|
472
|
-
FBPressItem.class,
|
|
473
|
-
FBLongPressItem.class,
|
|
474
|
-
FBMoveToItem.class,
|
|
475
|
-
FBWaitItem.class,
|
|
476
|
-
FBReleaseItem.class]) {
|
|
477
|
-
[itemsMapping setObject:cls forKey:[cls actionName]];
|
|
478
|
-
}
|
|
479
|
-
gestureItemsMapping = itemsMapping.copy;
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
FBAppiumGestureItemsChain *chain = [[FBAppiumGestureItemsChain alloc] init];
|
|
483
|
-
BOOL isAbsoluteTouchPositionSet = NO;
|
|
484
|
-
for (NSDictionary<NSString *, id> *actionItem in action) {
|
|
485
|
-
id actionItemName = [actionItem objectForKey:FB_ACTION_KEY];
|
|
486
|
-
if (![actionItemName isKindOfClass:NSString.class]) {
|
|
487
|
-
NSString *description = [NSString stringWithFormat:@"'%@' property is mandatory for gesture chain item %@", FB_ACTION_KEY, actionItem];
|
|
488
|
-
if (error) {
|
|
489
|
-
*error = [[FBErrorBuilder.builder withDescription:description] build];
|
|
490
|
-
}
|
|
491
|
-
return nil;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
Class gestureItemClass = [gestureItemsMapping objectForKey:actionItemName];
|
|
495
|
-
if (nil == gestureItemClass) {
|
|
496
|
-
NSString *description = [NSString stringWithFormat:@"%@ value '%@' is unknown", FB_ACTION_KEY, actionItemName];
|
|
497
|
-
if (error) {
|
|
498
|
-
*error = [[FBErrorBuilder.builder withDescription:description] build];
|
|
499
|
-
}
|
|
500
|
-
return nil;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
FBAppiumGestureItem *gestureItem = nil;
|
|
504
|
-
if ([gestureItemClass hasAbsolutePositioning]) {
|
|
505
|
-
gestureItem = [[gestureItemClass alloc] initWithActionItem:actionItem application:self.application atPosition:nil offset:chain.durationOffset error:error];
|
|
506
|
-
isAbsoluteTouchPositionSet = YES;
|
|
507
|
-
} else {
|
|
508
|
-
if (!isAbsoluteTouchPositionSet) {
|
|
509
|
-
if (error) {
|
|
510
|
-
NSString *description = [NSString stringWithFormat:@"'%@' %@ should be preceded by an item with absolute positioning", actionItemName, FB_ACTION_KEY];
|
|
511
|
-
*error = [[FBErrorBuilder.builder withDescription:description] build];
|
|
512
|
-
}
|
|
513
|
-
return nil;
|
|
514
|
-
}
|
|
515
|
-
FBAppiumGestureItem *lastItem = [chain.items lastObject];
|
|
516
|
-
gestureItem = [[gestureItemClass alloc] initWithActionItem:actionItem
|
|
517
|
-
application:self.application
|
|
518
|
-
atPosition:lastItem.atPosition
|
|
519
|
-
offset:chain.durationOffset
|
|
520
|
-
error:error];
|
|
521
|
-
}
|
|
522
|
-
if (nil == gestureItem) {
|
|
523
|
-
return nil;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
[chain addItem:gestureItem];
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
return [chain asEventPathsWithError:error];
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
- (nullable XCSynthesizedEventRecord *)synthesizeWithError:(NSError **)error
|
|
533
|
-
{
|
|
534
|
-
XCSynthesizedEventRecord *eventRecord;
|
|
535
|
-
BOOL isMultiTouch = [self.actions.firstObject isKindOfClass:NSArray.class];
|
|
536
|
-
eventRecord = [[XCSynthesizedEventRecord alloc]
|
|
537
|
-
initWithName:(isMultiTouch ? @"Multi-Finger Touch Action" : @"Single-Finger Touch Action")
|
|
538
|
-
interfaceOrientation:self.application.interfaceOrientation];
|
|
539
|
-
for (NSArray<NSDictionary<NSString *, id> *> *action in (isMultiTouch ? self.actions : @[self.actions])) {
|
|
540
|
-
NSArray<NSDictionary<NSString *, id> *> *preprocessedAction = [self preprocessAction:action];
|
|
541
|
-
NSArray<XCPointerEventPath *> *eventPaths = [self eventPathsWithAction:preprocessedAction error:error];
|
|
542
|
-
if (nil == eventPaths) {
|
|
543
|
-
return nil;
|
|
544
|
-
}
|
|
545
|
-
for (XCPointerEventPath *eventPath in eventPaths) {
|
|
546
|
-
[eventRecord addPointerEventPath:eventPath];
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
return eventRecord;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
@end
|
|
553
|
-
|
|
554
|
-
#endif
|