appium-webdriveragent 6.1.1 → 7.0.0

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.
@@ -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